先日App StoreからリリースされたXcode 4.3ですが、個人的には結構驚きな変更がありました。ARCを利用している場合に、プロパティのデフォルト属性(オブジェクトの所有に関する属性)が変更になっているではないですか。
これまでのデフォルト属性はassign
オブジェクトの所有に関するデフォルト属性は、これまでassignでした。したがって、オブジェクトのプロパティで属性指定を行わないと警告が出ていたと思います。
また、Xcode 4.2 + ARC環境では属性指定は必須でした。これは、readonlyプロパティのときも同様です。属性を指定しないとエラーとなります(参考:[iOS5] ARC : プロパティ属性と使い方)。エラーになるのは、インスタンス変数生成時にどの所有修飾子をつけていいか分からないためです。
Xcode 4.3 + ARCでのデフォルト属性はstrong
Xcode 4.3でも、ARCを利用しない場合はこれまでどおりのようですが、ARCを使っているとデフォルト属性がstrongになっています。つまり、属性指定なしでの宣言が可能となったということです。
// interface
@property (nonatomic) NSString *string;
// impliment
@synthesize string;
この場合、生成されるstring変数はstrongとなります。もちろんエラーも警告も出ません。
環境ごとのデフォルト属性
実際に、Xcode 4.2, Xcode 4.3で属性指定なしのプロパティがどのように扱われているか、property_getAttributesを使って調べてみました。確認用に用意したコードは以下。showPropertyAttribute内でログを出力し、結果を確認します。
@interface AttributeTest : NSObject
@property (nonatomic) NSString *noattributeStr;
@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, weak) NSString *weakStr;
@property (nonatomic, unsafe_unretained) NSString *unsafeunretainedStr;
@property (nonatomic, retain) NSString *retainStr;
@property (nonatomic, assign) NSString *assignStr;
+ (void)showPropertyAttribute;
@end
@implementation AttributeTest
@synthesize noattributeStr, strongStr, weakStr, unsafeunretainedStr, retainStr, assignStr;
+ (void)showPropertyAttribute {
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s\n", property_getAttributes(property));
}
}
@end
結果のフォーマットは、”Tエンコードタイプ, … ,Vプロパティ名”となり、カンマ区切りの部分にプロパティの属性が表示されます。例えば、readonlyは”R”, retainは”&”, weakは”W”といった具合です(詳細は、Declared Properties : Objective-C Runtime programming Guideを参照のこと)。
Xcode 4.3 + ARC
まず、Xcode 4.3でARCを利用した場合の結果です。
結果:
T@"NSString",&,N,VnoattributeStr
T@"NSString",&,N,VstrongStr
T@"NSString",W,N,VweakStr
T@"NSString",N,VunsafeunretainedStr
T@"NSString",&,N,VretainStr
T@"NSString",N,VassignStr
この結果より、noattributeStrは”&”が表示されているためretainされていることがわかります。つまり、strongプロパティとして扱われていることになります。すぐ下にあるstrongStrと属性が全く同じになっていることが確認できます。
Xcode 4.3 + 非ARC
次に、Xcode 4.3で非ARCの場合です(以下、noattributeStrの結果のみ表示します)。
結果:
T@"NSString",N,VnoattributeStr
“&”が入っていませんので、これはassignプロパティであることを意味しています。従来どおりの仕様です。なお、非ARC環境で属性指定なしのプロパティを宣言すると、これまでどおりの警告が出ます。
No ‘assign’, ‘retain’, or ‘copy’ attribute is specified – ‘assign’ is assumed
Xcode 4.2 + ARC
続いて、Xcode 4.2でARCを用いた場合です。この場合、何もしないとエラーになってしまい確認ができませんので、まずはインスタンス変数を定義します。
@interface AttributeTest : NSObject {
NSString *noattributeStr;
}
@property (nonatomic) NSString *noattributeStr;
@end
お察しのとおり、これでもまだエラーとなります。エラーの内容を見ると、
Existing ivar ‘noattributeStr’ for unsafe_unretaind property ‘noattributeStr’ must be __unsafe_unretained
となっています。これですでに、noattributeStrはunsafe_unretainedプロパティだと分かりました。変数宣言を__unsafe_unretainedに修正し、property_getAttributesを実行します。
@interface AttributeTest : NSObject {
__unsafe_unretained NSString *noattributeStr;
}
@property (nonatomic) NSString *noattributeStr;
...
@end
結果:
T@"NSString",N,VnoattributeStr
インスタンス変数を__unsafe_unretainedとしているので当たり前の結果ですが、やはりretainされていません。なお、ここでも、Xcode 4.3+非ARCのときと同様の警告が出ます。
Xcode 4.2 + 非ARC
最後に、Xcode 4.2 + 非ARCの場合です。これは、Xcode 4.3 + 非ARCと全く同じですのでここでは省略します。
XcodeのバージョンとARCの有無による違いを見てきましたが、まとめると、“Xcode 4.3 + ARC”のときのみデフォルト属性がstrongとなり、その他はすべてこれまでどおり、デフォルト属性はassign(またはunsafe_unretained)です。Xcode 4.3でARCを利用する場合、この変更は頭に入れておいた方がよいでしょう。
ARCへの自動変換時はご注意を
Xcodeの各バージョンとARC対応の有無によるデフォルト属性の違いは上にまとめたとおりです。最後に、これに伴うXcodeの変更で最も大きい(かつ、場合によってはかなり危険)と思ったことです。
Xcode 4.2以降では、非ARCのコードをARCに対応させるための自動変換機能を利用できます。これは、Xcodeのメニューから、”Edit->Refactor->Convert to Objective-C ARC…”を選択することで実行可能です。
この自動変換によるプロパティ属性の置き換え方法が、大きく変更されています。Xcode 4.2では、retainプロパティはstrongプロパティに置き換わりました。つまり、
@property (nonatomic, retain) NSString *string;
は、
@property (nonatomic, strong) NSString *string;
に置き換わります。
これが、Xcode 4.3では、属性指定なしの宣言に置き換わるようになりました。つまり、
@property (nonatomic, retain) NSString *string;
は、
@property (nonatomic) NSString *string;
に置き換わります。
Xcode 4.3ではデフォルト属性がstrongですので、この変換は、Xcode4.2での変換と全く同じ意味を持ちます。したがって、変換自体は正しく行われていることになります。何が問題かと言うと、Xcode 4.3で自動変換したコードをXcode 4.2でビルドしようと思ってもエラーになってビルドできないということです。
みなさま、はまらないようにご注意ください。せめて自動変換のときくらいはstrongつけてほしい気もしますね。。
なお、ここで使っているXcode 4.3はMac App Storeよりダウンロードした公式バージョンです(Build 4D502)。iOSSDK 5.1がついているベータバージョンではありません。
質問、間違いの指摘などはツイッターでお願いします。@natsun_happy