iOS內(nèi)存管理探底

關(guān)于內(nèi)存

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


image.png

大致說下五大區(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,我們扯扯這個的工作原理:


1707017-089e69ea2340eaa6.png
- (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 就成了一個難題。


image.png

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ù)返回時會被自動釋放掉。

二、修飾屬性

  • assign
  • weak
  • strong
  • retain
  • copy
  • unsafe_unretained
image.png
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);

打印如下:


image.png

很明顯,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)野指針


image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.1 什么是自動引用計數(shù) 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,463評論 1 17
  • 那年的冬季,暫時放下了愛。不是不愛,是傷痛讓自己對愛變得茫然。抓取一片楓葉,卻在相思中飄零并化進(jìn)了土里不留下一...
    王講江紅閱讀 504評論 0 0
  • 看著窗外街燈下似曾相識的街道,聽著同行的小伙伴已入夢鄉(xiāng)的呼吸,今晚已是此行臺灣的最后一夜,一周時間從南到北,真是不...
    honeyloveya閱讀 208評論 0 0
  • 代表你掌握一門知識的標(biāo)志是能夠給別人講述出來,如果不能講授,說明你根本沒有掌握
    MrBadman閱讀 239評論 0 0
  • 新年伊始,大家伙是不是又開始進(jìn)入新一輪的問候和套路之中了。累,真是累啊,千山萬水總是情,少點套路行不行。這個嘛,當(dāng)...
    wuli小情懷閱讀 2,334評論 7 13

友情鏈接更多精彩內(nèi)容