*本文摘錄自[饒志臻的博客] 屬性聲明在@implementation里與extension里的區別
當你新建一個類的時候,Xcode會自動給你寫上以下代碼。
#import <Foundation/Foundation.h>
@interface Car : NSObject
@end
#import "Car.h"
@implementation Car
@end
Objective-C編譯器指令是以@打頭,它通常用來描述文件中的內容。.h文件中@interface指令用來標識文件的接口代碼的起始位置,而@end指令標示該段的結束位置。在.m文件中,@implementation指令用來標識實現的起始位置,@end標識結束位置
@interface用于定義類的公共接口,通常,接口被稱為API(application programming interface)而真正使對象能夠運行的代碼,位于@implementation中。
當我們要給一個Car類聲明一個發動機屬性的時候,如果對外公開,則代碼為
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property (nonatomic, strong) Engine *engine;
@end
如果不對外公開,則在.m里的代碼為
@interface Car ()
@property (nonatomic, strong) Engine *engine;
@end
@interface Car ()看起來和.h里的@interface Car : NSObject很像,其實@interface Car ()是一個特殊的匿名 Category,即擴展(extension)。
類別(Category)是一種為現有的類添加新方法的方式。
利用Objective-C的動態運行時分配機制,Category提供了一種比繼承(inheritance)更為簡潔的方法來對class進行擴展,無需創建對象類的子類就能為現有的類添加新方法,可以為任何已經存在的class添加方法,包括那些沒有源代碼的類(如某些框架類),申明的方法不需要在@implementation里實現。
但Category無法向類中添加新的實例變量,類別沒有空間容納實例變量。(也有一些技術可以克服類別無法增加新實例變量的局限。例如,使用全局字典來存儲對象與你想要關聯的額外變量之間的映射。)
而extension可以添加新的實例變量
@property是以@開頭,所以它也是Objective—C編譯器指令,用于聲明屬性,并為它自動創建一個帶下劃線的實例變量,及實例變量的setter和getter方法。
而直接聲明實例變量的寫法,即
@interface Car () {
Engine *_engine;
}
@end
和
@implementation Car {
Engine *_engine;
}
@end
從語法上說它們等效。
如果只是聲明一個@implementation里需要用到的全局變量,自然是放在@implementation里聲明,但如果是聲明一個不對外公開的屬性呢,比如engine,既然是屬性,好像是需要在extension里聲明,但如果我使用_engine來訪問成員變量,則并不會用到它的setter和getter方法。如果我使用點語法來訪問成員變量呢,點語法其實是調用了getter方法[Car engine],而這種默認的隱藏在代碼中多了,會影響代碼的閱讀和維護。
但engine明明是Car的一個屬性,卻聲明在@implementation里作為一個變量,其實實例變量也是這個對象的構成元素,和屬性除了名字并沒有涵義上的區別。所以在@implementation里聲明的變量也是這個對象的屬性,只是為了區分兩種聲明方式的叫法不同而已。
另一個用@property和@implementation聲明屬性的區別就是,@property可以給屬性添加屬性標識符,即assign,copy,weak,strong,nonatomic,但其實大部分的屬性標識符都有對應的所有權修飾符,assign對應__unsafe_unretained,copy對應__strong修飾符(但copy賦值的是被復制的對象),strong對應__strong,weak對應__weak。id和對象類型在沒有明確指定所有權修飾符時,默認為__strong修飾符,而@property聲明屬性的默認屬性標識符為readwrite,assign, atomic。atomic的確沒有對應的所有權修飾符,id和對象類型自然是沒有原子性的,在iOS開發,除非特殊需要,我們都會給屬性標識符添加nonatomic,所以在這點上,@property和@implementation聲明屬性倒是沒什么區別。
在@interface里使用@property聲明屬性的時候,如果屬性類型為NSString,它的屬性標識符是需要添加copy的,原因就在與,設置方法的新值有可能指向一個NSMutableString類的實例,那么設置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改,那在@implementation里聲明一個NSString會不會有這個顧慮呢?copy不是簡單的賦值,對應的__strong并不會通過copyWithZone:方法復制賦值源所生產的對象,所以@implementation里聲明的NSString沒有copy作用的修飾符,但在@implementation里聲明即這個屬性是不對外公開的,即不會被其它對象直接修改這個屬性,那你既然聲明了一個NSString類型的屬性,自然用意就是使用一個不可變的字符串,自然自己不會去修改它,如果你無意中修改了它,我只能說這是你的代碼寫錯了。所以不需要使用copy作用的修飾符。同理,在extension里使用@property聲明NSString,也是不需要copy屬性標識符的。所以NSString在@implementation里聲明并不會有所影響。
博客:xuyafei.cn
簡書:jianshu.com/users/2555924d8c6e
微博:weibo.com/xuyafei86
Github:github.com/xiaofei86
總結
在@implementation里聲明并沒有缺點,但在extension里使用@property聲明屬性,會有不帶來價值的隱藏代碼,以及_engine比self.engine更簡短易讀,最后還有可以避免在init和dealloc中會去調用self.engine。