LoginSignup
13
4

2022年にいまさら振り返るObjective-C

Last updated at Posted at 2022-12-22

好きな言語はC++です.どうも @mizuhugu35 です.
いかがお過ごしでしょうか.私は今朝寝違えたため,首がとても痛いです.

初めに

Objcを触る機会があって,基本ができていない気がしてきたので,今年の4月頃に勉強しました.
それをこの2022年の最後として,Objective-Cを再確認しよう!という記事です.

(iOSアプリ開発は,Swiftから入ったクチです)

すべてを書くと長すぎるので端折るか,また違う記事として書こうと思います.

間違っているところがありましたら,ぜひコメントお願いします!

Objective-C

名前の通りオブジェクト指向の言語で,メッセージ式と呼ばれる記述方法が特徴的です.

// - (id) a; で自分自身を返すメソッド
[object a];
// ネストできる
[[[object a] a] a];

初めて見たときは, なんかLISPみたいな見た目してんな...と思いました.

コンパイラ指定子

@から始める命令のことで,代表的なのは@intarface,@implementation, @property,@synthesizeとかです.
Objcでは直接C言語を使用できるので,区別できるようになっています.

クラス

クラスを宣言するときには,実装ファイル (m/mm)インターフェイスファイル (h)の二つに分けて記述します.

インターフェース

ヘッダファイルには外部に公開したいメンバ関数(メソッド)などを記述します.C++でいうところのpublicに指定したものに相当します.ここで宣言したメソッドは必ず実装しなければなりません.また継承するにはクラス名の隣に: 親クラス名とします.

@interface ClassA: NSObject
@property (nonatomic) int a;

- (id) initWithValue: (int) value;
- (void) addAndSayA:(int) value;

@end

-から始まるものはインスタンスメソッドということを示しています.+から始まればクラスメソッドです.

id型はいわばvoid *型です.型が変わる動的に扱いたいときにはこのidが使えるわけですね.

実装

実装ファイルにはプライベートなメソッドとインターフェイスで宣言されたメソッドの両方を記述していきます.
self.*としてアクセスすることに注意です.この記述では気が付きませんが,実はこれゲッタとセッタを通してます.C/C++の感覚でいると???ですので... 

@implementation ClassA
@synthesize a;

- (id) initWithValue: (int) value{
    if(self = [super init]){
        self.a = value;
    }
    return self;
}

- (void) __addA:(int) value{
  self.a += value;
}

- (void) addAndSayA:(int) value{
  [self __addA: value];
  NSLog(@"%d", self.a);
}

@end

property

先のコードではGetter/Setterなどのアクセサを宣言していなかったと思います.実装ファイルでドット演算子を使用し,
自身のインスタンス変数にアクセスすることができました.本来であればドット演算子は使用できず,[]を使わなければなりません.またGetter/Setterの宣言・定義も必要なはずです.

これは@propertyというコンパイラ指定子を利用することで自動的にアクセサの生成 してくれる指定子のおかげらしいです.

重要なものとして,この指定子はインスタンス変数の生成を行います.つまり,プロパティを使用しなければ以下のように記述する必要があるます.

@interface ClassA: NSObject{
   int a;
}
// 省略

この場合,self->aでアクセスします.実装ファイル内で「なんでselfはポインタぽいのにアロー演算子じゃないの」と思ったのですが,このプロパティ君がゲッタとセッタを「ドット演算子」で提供してるからというからくりだったわけです.

ちなみにself->aを使うとプロパティで指定できる排他処理などを通さないため,スレッドセーフでなくなります.だめです.

プロパティでは属性の設定が詳しく行えます

オプション名 説明
readonly 読み取りのみ
readwrite 読み書き可能
assign 代入によって後で設定されることを示す.プリミティブな型に指定
retain 保持されながら設定されることを示す.参照カウントを増やす.
strong 強い参照で設定される.勝手に開放されないように指定
weak 弱すぎ参照で設定される.勝手に開放されるかも.nilが設定されるらしい
copy コピーされて設定されることを示す.強い参照だがコピーを受け取る.
getter ゲッターの名前を指定
setter セッターの名前を指定
atomic スレッドセーフ.つまり排他的
nonatomic スレッドセーフにしない

相容れない設定でなければすべて設定することができます.
例えば

@property (nonatomic, readonly, strong) int a;

などです.

strong/weakなどはARCが有効でないと使えません.(あたりまえ)

atomic / nonatomic

atomicを指定した場合, セッターにはlinuxでいうところのptherad_mutexで囲んで,ロックとアンロックをかけてくれます.

ゲッターには, セッターの排他制御に加え,retainautoreleaseが追加されるそうです.これらはそれぞれ確保自動的な開放(参照が切れたら解放) を行います.つまり,ゲッターで取得されたオブジェクトや値が勝手に開放されるのを防いでくれます.

synthesize

こいつは@propertyで宣言されたインスタンス変数に対して,実装ファイル内で自動的に変数の生成を行ってインターフェースと統合してくれます.
@synthesize 宣言しないと_*という名前で自動的に生成されますが,Xcodeで警告がたくさん出るので書いた方がよさそうです.

ちなみに@synthesize のスコープはこのコンパイラ指定子より後ろなので,@implementaionの直後に書くのが良いです.

クラス(Object)をメンバ変数にするとき

クラスを使用するときはポインタで扱います.NSArrayならば

@property (atomic) NSArray* ary; 

となります.C++の感覚で行くと自分が作ったクラスをほかのクラスで利用するときヘッダファイルに#includeすると思います.Objc も同様で#importすればいいのですが,@classを利用するとクラスのプロトタイプ宣言的なことが行えます.そうすることで,余計な情報を読み込まなくて済みます.

「余余計な情報」というのは例えば, Project-Swift.hのような自動変換されたコードで,こういったものはトラブル回避のため実装ファイルでのみ読み込んだ方がいいからです.
ヘッダファイルは他のクラスで利用するときに, importしますが,そこにProject-Swift.hなどが読み込まれていると自動生成されたコード等の影響範囲が大きくなってしまいます.

これの問題はC++のコードでも同じで, C/C++のヘッダが読み込まれるとそのファイルは純粋なObjective-Cではなくなってしまうため,他のファイルからimportした際にC/C++のコードとして認識されてしまいます.

従って,私はObjective-CのヘッダファイルではC/C++のヘッダファイルは読み込まず, プロトタイプ宣言に止めています.

まとめ

当たり前すぎることで,はずかしいですが

// ダメ✖ : インスタンス変数の宣言
@interface ClassA: NSObject{
    int a;
}

@end

// すばらしい ◯ : インスタンス変数の宣言
@interface ClassA: NSObject
@property (nonatomic) int a;
@end

// ダメ✖ : アクセスするときは..
self->a;
((ClassA*)objc)->a;

// すばらしい ◯ : アクセスするときは.. ドット演算子でGetter/Setterを通す!
self.a;
((ClassA*)objc).a;

ということですね.

アドベントカレンダーには書かないと思いますが,そのほかのメモリ管理におけるARC, リファレンスカウンタ, それすら使わない手動管理,C++クラスをWrapしれ使用する方法などもそのうち記事に書きたいです.

P.S.
Objective-Cの情報や書き方のパターンがたくさんあってどれがいいのかとても悩みました.
書き方やスタイルについてはこちらのNYTimesのガイドが参考になりました.

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4