ARC在編譯期間,根據Objective-C對象的存活周期,在適當的位置添加retain和release代碼。從概念上講,ARC與MRC內存管理遵循同樣的內存管理規則,區別MRC, ARC工作是編譯器完成的, 但是ARC也無法防止循環強引用。
ARC還引入了新的修飾符來修飾變量和聲明屬性。
聲明變量的修飾符:__strong, __weak, __unsafe_unretained, __autoreleasing;
聲明屬性的修飾符:strong, weak, unsafe_unretained。
對于線程的安全,有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屬性的類型分別設置為weak, assign,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。
typedef void (^HYBTestBlock)(NSString *name);
@property (nonatomic, copy) HYBTestBlock testBlock;
字符串
對于字符串,通常都是使用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是可變拷貝。比如,NSArray *arr = [modelsArray copy],那么arr是不可變的。而NSMutableArray *ma = [modelsArray mutableCopy],那么ma是可變的。
lili.array = [@[@"謝謝", @"感謝"] mutableCopy];
NSMutableArray *otherArray = [lili.array copy];
lili.array[0] = @"修改了謝謝";
NSLog(@"%@ %@", otherArray[0], 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來引用,用于解決循環強引用的問題。
(對于數組, 字典, 集合, 字符串來說, 可變用strong修飾, 不可變用copy修飾)
weak:對于xib上的控件引用,可以使用weak,也可以使用strong。
__weak:對于變量的聲明,如果要使用弱引用,可以使用__weak,如:__weak typeof(Model) weakModel = model;就可以直接使用weakModel了。
__strong:對于變量的聲明,如果要使用強引用,可以使用__strong,默認就是__strong,因此不寫與寫__strong聲明都是一樣的。
unsafe_unretained:這個是比較少用的,幾乎沒有使用到。在所引用的對象被釋放后,該指針就成了野指針,不好控制。
__unsafe_unretained:也是很少使用。同上。
__autoreleasing:如果要在循環過程中就釋放,可以手動使用__autoreleasing來聲明將之放到自動釋放池。
參考資料
http://www.lxweimin.com/p/c16467bbedc1