なぜNSStringをプロパティにする際にcopyオプションを選択すべきなのか

さて、`NSString`をプロパティとして保持すること、たくさんありますよね。[Googleのコーディング規約](http://www.textdrop.net/google-styleguide-ja/objcguide.xml#%E3%82%AF%E3%83%A9%E3%82%B9%E5%90%8D)にもcopyにするよう書いてあります。

自分もそうしていたのですが、**なぜそうしなければいけないのか**というところまで考えないエンジニア失格状態だったのでちゃんと考えてみました。

さて、そもそも`copy`メソッドとは何なのか？

##copyメソッド
`copy`メソッドは`NSObject`で定義されているのですが、`copy`メソッドを呼ぶと`copyWithZone:`メソッドが内部的に呼ばれるようになっています。

[APIリファレンス](https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/classes/nsobject_class/reference/reference.html#//apple_ref/occ/instm/NSObject/copy)を見るとしっかりと書かれています。

> Return Value
The object returned by the NSCopying protocol method copyWithZone:,.

`copyWithZone:`メソッドは`NSCopying`プロトコルのメソッドです。ただし`NSObject`は`copy`メソッドの定義はしていますが`NSCopying`プロトコルは採用していません。`copyWithZone:`が実装されていない場合には例外が発生します。


##copyWithZone:メソッド
前述の通り`NSCopying`プロトコルのメソッドです。

```objc
- (id)copyWithZone:(NSZone *)zone
```

引数はNSZone型のオブジェクトです。ゾーンは以前、実行効率を向上されるために使用されていたそうですが、もう使われていないとのこと（ゾーンについては[こちら](http://news.mynavi.jp/column/objc/059/)を参照）。現在は`copy`メソッドが呼ばれた際は引数に`NULL`を指定して自身の`copyWithZone:`を呼び出すようになっています。

`copyWithZone:`についてAPIリファレンスを見てみると、このようなことが書かれています。

> The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.

immutableなオブジェクトを返すようになっています。ただ`copy`の振る舞いはクラスによって変わってくるようです。

##プロパティのcopyについて
`copy`がクラスによって振る舞いを変えるということが分かりました。

ここでプロパティのオプションに`copy`を指定すると、内部的にどのような処理が行われるか見てみましょう。

```objc
- (void)setName:(TYPE)obj {
    if (name != obj) {
        name = [obj copy];
    }
}
```

`copy`を指定した場合、このようにセッタが作られます。`copyWithZone:`を考慮すると、mutableなオブジェクトがセットされるとimmutableなオブジェクトになります。

##NSStringのcopyメソッド
ここでようやく`NSString`に触れられます。**`NSString`クラスのインスタンスに`copy`を送っても同じアドレスを指すようになっています。**

```objc
NSString *string = NSStringFromClass([self class]);
NSLog(@"%p",string); // 0x8dbb950
NSLog(@"%p",[string copy]); // 0x8dbb950
```

そもそもimmutable = 不変なオブジェクトで変わることは考えられないので、わざわざコピーを作る必要はないということなのでしょう。つまりimmutableなオブジェクトが渡された場合、（内部的に`copy`は呼ばれるが）結果的に`strong`と同じ動作になります。

`NSMutableString`の場合は、通常のコピーが行われます。

##NSMutableStringによる意図しない変更
ではプロパティのオプションを`strong`にした場合に何が困るのでしょうか？

**プロパティのオプションを`strong`にした場合、参照している`NSMutableString`オブジェクトの値が変更されると、プロパティの値も変更されてしまい、意図しない変更が起きてしまいます。**

それぞれプロパティを用意して試してみましょう。

```objc:ViewController.m
@interface ViewController ()
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy)   NSString *copiedString;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"one, two, three"];

    self.strongString = mutableString;
    self.copiedString = mutableString;
    
    [mutableString appendString:@", four"];
    
    NSLog(@"%@",self.strongString);
    // one, two, three, four

    NSLog(@"%@",self.copiedString);
    // one, two, three
}
```

上記の例では`self.strongString`は`mutableString`と同じアドレスを指しているので、`mutableString`が変更されたことにより`self.strongString`も変更されてしまっています。

`self.copiedString`の方は、`copy`が行われ新たなメモリ領域が作られます。よって`mutableString`の変更の影響は受けません。

プロパティに`copy`オプションを指定しておけば、`NSMutableString`の代入を行って意図しない変更が発生する心配をせずに済みますね。

##まとめ
以上、ここまで`NSString`について書きましたが、immutableなクラスの`copy`がインスタンス自身を返すようになっていたり、mutableなクラスの`copy`の返り値がimmutableなのは`NSArray`や`NSDictionary`も同様です。

まとめるとこんな感じになります。

1.`NSString`や`NSArray`などのimmutableなクラスの`copy`メソッドは同じアドレスを指すようになっている。よって`strong`でも`copy`でも動作は変わらない。

2.`NSMutableString`の`copy`メソッドは、immutableなオブジェクトが返される。

mutableなクラスを渡されないことは保証できない、immutableなオブジェクトの場合はプロパティに`copy`を指定しても`strong`と変わらない動作をする。

これが`copy`オプションを選択すべき理由です。