關(guān)于內(nèi)存
App啟動后會把程序拷貝到內(nèi)存里,如下圖所示,內(nèi)存是一塊自下而上,地址由低到高分布的區(qū)域

大致說下五大區(qū):
- 棧
連續(xù)的內(nèi)存區(qū)域。使用棧保護(hù)函數(shù)現(xiàn)場,包括函數(shù)里的局部變量的分配和釋放,通過壓棧和彈棧的方式保護(hù)函數(shù)現(xiàn)場,先進(jìn)后出,由編譯器自動分配釋放,程序員不要管 - 堆
不連續(xù)的內(nèi)存區(qū)域,由程序員自己分配釋放([[xx alloc ] init]),又叫優(yōu)先隊列,本質(zhì)上是二叉樹
為什么要二叉樹:現(xiàn)實中,有很多一對多的情況要處理,尤其是面向?qū)ο蟮目蚣?/p>
- 全局區(qū)/靜態(tài)區(qū)
程序結(jié)束由系統(tǒng)釋放 - 常量
常量字符串存于此(由const修飾)
常量與宏的區(qū)別:宏定義只在預(yù)處理器進(jìn)行文本替換,不做任何的類型檢查,大量使用還會增大二進(jìn)制文件大?。怀A縿t共享一塊內(nèi)存空間,就算項目中N處用到,也不會分配N塊內(nèi)存空間,可以被修改,在編譯階段會執(zhí)行類型檢查
- 代碼區(qū)
存放二進(jìn)制代碼
MRC
一、引用計數(shù)(保留計數(shù))
1.1 原理
iOS的內(nèi)存管理主要是依賴引用計數(shù),so,我們扯扯這個的工作原理:

- (void)fun_1{
id obj = [[NSObject alloc] init];
[self printCount:obj];//1
[obj retain];
[self printCount:obj];//2
[obj retain];
[self printCount:obj];//3
[obj release];
[self printCount:obj];//2
[obj release];
[self printCount:obj];//1
[obj release];
[self printCount:obj];//crash, 因為對象已經(jīng)被釋放,無法打印retaincount
}
- (void)printCount:(id)obj{
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
注意,開啟MRC模式,可以在Build phase添加-fno-objc-arc
image.png
1.2 為什么要用retaincount
在面向?qū)ο蟮目蚣苤?,?jīng)常會出現(xiàn)對象直接傳遞和共享數(shù)據(jù),那此時的數(shù)據(jù)何時釋放?
假如對象 A 生成了一個對象 M,需要調(diào)用對象 B 的某一個方法,將對象 M 作為參數(shù)傳遞過去。在沒有引用計數(shù)的情況下,一般內(nèi)存管理的原則是 “誰申請誰釋放”,那么對象 A 就需要在對象 B 不再需要對象 M 的時候,將對象 M 銷毀。但對象 B 可能只是臨時用一下對象 M,也可能覺得對象 M 很重要,將它設(shè)置成自己的一個成員變量,那這種情況下,什么時候銷毀對象 M 就成了一個難題。

1.3 四大原則
自己生成的對象,自己持有。
非自己生成的對象,自己也能持有。
不在需要自己持有的對象的時候,釋放。
非自己持有的對象無法釋放。
ARC(自動的引用計數(shù))
ARC是蘋果引入的一中自動管理內(nèi)存的機制,背后的原理是依賴編譯器的靜態(tài)分析能力,通過在編譯時找出合理的插入引用計數(shù)管理代碼,讓程序員放飛自我
一、修飾變量
__strong__weak__unsafe_unretained__autoreleasing
1.1 __strong
__strong是默認(rèn)的變量修飾符。只要還有一個強引用指向?qū)ο螅搶ο缶鸵恢贝嬖?。會使retaincount+1
NSObject * a = [[NSObject alloc] init];
[self printCount:a];//2
注意,這里retaincount是2不是1,跟MRC是不一樣的,具體原因是因為這里等價于NSObject * __strong a = [[NSObject alloc] init];,而__strong會使retaincount+1
# 強引用對象指向強引用對象
__strong NSObject *obj1=[NSObject new];
__strong NSObject *obj2 = obj1;
obj1=nil;
NSLog(@"%@,%@",obj1,obj2);
//(null),<NSObject: 0x60000001f6c0>
1.2 __weak
__weak表示其存亡不決定所指對象的存亡,如果沒有強引用指向了,就被置為nil
# 強引用對象指向弱引用對象
__strong NSObject *obj1=[NSObject new];
__weak NSObject *obj2 = obj1;
obj1=nil;
NSLog(@"%@,%@",obj1,obj2);
//(null),(null)
1.3 __unsafe_unretained
表示其存亡不決定所指對象的存亡,如果沒有強引用指向了,不會被置為nil。如果它引用的對象被回收掉了,該指針就變成了野指針
1.4 __autoreleasing
用于標(biāo)示使用引用傳值的參數(shù)(id *),在函數(shù)返回時會被自動釋放掉。
二、修飾屬性
assignweakstrongretaincopyunsafe_unretained

2.1 retain, strong其實一個意思
2.2 copy, strong區(qū)別
- copy會重新開辟一塊內(nèi)存,并將源對象的內(nèi)容傳給新對象,是深拷貝
- strong只是將源對象的指針傳給新對象,是淺拷貝,如果源對象的內(nèi)容變化,新對象也跟著變化
#import <Foundation/Foundation.h>
@interface XYPerson : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,strong) NSString * StrongName;
@end
測試如下
NSMutableString *name = [NSMutableString stringWithFormat:@"will is so"];
self.name = name;
self.StrongName = name;
[name appendString:@" handsome"];
NSLog(@"%@ \n %@", self.name, self.StrongName);
打印如下:

很明顯,StrongName內(nèi)容發(fā)生了變化
拓展
經(jīng)常有這樣面試題
用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問題?
答:是因為NSString、NSArray、NSDictionary有對應(yīng)的可變類型NSMutableString、NSMutableArray、NSMutableDictionary,如果屬性被這些可變類型賦值了,那么會導(dǎo)致屬性無意變動,為避免這些,使用copy;如果使用strong關(guān)鍵字,會導(dǎo)致屬性無意變動
2.3 strong, weak區(qū)別
- strong:強引用,其存亡直接決定所指對象的存亡,在賦值時對對象進(jìn)行retain操作,使引用計數(shù)+1
- weak:弱引用,其存亡不決定所指對象的存亡,若所指對象被其他強引用指向,強引用置為nil,則其所指對象也置為nil
2.4 引用循環(huán)
- delegate都是用
weak修飾,為啥
兩個對象各有一個強引用指向?qū)Ψ?,會造成引用循環(huán)
image.png
當(dāng)[tableView.delegate method]就會使得delegate引用計數(shù)+1,導(dǎo)致無法釋放,所以用@property (nonatomic, weak) id<Delegate>delegate - block的引用循環(huán),也可以用
__weak破
#define RCLog(obj){if (obj){printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));}else{printf("null \n");}}
RCLog(self);//retain count = 7
[self block:^{
self.strongPoint = [NSDate date];
}];
RCLog(self);//retain count = 8
打印引用計數(shù),發(fā)現(xiàn)+1了,這里self持有block,而block又持有self,導(dǎo)致了引用循環(huán),我們用__weak來解決:
RCLog(self);//retain count = 7
__weak typeof(self) weakself=self;
[self block:^{
weakself.strongPoint = [NSDate date];
}];
RCLog(self);//retain count = 7
有的block用__strong來修飾對象,是為了防止對象引用時,不會已經(jīng)是nil了,舉個例子:
RCLog(self);//retain count = 7
__weak typeof(self) weakself=self;
[self block:^{
__strong typeof (self) strongself = weakself;
strongself.strongPoint = [NSDate date];
}];
RCLog(self);//retain count = 7
2.5 assign, weak區(qū)別
- assign與其他的都不一樣,只有他是修飾基本數(shù)據(jù)類型和結(jié)構(gòu)體的,其他的都是修飾對象的
- weak與assign不一樣的地方,是他指向的對象消失時候(內(nèi)存釋放),會自動置為nil,而assign則不會,這樣給weak修飾的屬性發(fā)送消息不會crash
舉個例子:
#import <Foundation/Foundation.h>
@interface XYPerson : NSObject
@property (nonatomic, strong) id strongPoint;
@property (nonatomic, weak) id weakPoint;
@property (nonatomic, assign) id assignPoint;
@end
測試如下:
self.strongPoint = [NSDate date];
self.weakPoint = self.strongPoint;
self.assignPoint = self.strongPoint;
self.strongPoint = nil;
打斷點,當(dāng)strongPoint置為nil后,weakPoint也置為nil,而assignPoint則出現(xiàn)野指針

參考
理解 iOS 的內(nèi)存管理 | 唐巧的博客
Objective-C 內(nèi)存管理——你需要知道的一切 - skyline75489 - SegmentFault 思否
iOS概念攻堅之路(三):內(nèi)存管理 - 掘金
iOS 宏(define)與常量(const)的正確使用
iOS 內(nèi)存管理總結(jié)

