IOS屬性修飾符總結

前言

很多剛接觸iOS的朋友,對property的可選參數如何使用,什麼情況下使用哪種選項不瞭解,也問了我很多這方面的知識,雖然知道怎麼用,但是有些說不出其區別。在這裏,再次深入學習一遍,對copy/strong/weak/__weak/__strong/assign的使用場景總結總結。如果有說得不對的地方,請指出。如果有疑問,請私聊我,或者直接回復我。

自動引用計數

原文檔關於自動引用說明:

Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code, the object graphs, and the relationships between objects in your application.

翻譯過來就是:Automatic Reference Counting (ARC)是一個編譯器的特性,提供了對iOS對象的自動內存管理,ARC在編譯期間自動在適當的地方添加ObjC對象的retainrelease操作代碼,而不需要我們關心。

ARC在編譯期間,根據Objective-C對象的存活週期,在適當的位置添加retainrelease代碼。從概念上講,ARC與手動引用計數內存管理遵循同樣的內存管理規則,但是ARC也無法防止循環強引用。

ARC還引入了新的修飾符來修飾變量和聲明屬性:

  • 聲明變量的修飾符:__strong, __weak, _unsafeunretained, __autoreleasing;
  • 聲明屬性的修飾符:strong, weak, unsafe_unretained。
  • 對象和Core Foundation-style對象直接的轉換修飾符號:bridge,bridge_retained或CFBridgingRetain, _bridgetransfer或CFBridgingRelease。
  • 對於線程的安全,有nonatomic,這樣效率就更高了,但是不是線程的。如果要線程安全,可以使用atomic,這樣在訪問是就會有線程鎖。
  • 記住內存管理法則:誰使對象的引用計數+1,不再引用時,誰就負責將該對象的引用計數-1。

實戰性學習

下面我們來聲明一個Person類來學習:

@interface Person : NSObject
 
// 注意:蘋果有命名規範的,命名屬性時,不能以copy開頭。
// 如果下面的屬性聲明爲copyString,會編譯不通過。
@property (nonatomic, copy) NSString *copiedString;
 
// 默認會是什麼呢?
@property (nonatomic) NSString *name;
// 默認是strong類型
@property (nonatomic) NSArray *array;
 
@end


如果屬性沒有指定類型,默認是什麼呢?其實是strong。如果證明呢?驗證方法:分別將array屬性的類型分別設置爲weakassign,strong,不設置,這四種情況的結果分別是:第一種打印爲空,第二種直接直接崩潰,第三種和最後一種是可以正常使用。如下面的驗證代碼:

Person *lili = [[Person alloc] init];
lili.name = @"LiLi";
lili.copiedString = @"LiLi\' father is LLL";
lili.array = @[@"謝謝", @"感謝"];
  
NSArray *otherArray = lili.array;
lili = nil;
NSLog(@"%@", otherArray);


再繼續添加下面的代碼。默認聲明變量的類型爲__strong類型,因此上面的NSArray *otherArray = lili.array;__strong NSArray *otherArray = lili.array;是一樣的。如果我們要使用弱引用,特別是在解決循環強引用時就特別重要了。我們可以使用__weak聲明變量爲弱引用,這樣就不會增加引用計數值。

__strong NSArray *strongArray = otherArray;
otherArray = nil;
// 打印出來正常的結果。
NSLog(@"strongArray = %@", strongArray);
  
__weak NSArray * weakArray = strongArray;
strongArray = nil;
// 打印出來:null
NSLog(@"weakArray: %@", weakArray);


xib/storybard連接的對象爲什麼可以使用weak

@property (nonatomic, weak) IBOutlet UIButton *button;

像上面這行代碼一樣,在連接時自動生成爲weak。因爲這個button已經放到view上了,因此只要這個View不被釋放,這個button的引用計數都不會爲0,因此這裏可以使用weak引用。

如果我們不使用xib/storyboard,而是使用純代碼創建呢?

@property (nonatomic, weak) UIButton *button;


補充:有朋友反映這裏說得不夠詳細,感謝這位朋友的反饋。

使用weak時,由於button在創建時,沒有任何強引用,因此就有可能提前釋放。Xcode編譯器會告訴我們,這裏不能使用weak。因此我們需要記住,只要我們在創建以後需要使用它,我們必須保證至少有一個強引用,否則引用計數爲0,就會被釋放掉。對於上面的代碼,就是由於在創建時使用了weak引用,因此button的引用計數仍然爲0,也就是會被釋放,編譯器在編譯時會檢測出來的。

這樣寫,在創建時通過self.button = ...就是出現錯誤,因爲這是弱引用。所以我們需要聲明爲強引用,也就是這樣:

@property (nonatomic, strong) UIButton *button;


block聲明使用copy

在使用block時,儘量使用typedef來起一個別名,這樣更容易閱讀。使block作爲屬性時,儘量使用copy。蘋果官方有說明,因爲有上下文變量捕獲,應該使用copy。但是很多朋友說使用strong也一樣,沒有區別。目前,沒有辦法說明這使用strong與copy導致有沒有區別。不過,筆者一直都會使用copy來聲明Block,而不是strong:

typedef void (^HYBTestBlock)(NSString *name);
 
@property (nonatomic, copy) HYBTestBlock testBlock;


字符串使用copy

對於字符串,通常都是使用copy的方式。雖然使用strong也沒有沒有問題,但是事實上在開發中都會使用copy。爲什麼這麼做?因爲對於字符串,我們希望是一次內容的拷貝,外部修改也不會影響我們的原來的值,而且NSString類遵守了NSCopying, NSMutableCopying, NSSecureCoding協議。

下面時使用copy的方式,驗證如下:

NSString *hahaString = @"哈哈";
NSString *heheString = [hahaString copy];
// 哈哈, 哈哈
NSLog(@"%@, %@", hahaString, heheString);
heheString = @"呵呵";
// 哈哈, 呵呵
NSLog(@"%@, %@", hahaString, heheString);

我們修改了heheString,並不會影響到原來的hahaString。copy一個對象變成新的對象(新內存地址) 引用計數爲1 原來對象計數不變。

屬性聲明修飾符

屬性聲明修飾符有:strong, weak, unsafe_unretained, readWrite,默認strong, readWrite的。

  • strong:strong和retain相似,只要有一個strong指針指向對象,該對象就不會被銷燬
  • weak:聲明爲weak的指針,weak指針指向的對象一旦被釋放,weak的指針都將被賦值爲nil;
  • unsafe_unretained:用unsafe_unretained聲明的指針,指針指向的對象一旦被釋放,這些指針將成爲野指針。
@property (nonatomic, copy) NSString *name;
// 一旦所指向的對象被釋放,就會成爲野指針
@property (nonatomic, unsafe_unretained) NSString *unsafeName;
 
lili.name = @"Lili";
lili.unsafeName = lili.name;
lili.name = nil;
// unsafeName就變成了野指針。這裏不會崩潰,因爲爲nil.
NSLog(@"%@", lili.unsafeName);


深拷貝與淺拷貝

關於淺拷貝,簡單來說,就像是人與人的影子一樣。而深拷貝就像是夢幻西遊中的龍宮有很多個長得一樣的龍宮,但是他們都是不同的精靈,因此他們各自都是獨立的。

我相信還有不少朋友有這樣一種誤解:淺拷貝就是用copy,深拷貝就是用mutableCopy。如果有這樣的誤解,一定要更正過來。copy只是不可變拷貝,而mutableCopy是可變拷貝。比如,

// 那麼arr是不可變的
NSArray *arr = [modelsArray copy];
 
// 那麼ma是可變的。
NSMutableArray *ma = [modelsArray mutableCopy];


舉個例子:

lili.array = [@[@"謝謝", @"感謝"] mutableCopy];
NSMutableArray *otherArray = [lili.array copy];
lili.array[0] = @"修改了謝謝";
 
// 打印: 謝謝 修改了謝謝
// 說明數組裏面是字符串時,直接使用copy是相當於深拷貝的。
NSLog(@"%@ %@", otherArray[0], lili.array[0]);


這裏就是淺拷貝,但是由於數組中的元素都是字符串,因此不會影響原來的值。

數組中是對象時:

NSMutableArray *personArray = [[NSMutableArray alloc] init];
Person *person1 = [[Person alloc] init];
person1.name = @"lili";
[personArray addObject:person1];
  
Person *person2 = [[Person alloc] init];
person2.name = @"lisa";
[personArray addObject:person2];
  
// 淺拷貝
NSArray *newArray = [personArray copy];
Person *p = newArray[0];
p.name = @"lili的名字被修改了";
  
// 打印結果:lili的名字被修改了
// 說明這邊修改了,原來的數組對象的值也被修改了。雖然newArray和personArray不是同一個數組,不是同一塊內存,
// 但是實際上兩個數組的元素都是指向同一塊內存。
NSLog(@"%@", ((Person *)(personArray[0])).name);
深拷貝,其實就是對數組中的所有對象都創建一個新的對象:
 
NSMutableArray *personArray = [[NSMutableArray alloc] init];
Person *person1 = [[Person alloc] init];
person1.name = @"lili";
[personArray addObject:person1];
  
Person *person2 = [[Person alloc] init];
person2.name = @"lisa";
[personArray addObject:person2];
  
// 深拷貝
NSMutableArray *newArray = [[NSMutableArray alloc] init];
for (Person *p in personArray) {
Person *newPerson = [[Person alloc] init];
newPerson.name = p.name;
 
[newArray addObject:newPerson];
}
Person *p = newArray[0];
p.name = @"lili的名字被修改了";
  
// 打印結果:lili
NSLog(@"%@", ((Person *)(personArray[0])).name);


閱讀更多關於深拷貝與淺拷貝

Getter/Setter

在ARC下,getter/setter的寫法與MRC的不同了。我面試過一些朋友,筆試這關就寫得很糟(不包括算法)。通常在筆試時都會讓重寫一個屬性的Getter/Setter方法。

@property (nonatomic, strong) NSMutableArray *array;
 
- (void)setArray:(NSMutableArray *)array {
  if (_array != array) {
    _array = nil;
    
    _array = array;
  }
}


如果是要重寫getter就去呢?就得增加一個變量了,如果同時重寫getter/setter方法,就不會自動生成array變量,因此我們可以聲明一個變量爲array:

- (void)setArray:(NSMutableArray *)array {
  if (_array != array) {
    _array = nil;
    
    _array = array;
  }
}
 
- (NSMutableArray *)array {
  return _array;
}

總結

關於屬性的這些選項的學習,做一下總結:

  • 所有的屬性,都儘可能使用nonatomic,以提高效率,除非真的有必要考慮線程安全
  • NSString:通常都使用copy,以得到新的內存分配,而不只是原來的引用。
  • strong:對於繼承於NSObject類型的對象,若要聲明爲強使用,使用strong,若要使用弱引用,使用__weak來引用,用於解決循環強引用的問題。
  • weak:對於xib上的控件引用,可以使用weak,也可以使用strong。
  • __weak:對於變量的聲明,如果要使用弱引用,可以使用weak,如:weak typeof(Model) weakModel = model;就可以直接使用weakModel了。
  • __strong:對於變量的聲明,如果要使用強引用,可以使用strong,默認就是strong,因此不寫與寫__strong聲明都是一樣的。
  • __unsafe_unretained:在所引用的對象被釋放後,該指針就成了野指針,不好控制。對於C語言的指針,我們會使用這個來聲明。
  • __autoreleasing:如果要在循環過程中就釋放,可以手動使用__autoreleasing來聲明將之放到自動釋放池。

參考資料

官方文檔關於自動引用計數內存管理介紹:官方文檔說明











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