iOS 深入理解“屬性”及其關鍵字

1.weak 與 assign 不同

什麼情況使用 weak 關鍵字?

  1. 在ARC中,在有可能出現循環引用的時候,往往要通過讓其中一端使用weak來解決,比如:delegate代理屬性

  2. 自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用weak,自定義IBOutlet控件屬性一般也使用weak;當然,也可以使用strong。在下文也有論述:《IBOutlet連出來的視圖屬性爲什麼可以被設置成weak?》

不同點:

  1. weak 此特質表明該屬性定義了一種“非擁有關係” (nonowning relationship)。爲這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同assign類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。 而assign 的“設置方法”只會執行鍼對“純量類型” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。

  2. assigin 可以用非OC對象,而weak必須用於OC對象

ps:weak屬性不需要在dealloc中置nil


2.copy 關鍵字

用途:

  1. NSString、NSArray、NSDictionary 等等經常使用copy關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;他們之間可能進行賦值操作,爲確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
如:@property (copy) NSMutableArray *array;
兩個問題:1、添加,刪除,修改數組內的元素的時候,程序會因爲找不到對應的方法而崩潰.因爲copy就是複製一個不可變NSArray的對象;2、使用了atomic屬性會嚴重影響性能 ;

   2. block也經常使用copy關鍵字,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks

block 使用 copy 是從 MRC 遺留下來的“傳統”,在 MRC 中,方法內部的 block 是在棧區的,使用 copy 可以把它放到堆區.在ARC中寫不寫都行:對於 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進行了 copy 操作。如果不寫 copy ,該類的調用者有可能會忘記或者根本不知道“編譯器會自動對 block 進行了 copy 操作”,他們有可能會在調用之前自行拷貝屬性值。這種操作多餘而低效。

用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,爲什麼?如果改用strong關鍵字,可能造成什麼問題?

  1. 因爲父類指針可以指向子類對象,使用copy的目的是爲了讓本對象的屬性不受外界影響,使用copy無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
  2. 如果我們使用是strong,那麼這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那麼會影響該屬性.

copy此特質所表達的所屬關係與strong類似。然而設置方法並不保留新值,而是將其“拷貝” (copy)。 當屬性類型爲NSString時,經常用此特質來保護其封裝性,因爲傳遞給設置方法的新值有可能指向一個NSMutableString類的實例。這個類是NSString的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那麼設置完屬性之後,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” (mutable),就應該在設置新屬性值時拷貝一份,以防止之後“值”被可變。

爲了理解這種做法,首先要知道,對非集合類對象的copy操作:

在非集合類對象中:對immutable對象進行copy操作,是指針複製,mutableCopy操作時內容複製;對mutable對象進行copy和mutableCopy都是內容複製。用代碼簡單表示如下:

  • [immutableObject copy] // 淺複製
  • [immutableObject mutableCopy] //深複製
  • [mutableObject copy] //深複製
  • [mutableObject mutableCopy] //深複製

比如以下代碼:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy]; 

查看內存,會發現 string、stringCopy 內存地址都不一樣,說明此時都是做內容拷貝、深拷貝。即使你進行如下操作:

[string appendString:@"origion!"]

stringCopy的值也不會因此改變,但是如果不使用copy,stringCopy的值就會被改變。 集合類對象以此類推。 所以,

用@property聲明 NSString、NSArray、NSDictionary 經常使用copy關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,爲確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。

參考鏈接:iOS 集合的深複製與淺複製



3.讓自己的類用 copy 修飾符,重寫帶 copy 關鍵字的 setter

若想令自己所寫的對象具有拷貝功能,則需實現NSCopying協議。如果自定義的對象分爲可變版本與不可變版本,那麼就要同時實現NSCopyiog與NSMutableCopying協議。

具體步驟:

  1. 需聲明該類遵從NSCopying協議
  2. 實現NSCopying協議。該協議只有一個方法:

    - (id)copyWithZone: (NSZone*) zone

    注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法,其實真正需要實現的卻是“copyWithZone”方法。

以第一題的代碼爲例:

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 修改完的代碼

typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

@interface CYLUser : NSObject<NSCopying>

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSUInteger age;
@property (nonatomic, assign, readonly) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

然後實現協議中規定的方法:

- (id)copyWithZone:(NSZone *)zone {
    CYLUser *copy = [[[self class] allocWithZone:zone] 
                     initWithName:_name
                                  age:_age
                                  sex:_sex];
    return copy;
}

但在實際的項目中,不可能這麼簡單,遇到更復雜一點,比如類對象中的數據結構可能並未在初始化方法中設置好,需要另行設置。舉個例子,假如CYLUser中含有一個數組,與其他CYLUser對象建立或解除朋友關係的那些方法都需要操作這個數組。那麼在這種情況下,你得把這個包含朋友對象的數組也一併拷貝過來。下面列出了實現此功能所需的全部代碼:

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 以第一題《風格糾錯題》裏的代碼爲例

typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

@interface CYLUser : NSObject<NSCopying>

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSUInteger age;
@property (nonatomic, assign, readonly) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
- (void)addFriend:(CYLUser *)user;
- (void)removeFriend:(CYLUser *)user;

@end

// .m文件

// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 

@implementation CYLUser {
    NSMutableSet *_friends;
}

- (void)setName:(NSString *)name {
    _name = [name copy];
}

- (instancetype)initWithName:(NSString *)name 
                             age:(NSUInteger)age 
                             sex:(CYLSex)sex {
     if(self = [super init]) {
        _name = [name copy];
        _age = age;
        _sex = sex;
        _friends = [[NSMutableSet alloc] init];
     }
     return self;
}

- (void)addFriend:(CYLUser *)user {
    [_friends addObject:user];
}

- (void)removeFriend:(CYLUser *)user {
    [_friends removeObject:person];
}

- (id)copyWithZone:(NSZone *)zone {
    
CYLUser *copy = [[[self class] allocWithZone:zone] 
                     initWithName:_name
                                  age:_age
                                  sex:_sex];
    copy->_friends = [[NSMutableSet alloc] initWithSet:_friends 
                                             copyItems:YES];
    return copy;
}@end

4.@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語法來指定實例變量的名字.(關於@synthesize看第9點)

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

我爲了搞清屬性是怎麼實現的,曾經反編譯過相關的代碼,他大致生成了五個東西

  1. OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset),這個偏移量是“硬編碼” (hardcode),表示該變量距離存放對象的內存區域的起始地址有多遠。
  2. setter與getter方法對應的實現函數
  3. ivar_list :成員變量列表
  4. method_list :方法列表
  5. prop_list :屬性列表

也就是說我們每次在增加一個屬性,系統都會在ivar_list中添加一個成員變量的描述,在method_list中增加setter與getter方法的描述,在屬性列表中增加一個屬性的描述,然後計算該屬性在對象中的偏移量,然後給出setter與getter方法對應的實現,在setter方法中從偏移量的位置開始賦值,在getter方法中從偏移量開始取值,爲了能夠讀取正確字節數,系統對象偏移量的指針類型進行了類型強轉.

5.@protocol 和 category 中使用 @property

  1. 在protocol中使用property只會生成setter和getter方法聲明,我們使用屬性的目的,是希望遵守我協議的對象能實現該屬性
  2. category 使用 @property 也是隻會生成setter和getter方法的聲明,如果我們真的需要給category增加屬性的實現,需要藉助於運行時的兩個函數:

    1. objc_setAssociatedObject
    2. objc_getAssociatedObject

6.runtime 如何實現 weak 屬性

要實現weak屬性,首先要搞清楚weak屬性的特點:

weak 此特質表明該屬性定義了一種“非擁有關係” (nonowning relationship)。爲這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同assign類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。

那麼runtime如何實現weak變量的自動置nil?

runtime 對註冊的類, 會進行佈局,對於 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作爲 key,當此對象的引用計數爲0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那麼就會以a爲鍵, 在這個 weak 表中搜索,找到所有以a爲鍵的 weak 對象,從而設置爲 nil。

(注:在下文的《使用runtime Associate方法關聯的對象,需要在主對象dealloc的時候釋放麼?》裏給出的“對象的內存銷燬時間表”也提到__weak引用的解除時間。)

我們可以設計一個函數(僞代碼)來表示上述機制:

objc_storeWeak(&a, b)函數:

objc_storeWeak函數把第二個參數--賦值對象(b)的內存地址作爲鍵值key,將第一個參數--weak修飾的屬性變量(a)的內存地址(&a)作爲value,註冊到 weak 表中。如果第二個參數(b)爲0(nil),那麼把變量(a)的內存地址(&a)從weak表中刪除,

你可以把objc_storeWeak(&a, b)理解爲:objc_storeWeak(value, key),並且當key變nil,將value置nil。

在b非nil時,a和b指向同一個內存地址,在b變nil時,a變nil。此時向a發送消息不會崩潰:在Objective-C中向nil發送消息是安全的。

而如果a是由assign修飾的,則: 在b非nil時,a和b指向同一個內存地址,在b變nil時,a還是指向該內存地址,變野指針。此時向a發送消息極易崩潰。

下面我們將基於objc_storeWeak(&a, b)函數,使用僞代碼模擬“runtime如何實現weak屬性”:

// 使用僞代碼模擬:runtime如何實現weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

 id obj1;
 objc_initWeak(&obj1, obj);
/*obj引用計數變爲0,變量作用域結束*/
 objc_destroyWeak(&obj1);

下面對用到的兩個方法objc_initWeakobjc_destroyWeak做下解釋:

總體說來,作用是: 通過objc_initWeak函數初始化“附有weak修飾符的變量(obj1)”,在變量作用域結束時通過objc_destoryWeak函數釋放該變量(obj1)。

下面分別介紹下方法的內部實現:

objc_initWeak函數的實現是這樣的:在將“附有weak修飾符的變量(obj1)”初始化爲0(nil)後,會將“賦值對象”(obj)作爲參數,調用objc_storeWeak函數。

obj1 = 0obj_storeWeak(&obj1, obj);

也就是說:

weak 修飾的指針默認值是 nil (在Objective-C中向nil發送消息是安全的)

然後obj_destroyWeak函數將0(nil)作爲參數,調用objc_storeWeak函數。

objc_storeWeak(&obj1, 0);

前面的源代碼與下列源代碼相同。

// 使用僞代碼模擬:runtime如何實現weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用計數變爲0,被置nil ... */
objc_storeWeak(&obj1, 0);

objc_storeWeak函數把第二個參數--賦值對象(obj)的內存地址作爲鍵值,將第一個參數--weak修飾的屬性變量(obj1)的內存地址註冊到 weak 表中。如果第二個參數(obj)爲0(nil),那麼把變量(obj1)的地址從weak表中刪除,在後面的相關一題會詳解。

使用僞代碼是爲了方便理解,下面我們“真槍實彈”地實現下:

如何讓不使用weak修飾的@property,擁有weak的效果。

我們從setter方法入手:

- (void)setObject:(NSObject *)object
{
    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    [object cyl_runAtDealloc:^{
        _object = nil;
    }];
}

也就是有兩個步驟:

  1. 在setter方法中做如下設置:

    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    
  2. 在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。做到這點,同樣要藉助runtime:

    //要銷燬的目標對象
    id objectToBeDeallocated;
    //可以理解爲一個“事件”:當上面的目標對象銷燬時,同時要發生的“事件”。
    id objectWeWantToBeReleasedWhenThatHappens;
    objc_setAssociatedObject(objectToBeDeallocted,
                         someUniqueKey,
                         objectWeWantToBeReleasedWhenThatHappens,
                         OBJC_ASSOCIATION_RETAIN);

知道了思路,我們就開始實現cyl_runAtDealloc方法,實現過程分兩部分:

第一部分:創建一個類,可以理解爲一個“事件”:當目標對象銷燬時,同時要發生的“事件”。藉助block執行“事件”。

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 這個類,可以理解爲一個“事件”:當目標對象銷燬時,同時要發生的“事件”。藉助block執行“事件”。

typedef void (^voidBlock)(void);

@interface CYLBlockExecutor : NSObject 

- (id)initWithBlock:(voidBlock)block;

@end


// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 這個類,可以理解爲一個“事件”:當目標對象銷燬時,同時要發生的“事件”。藉助block執行“事件”。

#import "CYLBlockExecutor.h"

@interface CYLBlockExecutor() {
    voidBlock _block;
}
@implementation CYLBlockExecutor

- (id)initWithBlock:(voidBlock)aBlock
{
    self = [super init];

    if (self) {
        _block = [aBlock copy];
    }

    return self;
}

- (void)dealloc
{
    _block ? _block() : nil;
}

@end

第二部分:核心代碼:利用runtime實現cyl_runAtDealloc方法

// CYLNSObject+RunAtDealloc.h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime實現cyl_runAtDealloc方法

#import "CYLBlockExecutor.h"

const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;

@interface NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block;

@end


// CYLNSObject+RunAtDealloc.m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime實現cyl_runAtDealloc方法

#import "CYLNSObject+RunAtDealloc.h"
#import "CYLBlockExecutor.h"

@implementation NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block
{
    if (block) {
        CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];

        objc_setAssociatedObject(self,
                                 runAtDeallocBlockKey,
                                 executor,
                                 OBJC_ASSOCIATION_RETAIN);
    }
}

@end

使用方法: 導入

#import "CYLNSObject+RunAtDealloc.h"

然後就可以使用了:

    NSObject *foo = [[NSObject alloc] init];

    [foo cyl_runAtDealloc:^{
        NSLog(@"正在釋放foo!");
    }];

如果對cyl_runAtDealloc的實現原理有興趣,可以看下這篇博文 Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object


7.ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字

  1. 對應基本數據類型默認關鍵字是

    atomic,readwrite,assign

  2. 對於普通的OC對象

    atomic,readwrite,strong

參考鏈接:

  1. Objective-C ARC: strong vs retain and weak vs assign

  2. Variable property attributes or Modifiers in iOS


非ARC:atomic,readwrite,assign

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

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

  1. 原子性---nonatomic特質

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

  2. 讀/寫權限---readwrite(讀寫)readooly (只讀)

  3. 內存管理語義---assignstrong、 weakunsafe_unretainedcopy
  4. 方法名---getter=<name> 、setter=<name>

    getter=<name>的樣式:

      @property (nonatomic, getter=isOn) BOOL on;
    

    ( setter=<name>這種不常用,也不推薦使用。故不在這裏給出寫法。)

  5. 不常用的:nonnull,null_resettable,nullable

unsafe_unretained 不建議使用:

如果這樣聲明兩個屬性:

並定義

  1. @property (nonatomic, strong) NSString *string1;   
  2. @property (nonatomic, unsafe_unretained) NSString *string2;  

再來猜一下,下面的代碼會有什麼結果?
  1. self.string1 = @"String 1";   
  2. self.string2 = self.string1;   
  3. self.string1 = nil;  
  4. NSLog(@"String 2 = %@", self.string2);  

請注意,在此我並沒有叫你猜會有什麼輸出,因爲根本不會有輸出,你的程序會crash掉。

原因是什麼,其實就是野指針造成的,所以野指針是可怕的。爲何會造成野指針呢?同於用unsafe_unretained聲明的指針,由於self.string1=nil已將內存釋放掉了,但是string2並不知道已被釋放了,所以是野指針。然後訪問野指針的內存就造成crash.  所以儘量少用unsafe_unretained關鍵字。



擴展strong,weak, unsafe_unretained都是用來聲明屬性的,如果想聲明臨時變量就得用__strong,  __weak, __unsafe_unretained,  __autoreleasing, 其用法與上面介紹的類似。
__unsafe_unretain、__strong、__autoreleasing可以在不使用ARC(自動參考計數)可用。在ARC下,默認的指針都是__strong屬性。這意味着一個對象賦值給另外一個指針,那麼只要指針參考了該對象,該對象就會一直保持。這對於大部分對象都實用,但是這可能會導致retain cycle。例如,你擁有一個對象包含了另外了一個實例變量對象,但是第二個對象又把前一個對象作爲它的委託,那麼這兩個對象將不會被釋放。

__autoreleasing的用法介紹:

在c/c++,objective-c內存管理中有一條是:誰分配誰釋放。 __autoreleasing則可以使對像延遲釋放。比如你想傳一個未初始化地對像引用到一個方法當中,在此方法中實始化此對像,那麼這種情況將是__autoreleasing表演的時候。看個示例:

  1. - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{   
  2.     NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];  
  3.     NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];  
  4.     NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];  
  5.     *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];  
  6. }  
  7. -(void)test  
  8. {  
  9.     NSError *error = nil;   
  10.     [self generateErrorInVariable:&error];  
  11.     NSLog(@"Error = %@", error);  
  12. }  

這樣即便在函數內部申請的空間,在函數外部也可以使用,同樣也適合誰分配誰釋放的原則。


同樣下面的代碼也是類似原因, 只不過在沒有開啓ARC的情況下適用:

  1. -(NSString *)stringTest  
  2. {  
  3.     NSString *retStr = [NSString stringWithString:@"test"];  
  4.       
  5.     return [[retStr retain] autorelease];  
  6. }  

開啓ARC後,應改爲:
  1. -(NSString *)stringTest  
  2. {  
  3.     __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];  
  4.       
  5.     return retStr;  
  6. }  

9.@synthesize和@dynamic

9.1分別的作用

  1. @property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那麼默認的就是@syntheszie var = _var;
  2. @synthesize的語義是如果你沒有手動實現setter方法和getter方法,那麼編譯器會自動爲你加上這兩個方法。
  3. @dynamic告訴編譯器:屬性的setter與getter方法由用戶自己實現,不自動生成。(當然對於readonly的屬性只需提供getter即可)。假如一個屬性被聲明爲@dynamic var,然後你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程序運行到instance.var = someVar,由於缺setter方法會導致程序崩潰;或者當運行到 someVar = var時,由於缺getter方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。

9.2@synthesize合成實例變量的規則,有以下幾點:

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

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

  3. 如果是 @synthesize foo; 還會生成一個名稱爲foo的成員變量,也就是說:

    如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量,

  4. 如果是 @synthesize foo = _foo; 就不會生成成員變量了.

如:
@interface TestClass : NSObject
{
    NSObject * _object;
}
@property (nonatomic,retain)NSObject *object;
@property (nonatomic,retain)NSObject *_object;
@end
Xcode會提示:Auto property synthesis will not synthesizeproperty '_object' because it cannot share an ivar with another synthesized property
意思是property名爲object,存在一個名爲_object的實例變量,那麼就不會自動合成新變量

9.3不會autosynthesis(自動合成)的情況

前三條就是手動設置了存取方法
  1. 同時重寫了setter和getter時
  2. 重寫了只讀屬性的getter時
  3. 使用了@dynamic時
  4. 在 @protocol 中定義的所有屬性
  5. 在 category 中定義的所有屬性
  6. 重載的屬性

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

注意:
如果你用到了ivar,autosynthesis(自動合成)又失效了,如果不去手動定義ivar,那麼你就得藉助@synthesize來手動合成ivar。
如:
@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(實例變量/成員變量)。這時候有兩種選擇:

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

更多信息,請戳- 》 When should I use @synthesize explicitly?


上述文章整理自:https://github.com/ChenYilong/iOSInterviewQuestions




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章