當(dāng)你新建一個(gè)類的時(shí)候, Xcode 會(huì)自動(dòng)給你寫上以下代碼嗤攻。
#import <Foundation/Foundation.h>
@interface Car : NSObject
@end
#import "Car.h"
@implementation Car
@end
Objective-C 編譯器指令是以 @ 打頭集索,它通常用來描述文件中的內(nèi)容。.h 文件中 @interface 指令用來標(biāo)識(shí)文件的接口代碼的起始位置雌桑,而 @end 指令標(biāo)示該段的結(jié)束位置喇喉。在 .m 文件中,@implementation 指令用來標(biāo)識(shí)實(shí)現(xiàn)的起始位置校坑,@end 標(biāo)識(shí)結(jié)束位置
@interface 用于定義類的公共接口拣技,通常,接口被稱為 API(application programming interface)而真正使對(duì)象能夠運(yùn)行的代碼耍目,位于 @implementation 中过咬。
當(dāng)我們要給一個(gè) Car 類聲明一個(gè)發(fā)動(dòng)機(jī)屬性的時(shí)候,如果對(duì)外公開制妄,則代碼為
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property (nonatomic, strong) Engine *engine;
@end
如果不對(duì)外公開掸绞,則在 .m 里的代碼為
@interface Car ()
@property (nonatomic, strong) Engine *engine;
@end
@interface Car () 看起來和 .h 里的 @interface Car : NSObject 很像,其實(shí) @interface Car () 是一個(gè)特殊的匿名 Category耕捞,即擴(kuò)展(extension)衔掸。
類別(Category)是一種為現(xiàn)有的類添加新方法的方式。
利用 Objective-C 的動(dòng)態(tài)運(yùn)行時(shí)分配機(jī)制俺抽,Category 提供了一種比繼承(inheritance)更為簡(jiǎn)潔的方法來對(duì) class 進(jìn)行擴(kuò)展敞映,無需創(chuàng)建對(duì)象類的子類就能為現(xiàn)有的類添加新方法,可以為任何已經(jīng)存在的 class 添加方法磷斧,包括那些沒有源代碼的類(如某些框架類)振愿,申明的方法不需要在 @implementation 里實(shí)現(xiàn)捷犹。
但 Category 無法向類中添加新的實(shí)例變量,類別沒有空間容納實(shí)例變量冕末。(也有一些技術(shù)可以克服類別無法增加新實(shí)例變量的局限萍歉。例如,使用全局字典來存儲(chǔ)對(duì)象與你想要關(guān)聯(lián)的額外變量之間的映射档桃。)
而 extension 可以添加新的實(shí)例變量
@property 是以 @ 開頭枪孩,所以它也是 Objective—C 編譯器指令,用于聲明屬性藻肄,并為它自動(dòng)創(chuàng)建一個(gè)帶下劃線的實(shí)例變量蔑舞,及實(shí)例變量的 setter 和 getter 方法。
而直接聲明實(shí)例變量的寫法嘹屯,即
@interface Car () {
Engine *_engine;
}
@end
和
@implementation Car {
Engine *_engine;
}
@end
從語法上說它們等效攻询。
如果只是聲明一個(gè) @implementation 里需要用到的全局變量,自然是放在 @implementation 里聲明州弟,但如果是聲明一個(gè)不對(duì)外公開的屬性呢蜕窿,比如 engine,既然是屬性呆馁,好像是需要在 extension 里聲明桐经,但如果我使用 _engine 來訪問成員變量,則并不會(huì)用到它的 setter 和 getter 方法浙滤。如果我使用點(diǎn)語法來訪問成員變量呢阴挣,點(diǎn)語法其實(shí)是調(diào)用了 getter 方法 [Car engine],而這種默認(rèn)的隱藏在代碼中多了纺腊,會(huì)影響代碼的閱讀和維護(hù)畔咧。
但 engine 明明是 Car 的一個(gè)屬性,卻聲明在 @implementation 里作為一個(gè)變量揖膜,其實(shí)實(shí)例變量也是這個(gè)對(duì)象的構(gòu)成元素誓沸,和屬性除了名字并沒有涵義上的區(qū)別。所以在 @implementation 里聲明的變量也是這個(gè)對(duì)象的屬性壹粟,只是為了區(qū)分兩種聲明方式的叫法不同而已拜隧。
另一個(gè)用 @property 和 @implementation 聲明屬性的區(qū)別就是,@property 可以給屬性添加屬性標(biāo)識(shí)符趁仙,即 assign洪添,copy,weak雀费,strong干奢,nonatomic,但其實(shí)大部分的屬性標(biāo)識(shí)符都有對(duì)應(yīng)的所有權(quán)修飾符盏袄,assign 對(duì)應(yīng) __unsafe_unretained忿峻,copy 對(duì)應(yīng) __strong 修飾符(但 copy 賦值的是被復(fù)制的對(duì)象)薄啥,strong 對(duì)應(yīng) __strong,weak 對(duì)應(yīng) __weak逛尚。id 和對(duì)象類型在沒有明確指定所有權(quán)修飾符時(shí)垄惧,默認(rèn)為 __strong 修飾符,而 @property 聲明屬性的默認(rèn)屬性標(biāo)識(shí)符為 readwrite黑低,assign, atomic赘艳。atomic 的確沒有對(duì)應(yīng)的所有權(quán)修飾符酌毡,id 和對(duì)象類型自然是沒有原子性的克握,在 iOS 開發(fā),除非特殊需要枷踏,我們都會(huì)給屬性標(biāo)識(shí)符添加 nonatomic菩暗,所以在這點(diǎn)上,@property 和 @implementation 聲明屬性倒是沒什么區(qū)別旭蠕。
在 @interface 里使用 @property 聲明屬性的時(shí)候停团,如果屬性類型為 NSString ,它的屬性標(biāo)識(shí)符是需要添加 copy 的掏熬,原因就在與佑稠,設(shè)置方法的新值有可能指向一個(gè) NSMutableString 類的實(shí)例,那么設(shè)置完屬性之后旗芬,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改舌胶,那在 @implementation 里聲明一個(gè) NSString 會(huì)不會(huì)有這個(gè)顧慮呢?copy 不是簡(jiǎn)單的賦值疮丛,對(duì)應(yīng)的 __strong 并不會(huì)通過 copyWithZone: 方法復(fù)制賦值源所生產(chǎn)的對(duì)象幔嫂,所以 @implementation 里聲明的 NSString 沒有 copy 作用的修飾符,但在 @implementation 里聲明即這個(gè)屬性是不對(duì)外公開的誊薄,即不會(huì)被其它對(duì)象直接修改這個(gè)屬性履恩,那你既然聲明了一個(gè) NSString 類型的屬性,自然用意就是使用一個(gè)不可變的字符串呢蔫,自然自己不會(huì)去修改它切心,如果你無意中修改了它,我只能說這是你的代碼寫錯(cuò)了片吊。所以不需要使用 copy 作用的修飾符昙衅。同理,在 extension 里使用 @property 聲明 NSString定鸟,也是不需要 copy 屬性標(biāo)識(shí)符的而涉。所以 NSString 在 @implementation 里聲明并不會(huì)有所影響。
總結(jié)
在 @implementation 里聲明并沒有缺點(diǎn)联予,但在 extension 里使用 @property 聲明屬性啼县,會(huì)有不帶來價(jià)值的隱藏代碼材原,以及 _engine 比 self.engine 更簡(jiǎn)短易讀,最后還有可以避免在 init 和 dealloc 中會(huì)去調(diào)用 self.engine季眷。(這是一件比較危險(xiǎn)的事情余蟹。)
參考資料
《iOS與OS X多線程和內(nèi)存管理》
《Objective-C基礎(chǔ)教程(第二版)》