美文网首页
iOS 内存管理

iOS 内存管理

作者: Nothing_xy | 来源:发表于2021-08-06 10:06 被阅读0次

    范围:

    • 任何继承了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是一个存放着对象引用计数的散列表

    相关文章

      网友评论

          本文标题:iOS 内存管理

          本文链接:https://www.haomeiwen.com/subject/ricwpftx.html