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阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容