iOS 內存管理

范圍:

  • 任何繼承了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是一個存放著對象引用計數的散列表
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容