范圍:
- 任何繼承了NSObject的對象,對基本數據類型無效。OC對象是放在堆內存里,非OC對象是放在棧內存里,棧內存里的東西系統會自動管理
內存區域
- 內存分為5個區域,分別指的是----->棧區/堆區/BSS段/數據段/代碼段
- 棧:存儲局部變量,當其作用域執行完畢之后,就會被系統立即收回,函數調用開銷,比如局部變量,分配的內存空間地址越來越小
- 堆:存儲OC對象,手動申請的字節空間,需要調用free來釋放
- BSS段:未初始化的全局變量和靜態變量,一旦初始化就會從BSS段中回收掉,轉存到數據段中
- 數據段:存儲已經初始化的全局變量和靜態變量,以及常量數據,直到結束程序時才會被立即收回
- 代碼段:代碼,直到結束程序時才會被立即收回
原理
- 每個對象內部都保存了一個與之相關聯的整數,稱之為引用計數器。
- 當使用alloc,new copy 創建一個對象時,對象的引用計數器被設置為1.
- 給對象發送一條retain消息,可以使引用計數器值+1
- 給對象發送一條release消息,可以使引用計數器值-1
- 當一個對象的引用計數器值為0時,那么它將被銷毀,其占用的內存被系統回收,OC也會自動向對象發送一條dealloc消息,一般會重寫dealloc方法,在這里釋放一些相關資源,一定不要直接調用dealloc方法。
- 可以給對象發送retainCount消息獲得當前的引用計數器值
內存管理原則
- 誰創建,誰釋放。如果你通過alloc,new或(mutable)copy來創建一個對象,那么你必須調用release或autorelease。換句話來說,不是你創建的,就不用你去釋放
- 一般來說,除來allocation,new或copy之外的方法創建的對象都被聲明了autorelease。
- 誰retain,誰release,只要你調用了retain,無論這個對象是如何生成的,你都要release。
//計數器為1 NSObject *obj = [[NSObject alloc] init]; //0,被釋放了。 [obj release]; //查看引用計數 [obj retainCount];
- 在MRC在用retain修飾屬性,先release原來的值在retain新的值。
自動釋放(autorelease)
- OC對象只需要發送一條autorelease消息,就會把這個對象添加到最近的自動釋放池(棧頂的釋放池),不會改變引用計數。
- autorelease實際上只是把對release的調用延遲了,對于每一次autorelease,系統只是把該對象放入了當前的autoreleasepool中,當pool被釋放時,該pool中所有的對象會被調用release。
- 靜態方法返回的對象不需要自己管理內存,會自動釋放。
自動釋放池
- 自動釋放池是oc里面的一種內存自動回收機制,一般可以將一些臨時變量添加到自動釋放池中,統一回收釋放,當自動釋放池銷毀時,池里面的所有對象都會調用一次release方法
- 自動釋放池中的對象會集中同一時間釋放,如果操作需要生成的對象較多占用內存空間大,可以使用多個釋放池來進行優化。比如在一個循環中需要創建大量的臨時變量,可以創建內部的池子來降低內存占用峰值
- 使用注意
- 再ARC下,不能使用[[autoreleasepool alloc]init],可以使用@autoreleasepool
- 不要把大量循環操作放在同一個autoreleasepool 中,這樣會造成內存峰值的上升。
- 盡量避免對大內存使用該方法,對于這種延遲釋放機制還是少用。
- SDK中一般利用靜態方法創建并返回的對象已經是autorelease,不需要release操作了。
- 通過[NSmunber numberWithInt:10];返回的對象不再需要release的,但是通過[[NSnumber alloc]initWithInt:10]創建的對象需要release
野指針和空指針
-
野指針
- 在C中,聲明一個指針變量,沒有為這個指針變量初始化,那么這個指針變量的值也就是一個垃圾值,指針指向隨機的一塊空間,那么我們叫做野指針
- 在OC中,一個指針指向的對象被釋放了,那么這個指針叫野指針
- 給野指針發消息會報EXC_BAD_ACCESS錯誤
-
空指針
- 沒有指向存儲空間的指針(里面存的是nil, 也就是0)
- 給空指針發消息是沒有任何反應的
-
僵尸對象
已經被收回但是這個對象的數據仍然處在內存中,像這樣的對象叫做僵尸對象
僵尸對象有可能可以訪問也有可能不可以訪問,當僵尸對象所占的內存空間還沒有分配給別人使用的時候,這個數據的對象其實仍然存在,通過指針仍然可以找到這個對象,所以說這個時候僵尸對象還可以被訪問,當這個僵尸對象已經分配給別人使用的時候,這個對象就不存在了,這個時候不可以被訪問
注意:一旦一個對象成為僵尸對象之后,這個對象無論如何都不應該被使用,無論有沒有分配給別人使用,都不能用!且不可以復活
屬性修飾
- 讀寫屬性
- readwrite
- readonly
- set處理
- retain:自動把set方法中的成員變量,release原來的值,然后再retain新的值。
- assign:基本數據類型,set方法直接賦值,而不進行retain操作。
- copy:set方法release原來的值,在copy新的值。
- getter:指定get方法的方法名。
- 原子性
定時器的循環引用問題
- CADisplayLink、NSTimer會對target產生強引用,如果target又對它們產生強引用,那么就會引發循環引用
- 解決方案
- 使用block
- 使用代理對象(NSProxy)
- NSProxy介紹
- 不繼承NSObject,和NSObject是同一個級別,是一個特殊的基類
- 作用:經常用作,做消息轉發。
- 示例
//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對象不需要調用init,因為它本來就沒有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 //調用 #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技術,用于優化NSNumber、NSDate、NSString等小對象的存儲
在沒有使用Tagged Pointer之前, NSNumber等對象需要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值
使用Tagged Pointer之后,NSNumber指針里面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中
當指針不夠存儲數據時,才會使用動態分配內存的方式來存儲數據
objc_msgSend能識別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數據,節省了以前的調用開銷
如何判斷一個指針是否為Tagged Pointer?
- iOS平臺,最高有效位是1(第64bit)非Tagged Pointer最后一位是0。
- Mac平臺,最低有效位是1
copy詳解
fcfdaa4c.png
自定義對象添加copy屬性
- 實現NSCopying協議
- 重寫- (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 //調用 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; }
引用計算的存儲
- 在64bit中,引用計數可以直接存儲在優化過的isa指針中,也可能存儲在SideTable類中
- refcnts是一個存放著對象引用計數的散列表