范圍:
- 任何繼承了NSObject的對(duì)象,對(duì)基本數(shù)據(jù)類型無效。OC對(duì)象是放在堆內(nèi)存里,非OC對(duì)象是放在棧內(nèi)存里,棧內(nèi)存里的東西系統(tǒng)會(huì)自動(dòng)管理
內(nèi)存區(qū)域
- 內(nèi)存分為5個(gè)區(qū)域,分別指的是----->棧區(qū)/堆區(qū)/BSS段/數(shù)據(jù)段/代碼段
- 棧:存儲(chǔ)局部變量,當(dāng)其作用域執(zhí)行完畢之后,就會(huì)被系統(tǒng)立即收回,函數(shù)調(diào)用開銷,比如局部變量,分配的內(nèi)存空間地址越來越小
- 堆:存儲(chǔ)OC對(duì)象,手動(dòng)申請(qǐng)的字節(jié)空間,需要調(diào)用free來釋放
- BSS段:未初始化的全局變量和靜態(tài)變量,一旦初始化就會(huì)從BSS段中回收掉,轉(zhuǎn)存到數(shù)據(jù)段中
- 數(shù)據(jù)段:存儲(chǔ)已經(jīng)初始化的全局變量和靜態(tài)變量,以及常量數(shù)據(jù),直到結(jié)束程序時(shí)才會(huì)被立即收回
- 代碼段:代碼,直到結(jié)束程序時(shí)才會(huì)被立即收回
原理
- 每個(gè)對(duì)象內(nèi)部都保存了一個(gè)與之相關(guān)聯(lián)的整數(shù),稱之為引用計(jì)數(shù)器。
- 當(dāng)使用alloc,new copy 創(chuàng)建一個(gè)對(duì)象時(shí),對(duì)象的引用計(jì)數(shù)器被設(shè)置為1.
- 給對(duì)象發(fā)送一條retain消息,可以使引用計(jì)數(shù)器值+1
- 給對(duì)象發(fā)送一條release消息,可以使引用計(jì)數(shù)器值-1
- 當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)器值為0時(shí),那么它將被銷毀,其占用的內(nèi)存被系統(tǒng)回收,OC也會(huì)自動(dòng)向?qū)ο蟀l(fā)送一條dealloc消息,一般會(huì)重寫dealloc方法,在這里釋放一些相關(guān)資源,一定不要直接調(diào)用dealloc方法。
- 可以給對(duì)象發(fā)送retainCount消息獲得當(dāng)前的引用計(jì)數(shù)器值
內(nèi)存管理原則
- 誰創(chuàng)建,誰釋放。如果你通過alloc,new或(mutable)copy來創(chuàng)建一個(gè)對(duì)象,那么你必須調(diào)用release或autorelease。換句話來說,不是你創(chuàng)建的,就不用你去釋放
- 一般來說,除來allocation,new或copy之外的方法創(chuàng)建的對(duì)象都被聲明了autorelease。
- 誰retain,誰release,只要你調(diào)用了retain,無論這個(gè)對(duì)象是如何生成的,你都要release。
//計(jì)數(shù)器為1 NSObject *obj = [[NSObject alloc] init]; //0,被釋放了。 [obj release]; //查看引用計(jì)數(shù) [obj retainCount];- 在MRC在用retain修飾屬性,先release原來的值在retain新的值。
自動(dòng)釋放(autorelease)
- OC對(duì)象只需要發(fā)送一條autorelease消息,就會(huì)把這個(gè)對(duì)象添加到最近的自動(dòng)釋放池(棧頂?shù)尼尫懦兀?,不?huì)改變引用計(jì)數(shù)。
- autorelease實(shí)際上只是把對(duì)release的調(diào)用延遲了,對(duì)于每一次autorelease,系統(tǒng)只是把該對(duì)象放入了當(dāng)前的autoreleasepool中,當(dāng)pool被釋放時(shí),該pool中所有的對(duì)象會(huì)被調(diào)用release。
- 靜態(tài)方法返回的對(duì)象不需要自己管理內(nèi)存,會(huì)自動(dòng)釋放。
自動(dòng)釋放池
- 自動(dòng)釋放池是oc里面的一種內(nèi)存自動(dòng)回收機(jī)制,一般可以將一些臨時(shí)變量添加到自動(dòng)釋放池中,統(tǒng)一回收釋放,當(dāng)自動(dòng)釋放池銷毀時(shí),池里面的所有對(duì)象都會(huì)調(diào)用一次release方法
- 自動(dòng)釋放池中的對(duì)象會(huì)集中同一時(shí)間釋放,如果操作需要生成的對(duì)象較多占用內(nèi)存空間大,可以使用多個(gè)釋放池來進(jìn)行優(yōu)化。比如在一個(gè)循環(huán)中需要?jiǎng)?chuàng)建大量的臨時(shí)變量,可以創(chuàng)建內(nèi)部的池子來降低內(nèi)存占用峰值
- 使用注意
- 再ARC下,不能使用[[autoreleasepool alloc]init],可以使用@autoreleasepool
- 不要把大量循環(huán)操作放在同一個(gè)autoreleasepool 中,這樣會(huì)造成內(nèi)存峰值的上升。
- 盡量避免對(duì)大內(nèi)存使用該方法,對(duì)于這種延遲釋放機(jī)制還是少用。
- SDK中一般利用靜態(tài)方法創(chuàng)建并返回的對(duì)象已經(jīng)是autorelease,不需要release操作了。
- 通過[NSmunber numberWithInt:10];返回的對(duì)象不再需要release的,但是通過[[NSnumber alloc]initWithInt:10]創(chuàng)建的對(duì)象需要release
野指針和空指針
-
野指針
- 在C中,聲明一個(gè)指針變量,沒有為這個(gè)指針變量初始化,那么這個(gè)指針變量的值也就是一個(gè)垃圾值,指針指向隨機(jī)的一塊空間,那么我們叫做野指針
- 在OC中,一個(gè)指針指向的對(duì)象被釋放了,那么這個(gè)指針叫野指針
- 給野指針發(fā)消息會(huì)報(bào)EXC_BAD_ACCESS錯(cuò)誤
-
空指針
- 沒有指向存儲(chǔ)空間的指針(里面存的是nil, 也就是0)
- 給空指針發(fā)消息是沒有任何反應(yīng)的
-
僵尸對(duì)象
已經(jīng)被收回但是這個(gè)對(duì)象的數(shù)據(jù)仍然處在內(nèi)存中,像這樣的對(duì)象叫做僵尸對(duì)象
僵尸對(duì)象有可能可以訪問也有可能不可以訪問,當(dāng)僵尸對(duì)象所占的內(nèi)存空間還沒有分配給別人使用的時(shí)候,這個(gè)數(shù)據(jù)的對(duì)象其實(shí)仍然存在,通過指針仍然可以找到這個(gè)對(duì)象,所以說這個(gè)時(shí)候僵尸對(duì)象還可以被訪問,當(dāng)這個(gè)僵尸對(duì)象已經(jīng)分配給別人使用的時(shí)候,這個(gè)對(duì)象就不存在了,這個(gè)時(shí)候不可以被訪問
注意:一旦一個(gè)對(duì)象成為僵尸對(duì)象之后,這個(gè)對(duì)象無論如何都不應(yīng)該被使用,無論有沒有分配給別人使用,都不能用!且不可以復(fù)活
屬性修飾
- 讀寫屬性
- readwrite
- readonly
- set處理
- retain:自動(dòng)把set方法中的成員變量,release原來的值,然后再retain新的值。
- assign:基本數(shù)據(jù)類型,set方法直接賦值,而不進(jìn)行retain操作。
- copy:set方法release原來的值,在copy新的值。
- getter:指定get方法的方法名。
- 原子性
定時(shí)器的循環(huán)引用問題
- CADisplayLink、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用
- 解決方案
- 使用block
- 使用代理對(duì)象(NSProxy)
- NSProxy介紹
- 不繼承NSObject,和NSObject是同一個(gè)級(jí)別,是一個(gè)特殊的基類
- 作用:經(jīng)常用作,做消息轉(zhuǎn)發(fā)。
- 示例
//MJProxy.h @interface MJProxy : NSProxy + (instancetype)proxyWithTarget:(id)target; @property (weak, nonatomic) id target; @end //MJProxy.m #import "MJProxy.h" @implementation MJProxy + (instancetype)proxyWithTarget:(id)target { // NSProxy對(duì)象不需要調(diào)用init,因?yàn)樗緛砭蜎]有init方法 MJProxy *proxy = [MJProxy alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end //調(diào)用 #import "ViewController.h" #import "MJProxy.h" #import "MJProxy1.h" @interface ViewController () @property (strong, nonatomic) NSTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES]; } - (void)timerTest { NSLog(@"%s", __func__); } - (void)dealloc { NSLog(@"%s", __func__); [self.timer invalidate]; }
Tagged Pointer
從64bit開始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對(duì)象的存儲(chǔ)
在沒有使用Tagged Pointer之前, NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲(chǔ)的是堆中NSNumber對(duì)象的地址值
使用Tagged Pointer之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中
當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)
objc_msgSend能識(shí)別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開銷
如何判斷一個(gè)指針是否為Tagged Pointer?
- iOS平臺(tái),最高有效位是1(第64bit)非Tagged Pointer最后一位是0。
- Mac平臺(tái),最低有效位是1
copy詳解

自定義對(duì)象添加copy屬性
- 實(shí)現(xiàn)NSCopying協(xié)議
- 重寫- (id)copyWithZone:(NSZone *)zone方法
- 示例
MJPerson.h @interface MJPerson : NSObject <NSCopying> @property (assign, nonatomic) int age; @property (assign, nonatomic) double weight; @end MJPerson.m @implementation MJPerson - (id)copyWithZone:(NSZone *)zone { MJPerson *person = [[MJPerson allocWithZone:zone] init]; person.age = self.age; person.weight = self.weight; return person; } - (NSString *)description { return [NSString stringWithFormat:@"age = %d, weight = %f", self.age, self.weight]; } @end //調(diào)用 int main(int argc, const char * argv[]) { @autoreleasepool { MJPerson *p1 = [[MJPerson alloc] init]; p1.age = 20; p1.weight = 50; MJPerson *p2 = [p1 copy]; p2.age = 30; NSLog(@"%@", p1); NSLog(@"%@", p2); //MRC下要release [p2 release]; [p1 release]; } return 0; }
引用計(jì)算的存儲(chǔ)
- 在64bit中,引用計(jì)數(shù)可以直接存儲(chǔ)在優(yōu)化過的isa指針中,也可能存儲(chǔ)在SideTable類中
- refcnts是一個(gè)存放著對(duì)象引用計(jì)數(shù)的散列表