好きな言語はC++
です.
いかがお過ごしでしょうか.私は今朝寝違えたため,首がとても痛いです.
初めに
Objcを触る機会があって,基本ができていない気がしてきたので,今年の4月頃に勉強しました.
それをこの2022年の最後として,Objective-Cを再確認しよう!という記事です.
すべてを書くと長すぎるので端折るか,また違う記事として書こうと思います.
間違っているところがありましたら,ぜひコメントお願いします!
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
というコンパイラ指定子を利用することで自動的にアクセサの生成 も してくれるからだそうです.
重要なものとして,この指定子はインスタンス変数の生成を行います.つまり,プロパティを使用しなければ以下のように記述する必要があります.昔のObj-Cだと以下の様に記述するのが普通だったらしいですが,モダンObj-Cでは@propertyの使用が推奨されています.
@interface ClassA: NSObject{
int a;
}
// 省略
この場合,self->a
でアクセスします.アロー演算子でアクセスした場合には,生のメンバをさわることのになるので,自動生成されたゲッタとセッタを通しません.したがって,propertyにatomicを指定していても,排他処理などを通さないため,スレッドセーフでなくなります.
何らかの意図がない限りself.a
でアクセスしましょう.
プロパティでは属性の設定が詳しく行えます
オプション名 | 説明 |
---|---|
readonly | 読み取りのみ |
readwrite | 読み書き可能 |
assign | 代入によって後で設定されることを示す.プリミティブな型に指定 |
retain | 保持されながら設定されることを示す. |
strong | 強参照 |
weak | 弱参照 |
copy | コピーされて設定されることを示す.強い参照だがコピーを受け取る. |
getter | ゲッターの名前を指定 |
setter | セッターの名前を指定 |
atomic | スレッドセーフ |
nonatomic | スレッドセーフにしない |
相容れない設定でなければすべて設定することができます.
例えば
@property (nonatomic, strong) int a;
などです.
atomic / nonatomic
atomic
を指定した場合, セッターにはlinux
でいうところのpthread_mutex
で囲んで,ロックとアンロックをかけてくれます.
ゲッターには, セッターの排他制御に加え,retain
とautorelease
が追加されます.これらはそれぞれ確保と自動的な開放(参照が切れたら解放) を行います.つまり,ゲッターで取得されたオブジェクトや値が勝手に開放されるのを防いでくれます.
synthesize
こいつは@property
で宣言されたインスタンス変数に対して,実装ファイル内で自動的に変数の生成を行ってインターフェースと統合してくれます.
@synthesize
宣言しないと_*
という名前で自動的に生成されますが,Xcodeで警告がたくさん出るので書いた方がよさそうです.
ちなみに@synthesize
のスコープはこのコンパイラ指定子より後ろなので,@implementaion
の直後に書くのが良いです.
クラス(Object)をメンバ変数にするとき
クラスを使用するときはポインタで扱います.NSArray
ならば
@property (atomic) NSArray* ary;
となります.C++の感覚で行くと自分が作ったクラスをほかのクラスで利用するときヘッダファイルに#include
すると思います.Objc も同様で#import
すればいいのですが,@class
を利用するとクラスのプロトタイプ宣言的なことが行えます.そうすることで,余計な情報を読み込まなくて済みます.
「余計な情報」というのは例えば, Project-Swift.h
のような自動変換されたコードで,恋ったコードをヘッダファイルに読み込んでしまうと,読み込んだ側のヘッダファイルの読み込み先まで影響が出てしまいます.
これの問題はC++
のコードでも同じで, C/C++
のヘッダが読み込まれるとそのファイルは純粋なObjective-Cと認識されなくなるため,他のファイルからimport
した際にC/C++
のコードとして認識されてしまいます.
従って,私はObjective-CのヘッダファイルではC/C++のヘッダファイルは読み込まず, プロトタイプ宣言に止めています.
P.S.
Objective-C
の情報や書き方のパターンがたくさんあってどれがいいのかとても悩みました.
書き方やスタイルについてはこちらのNYTimes
のガイドが参考になりました.