美文网首页
iOS中的内存管理

iOS中的内存管理

作者: 文小猿666 | 来源:发表于2021-03-19 14:04 被阅读0次

    本节主要理解:
    1.定时器的种类与注意事项(NSTimer循环引用/)
    2.内存布局
    3.Tagged Pointer
    4.引用计数的原理
    5.weak引用的原理
    6.深拷贝与浅拷贝
    7.自动释放池

    一.读写安全

    1.atomic与noatomic

    2.pthead_rwlock

    3.dispatch_barrier_async 栅栏函数
    多读单写

    dispatch_queue_t dispatchQueue = dispatch_queue_create("hyq.queue.next", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, block1_for_reading)  
    dispatch_async(queue, block2_for_reading) 
    
    dispatch_async(queue, block_for_writing)
    
    dispatch_async(queue, block3_for_reading)  
    dispatch_async(queue, block4_for_reading)
    
    

    以上可能导致数据混乱
    使用栅栏函数

    dispatch_async(queue, block1_for_reading)  
    dispatch_async(queue, block2_for_reading)
    
    dispatch_barrier_async(queue, block_for_writing)
    
    dispatch_async(queue, block3_for_reading)  
    dispatch_async(queue, block4_for_reading) 
    

    dispatch_barrier_async会把队列的运行周期分为这三个过程:

    • 首先等目前追加到并行队列中所有任务都执行完成
    • 开始执行dispatch_barrier_async中的任务这时候即便向并行队列提交任务,也不会执行
    • dispatch_barrier_async中任务执行完成后,并行队列恢复正常。

    总的来说,dispatch_barrier_async起到了承上启下的作用。它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。正如barrier的含义一样,它起到一个栅栏或者分水岭的作用。

    使用并行队列和diapatch_barrier_async方法,就可以高效的进行数据和文件读写了。

    二.Tagged Pointer

    ■从64bit开始, iOS引入了Tagged Pointer技术,用于优化NSNumber. NSDate. NSString等小对象的存储
    ■在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等, NSNumber指针存储的是堆中NSNumber对象的地址值
    ■ 使用Tagged Pointer之后, NSNumber指针里面存储的数据变成了: Tag + Data ,也就是将数据直接存储在了指针中
    当对象指针的最低有效位是1 ,则该指针为Tagged Pointer,当对象指针的最低有效位是0时,为普通OC对象
    ■当指针不够存储数据时 ,才会使用动态分配内存的方式来存储数据
    ■objc_ msgSend能识别Tagged Pointer ,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销

    特点
    1.我们也可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:
    Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
    2.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
    3.在内存读取上有着3倍的效率,创建时比以前快106倍。
    由此可见,苹果引入Tagged Pointer
    ,不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。

    三.copy和mutableCopy

    1.iOS提供了2个拷贝方法
    copy,不可变拷贝,产生不可变副本
    mutableCopy,可变拷贝,产生可变副本

    深拷贝和浅拷贝
    深拷贝---内容拷贝,有产生新对象
    浅拷贝---指针拷贝,未产生新对象

    总结
    不可变对象NSString ,NSArray,NSDictionary,copy是浅拷贝,mutableCopy是深拷贝
    可变对象NSMutableString,NSMutableArray,NSMutableDictionary,不管copy还是mutableCopy都是深拷贝

    图片.png

    为什么NSString使用copy修饰,NSMutableString使用strong修饰?
    1.当原字符串是NSString时,由于是不可变字符串,所以,不管使用strong还是copy修饰,都是指向原来的对象,copy操作只是做了一次浅拷贝。
    2.当源字符串是NSMutableString时,strong只是将源字符串的引用计数加1,而copy则是对原字符串做了次深拷贝,从而生成了一个新的对象,并且copy的对象指向这个新对象。

    所以,如果源字符串是NSMutableString的时候,使用strong只会增加引用计数。但是copy会执行一次深拷贝,会造成不必要的内存浪费。而如果原字符串是NSString时,strong和copy效果一样,就不会有这个问题。
    但是,我们一般声明NSString时,也不希望它改变,所以一般情况下,建议使用copy,这样可以避免NSMutableString带来的错误。

    四.引用计数的存储

    在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中

    struct SideTable {
      spinlock_t slock;
      RefcountMap refcnts;
      weak_table_ t weak_table;
    };
    

    refcnts是一个存放着对象引用计数的散列表

    五.autoreleasepool 自动释放池

    ■自动释放池的主要底层数据结构是 :_ AtAutoreleasePool. AutoreleasePoolPage
    ■调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
    源码分析

    0 clang重写@autoreleasepool

    0 objc4源码 : NSObject.mm

    class AutoreleasePoolPage
    {
      magic_ t const magic;
      id*next; 
      pthread_ _t const thread ; 
      AutoreleasePoolPage * const parent;
      AutoreleasePoolPage *child;
      uint32_ ,t const depth;
      uint32_ .t hiwat;
    }
    
    面试题

    1.使用CADisplayLink、 NSTimer(基于runloop)有什么注意点 ?
    循环引用
    CADisplayLink、 NSTimer 会对taget产生强引用,如果target又对他们强引用,就会造成循环引用

    此种方式会造成循环引用
    self. timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target : self selector :@selector(timerTest) userInfo:nil repeats:YES];
    

    (1)最简单的解决办法如下

    _ weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer *_ Nonnull timer) [
      [weakSelf timerTest];
    });
    

    (2)或者使用NSProxy,(专门用来做转发,是一个基类,与NSObject并列,方法调用时会省去第一阶段-消息发送阶段,直接进入第三阶段-消息转发阶段)自行了解
    不准时
    因为依赖于runloop,如果runloop任务过于繁重(如线程中有滚动视图正在滚动,NSTimer会暂停),可能会导致NSTimer不准时,可以使用GCD定时器更加准时

    //创建-个定时器
    dispatch_ source_ t timer = dispatch_ source_ create (DISPATCH_ SOURCE_ TYPE_ TIMER, 0, 0, queue); 
    //没置吋囘(start是几秒后幵始抗行, interval是吋囘囘隔)
    dispatch_ source_ ,set_ timer(timer,
    dispatch_ time (DISPATCH_ TIME_ NOW, (int64_ t)(start * NSEC_ PER_ SEC)),
    (uint64_ t)(interval *NSEC_ PER_ SEC),
    0);
    //设置回调
    dispatch_ source_ ,set_ event_ handler(timer, ^{
    ));
    //启动定时器
    dispatch_ resume (timer);
    

    2.介绍下内存的几大区域
    地址从低到高布局

    图片.png
    ■代码段: 编译之后的代码
    ■数据段
    0字符串常量:比如NSString *str = @"123"
    0已初始化数据:已初始化的全局变量、静态变量等
    0未初始化数据:未初始化的全局变量、静态变量等
    ■堆: 通过alloc、malloc. calloc等动态分配的空间,分配的内存空间地址越来越大
    ■栈: 函数调用开销,比如局部变量。分配的内存空间地址越来越小

    3.讲一下你对iOS内存管理的理解

    4.autorelease在什么时机会被释放
    ① 如果有@autoreleasepool{},所以autoreleasepool里面调用了autorelease方法的对象会在{}结束之后释放。
    ② 如果没写@autoreleasepool{},由于整个程序没有退出,autoreleasepool里面调用了autorelease方法的对象会在RunLoop休眠之前被释放。

    5.方法里有局部对象 ,出了方法后会立即释放吗
    会立即释放,因为就相当于在方法的最后加一行release代码。

    6.ARC 都帮我们做了什么?
    实际上"引用计数式内存管理"的本质在ARC中并没有改变,ARC只是自动的帮助我们处理"引用计数"的相关部分

    7.weak指针的实现原理

    • 在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
    struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;     refcnts是一个存放着对象引用计数的散列表
    weak_table_t weak_table;   弱引用表
    };
    
    图片.png

    当对象引用计数为0时,会自动调用dealloc方法。
    苹果将弱引用都存在一个哈希表中,当对象调用dealloc方法时,系统会取出对象的地址,作为key到哈希表中找到对应的弱引用,并将其销毁掉。

    相关文章

      网友评论

          本文标题:iOS中的内存管理

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