iOS 內(nèi)存管理

范圍:

  • 任何繼承了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詳解

fcfdaa4c.png

自定義對(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ù)的散列表
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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