1. 程式人生 > >@property @synthesize @dynamic

@property @synthesize @dynamic

@property 的本質是什麼?ivar、getter、setter 是如何生成並新增到這個類中的

@property 的本質是什麼?

@property = ivar + getter + setter;

下面解釋下:

“屬性” (property)有兩大概念:ivar(例項變數)、存取方法(access method = getter + setter)。

“屬性” (property)作為 Objective-C 的一項特性,主要的作用就在於封裝物件中的資料。 Objective-C 物件通常會把其所需要的資料儲存為各種例項變數。例項變數一般通過“存取方法”(access method)來訪問。其中,“獲取方法” (getter)用於讀取變數值,而“設定方法” (setter)用於寫入變數值。這個概念已經定型,並且經由“屬性”這一特性而成為Objective-C 2.0的一部分。 而在正規的 Objective-C 編碼風格中,存取方法有著嚴格的命名規範。 正因為有了這種嚴格的命名規範,所以 Objective-C 這門語言才能根據名稱自動創建出存取方法。其實也可以把屬性當做一種關鍵字,其表示:

編譯器會自動寫出一套存取方法,用以訪問給定型別中具有給定名稱的變數。 所以你也可以這麼說:

@property = getter + setter;

例如下面這個類:

@interface Person : NSObject 
@property NSString *firstName; 
@property NSString *lastName; 
@end 

上述程式碼寫出來的類與下面這種寫法等效:

@interface Person : NSObject 
- (NSString *)firstName; 
- (void)setFirstName:(NSString
*)firstName; - (NSString *)lastName; - (void)setLastName:(NSString *)lastName; @end

ivar、getter、setter 是如何生成並新增到這個類中的?

“自動合成”( autosynthesis)

完成屬性定義後,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”( autosynthesis)。需要強調的是,這個過程由編譯 器在編譯期執行,所以編輯器裡看不到這些“合成方法”(synthesized method)的原始碼。除了生成方法程式碼 getter、setter 之外,編譯器還要自動向類中新增適當型別的例項變數,並且在屬性名前面加下劃線,以此作為例項變數的名字。在前例中,會生成兩個例項變數,其名稱分別為 _firstName與_lastName。也可以在類的實現程式碼裡通過 @synthesize語法來指定例項變數的名字.

@implementation Person 
@synthesize firstName = _myFirstName; 
@synthesize lastName = myLastName; 
@end 

@protocol 和 category 中如何使用 @property

在protocol中使用property只會生成setter和getter方法宣告,我們使用屬性的目的,是希望遵守我協議的物件能實現該屬性

category 使用 @property 也是隻會生成setter和getter方法的宣告,如果我們真的需要給category增加屬性的實現,需要藉助於執行時的兩個函式:

objc_setAssociatedObject
objc_getAssociatedObject

@property中有哪些屬性關鍵字?/ @property 後面可以有哪些修飾符?

屬性可以擁有的特質分為四類:

原子性—nonatomic特質

在預設情況下,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備nonatomic特質,則不使用同步鎖。請注意,儘管沒有名為“atomic”的特質(如果某屬性不具備nonatomic特質,那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質中寫明這一點,編譯器不會報錯。若是自己定義存取方法,那麼就應該遵從與屬性特質相符的原子性。

讀/寫許可權—readwrite(讀寫)、readooly (只讀)

記憶體管理語義—assign、strong、 weak、unsafe_unretained、copy
方法名—-getter=<name>setter=<name>

getter=<name>的樣式:

@property (nonatomic, getter=isOn) BOOL on;
setter=<name>這種不常用,也不推薦使用。故不在這裡給出寫法。)

不常用的:nonnull,null_resettable,nullable

@synthesize和@dynamic分別有什麼作用?

@property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那麼預設的就是@syntheszie var = _var;

@synthesize的語義是如果你沒有手動實現setter方法和getter方法,那麼編譯器會自動為你加上這兩個方法。

@dynamic告訴編譯器:屬性的setter與getter方法由使用者自己實現,不自動生成。(當然對於readonly的屬性只需提供getter即可)。假如一個屬性被宣告為@dynamic var,然後你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程式執行到instance.var = someVar,由於缺setter方法會導致程式崩潰;或者當執行到 someVar = var時,由於缺getter方法同樣會導致崩潰。編譯時沒問題,執行時才執行相應的方法,這就是所謂的動態繫結。

ARC下,不顯式指定任何屬性關鍵字時,預設的關鍵字都有哪些?

對應基本資料型別預設關鍵字是:

atomic,readwrite,assign

對於普通的OC物件:

atomic,readwrite,strong

@synthesize合成例項變數的規則是什麼?假如property名為foo,存在一個名為_foo的例項變數,那麼還會自動合成新變數麼?

在回答之前先說明下一個概念:

例項變數 = 成員變數 = ivar

這些說法,筆者下文中,可能都會用到,指的是一個東西。

正如 Apple官方文件 You Can Customize Synthesized Instance Variable Names 所說:

這裡寫圖片描述

如果使用了屬性的話,那麼編譯器就會自動編寫訪問屬性所需的方法,此過程叫做“自動合成”( auto synthesis)。需要強調的是,這個過程由編譯器在編譯期執行,所以編輯器裡看不到這些“合成方法” (synthesized method)的原始碼。除了生成方法程式碼之外,編譯器還要自動向類中新增適當型別的例項變數,並且在屬性名前面加下劃線,以此作為例項變數的名字。

@interface CYLPerson : NSObject 
@property NSString *firstName; 
@property NSString *lastName; 
@end

在上例中,會生成兩個例項變數,其名稱分別為 _firstName與_lastName。也可以在類的實現程式碼裡通過@synthesize語法來指定例項變數的名字:

@implementation CYLPerson 
@synthesize firstName = _myFirstName; 
@synthesize lastName = _myLastName; 
@end 

上述語法會將生成的例項變數命名為_myFirstName與_myLastName,而不再使用預設的名字。一般情況下無須修改預設的例項變數名,但是如果你不喜歡以下劃線來命名例項變數,那麼可以用這個辦法將其改為自己想要的名字。筆者還是推薦使用預設的命名方案,因為如果所有人都堅持這套方案,那麼寫出來的程式碼大家都能看得懂。

總結下@synthesize合成例項變數的規則,有以下幾點:

如果指定了成員變數的名稱,會生成一個指定的名稱的成員變數,

如果這個成員已經存在了就不再生成了.

如果是 @synthesize foo; 還會生成一個名稱為foo的成員變數,也就是說:

如果沒有指定成員變數的名稱會自動生成一個屬性同名的成員變數,
如果是 @synthesize foo = _foo; 就不會生成成員變量了.

假如property名為foo,存在一個名為_foo的例項變數,那麼還會自動合成新變數麼? 不會。如下圖:
這裡寫圖片描述

在有了自動合成屬性例項變數之後,@synthesize還有哪些使用場景?

回答這個問題前,我們要搞清楚一個問題,什麼情況下不會autosynthesis(自動合成)?

同時重寫了setter和getter時
重寫了只讀屬性的getter時
使用了@dynamic時
在 @protocol 中定義的所有屬性
在 category 中定義的所有屬性
過載的屬性

當你在子類中過載了父類中的屬性,你必須 使用@synthesize來手動合成ivar。

除了後三條,對其他幾個我們可以總結出一個規律:當你想手動管理@property的所有內容時,你就會嘗試通過實現@property的所有“存取方法”(the accessor methods)或者使用@dynamic來達到這個目的,這時編譯器就會認為你打算手動管理@property,於是編譯器就禁用了autosynthesis(自動合成)。

因為有了autosynthesis(自動合成),大部分開發者已經習慣不去手動定義ivar,而是依賴於autosynthesis(自動合成),但是一旦你需要使用ivar,而autosynthesis(自動合成)又失效了,如果不去手動定義ivar,那麼你就得藉助@synthesize來手動合成ivar。

其實,@synthesize語法還有一個應用場景,但是不太建議大家使用:

可以在類的實現程式碼裡通過@synthesize語法來指定例項變數的名字:

@implementation CYLPerson 
@synthesize firstName = _myFirstName; 
@synthesize lastName = _myLastName; 
@end 

上述語法會將生成的例項變數命名為_myFirstName與_myLastName,而不再使用預設的名字。一般情況下無須修改預設的例項變數名,但是如果你不喜歡以下劃線來命名例項變數,那麼可以用這個辦法將其改為自己想要的名字。筆者還是推薦使用預設的命名方案,因為如果所有人都堅持這套方案,那麼寫出來的程式碼大家都能看得懂。

舉例說明應用場景:

//
// .m檔案
// http://weibo.com/luohanchenyilong/ (微博@iOS程式犭袁)
// https://github.com/ChenYilong
// 開啟第14行和第17行中任意一行,就可編譯成功

@import Foundation;

@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end

@implementation CYLObject {
//    NSString *_title;
}

//@synthesize title = _title;

- (instancetype)init
{
    self = [super init];
    if (self) {
        _title = @"微博@iOS程式犭袁";
    }
    return self;
}

- (NSString *)title {
    return _title;
}

- (void)setTitle:(NSString *)title {
    _title = [title copy];
}

@end

結果編譯器報錯:

這裡寫圖片描述

當你同時重寫了setter和getter時,系統就不會生成ivar(例項變數/成員變數)。這時候有兩種選擇:

要麼如第14行:手動建立ivar
要麼如第17行:使用@synthesize foo = _foo; ,關聯@property與ivar。