LoginSignup
50
48

More than 5 years have passed since last update.

propertyアクセスで注意すること

Last updated at Posted at 2013-05-19

唐突だが、今日みたいな眠れない夜に記事を書くのもアリだなと思ったので書く。(日曜に寝過ぎた)

たとえば、UIViewにはallocというメソッドがある。
これはメッセージ記法のメソッド呼び出しでもいけるし、propertyアクセスでもメモッド呼び出しできる。
その次にinitってやると思うが、これもpropertyアクセスで呼び出せる。
つまり、UIView.alloc.initって書き方ができてしまうんですね。

じゃあ、どっちがいいの?みたいなことになり、結論からいえば、こういう書き方すんじゃねえって話ではあるんだけど、まずpropertyアクセスってなんだろうなみたいなところを抑えておく必要ある、ということになる。
その辺からざっくりと振り返ってみたい。

まず、assign,retain,copyのプロパティ属性の話をする。
次に、KVOの話をする。
次に、どういう場合にpropertyを使うかの話をする。

assign,retain,copy

assign.m
@property (assign) CGRect testFrame;

-(void)test:(CGFrame)frame{
  self.testFrame = frame;
}

CGRectみたいな、インスタンスじゃない、スカラを扱うときがassignとおぼえておく。int32_tやint32_t*なんかのプリミティブもassign。C言語の代入と一緒。
上のtest関数の例だと、frameの代入でスタックの内容がtestFrameの領域にコピーされる。アドレスも使える。

retain.m
@property (retain) UIView *testView;

-(void)test:(UIView*)baseView{
  self.testView = [[UIView alloc] initWithFrame:self.testFrame];
  [baseView addSubview:self.testView];
  [self.testView release];
}

retainは、主にイミュータブルなインスタンスを扱いたいときに使えばいい。また、オブジェクト指向を崩さないスタイルでもあるので、設計を美しくしたいのであれば積極的に使うといい。
retainはインスタンスの参照カウンタを1増やす。上の場合だと、testViewはinitで1になり、そのあとaddSubView:self.testViewで2になる。これはプロパティアクセスしていて、プロパティ属性がretainだからだ。
だから、そのあとで必ずreleaseを呼んでカウンタを1に減らそう。

あと、propertyに対するreleaseはやめておいたほうがいい。上記の場合だと、autoreleaseを使うのが無難だろう。上記の例でも別にいいのだが、意味の無いコードが展開されてしまう。わかりやすく書くと、次のような感じだ。

retain.m
[[_testView retain] autorelease];
[_testView release];

最後のpropertyに対するreleaseは上のコードと等価である。こういうのは無駄だし意味がない。autoreleaseを使おう。
ただし、autoreleaseは、繰り返し処理の中でやると、ループ内でヒープが膨れ上がることがあるので(NSAutoReleasePoolが解放されるのがその後だったりするとそうなる)、ループ内で使うときは気をつけよう。

copy.m
@interface CustomUIView *customUIView;
@property (copy) dispatch_block_t callBack;
@end

-(void)test{
  self.customUIVIew.callBack = ^{
       // something
  };
}

まず、copyはNSCopyingプロトコルが実装されていないオブジェクトに対して設定すると落ちる。NSObjectだけだと実装されていないので、使用する際は注意。とはいえ、NSCopyingプロトコルが実装されているオブジェクトでassignにしていると、コンパイル時に、ワーニングが出る。
上記の例だと、callBackにsomethingのコピーが入る。callBackはこのtestが呼び出されたときのキャプチャが入るので、呼び出しごとに内容が異なる。したがってretainは適さない。
あと、当然だが、copyをすることによって、ヒープメモリにオブジェクトの領域が確保されるので、解放についてはきちんと意識しよう。

KVO

次に、KVOについて。
KeyValueObservationというObjective-C特有の機構がある。
プロパティを監視しておいて、プロパティが変更されたら、監視オブジェクトに通知がいくというアレである。

initやdeallocで、self.view = nilってなぜやってはいけないのか?
selfに対するプロパティ代入をすると、このKVOが飛んで、まだinit,deallocされてない状態のselfが監視オブジェクトに渡されて、不完全なオブジェクトを扱うことになってしまうので、initやdeallocでself.アクセスすることは避けよう。

See also
Don’t Message self in Objective-C init (or dealloc)
プロパティとインスタンス変数(ivar)

だから、次のように書こう。

initAndDealloc.m
- (id)init:(UIView*)view withFrame:(CGFrame)frame withString:(NSMutableString*)string{
   if(self = [super init]){
   _view = [view retain];//self.view = viewと一緒
   _frame = frame;//self.frame = frameと一緒
   _string = [string copy];//self.string = stringと一緒
   }
  return self;
}
- (void)dealloc{
  [_view release];
  [_string release];
  //_frameは、インスタンスではないので解放しなくていい
  [super dealloc];
}

あと、deallocではreleaseしたあとに、nilを代入するということをやる人もいると思うが、それはしない。
dealloc後のプロパティの参照でわざと落ちるようにして、バグ検出につかう。

Aho.alloc.initの話

この記法はargumentがないやつしか使えない。引数が2つ以上あると、この呼び出し方ができないので、だから、根本的には混乱をきたすだけの書き方、メソッドについてはメッセージ記法による呼び出しが正解。

とりあえず、今日はこんなところか。また続きは気が向いたら書く。

50
48
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
50
48