美文网首页
ios内存管理

ios内存管理

作者: 斐波那契程序员 | 来源:发表于2021-12-13 19:35 被阅读0次

    ios采用引用计数管理对象的生命周期,开启指针优化后对象的引用计数器可能存在于isa结构体中,

    • CADisplayLink:类似于定时器,跟屏幕的刷新频率一致,也就是16ms一次,但是如果掉帧就不准了,用法:
        CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
        [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    

    注意点:会对target对象产生强引用,导致其无法释放。

    • NSTimer:定时器,多种用法,若使用以下用法也会对target产生强引用导致其无法释放。
        [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:true];
    

    如何解决以上强引用带来的内存无法释放的问题?

    中间人代理模式proxy,NSProxy专门同来解决代理问题,让代理对象对当前对象产生弱引用,CADisplayLink和NSTimer对代理对象产生强引用,这样定时器就不会对当前对象产生强引用了。proxy定义如下:

    @interface TargetProxy : NSProxy
    + (instancetype)proxyWithTarget:(id)target;
    @property(nonatomic, weak)id target;
    @end
    
    @implementation TargetProxy
    + (instancetype)proxyWithTarget:(id)target
    {
        //NSProxy类是不需要init方法的
        TargetProxy *proxy = [self alloc];
        proxy.target = target;
        return proxy;
    }
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)sel
    {
        return [self.target methodSignatureForSelector:sel];
    }
    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        [invocation invokeWithTarget:self.target];
    }
    @end
    
    

    proxy用法如下:

        //CADisplayLink代理用法
        CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[TargetProxy proxyWithTarget:self] selector:@selector(test)];
        [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        //NSTimer代理用法
        [NSTimer scheduledTimerWithTimeInterval:1 target:[TargetProxy proxyWithTarget:self] selector:@selector(test) userInfo:nil repeats:true];
    

    NSTimer还可以使用代码库解决这个问题如下:

        __weak typeof(self) ws = self;
        [[NSTimer scheduledTimerWithTimeInterval:1
                                         repeats:YES
                                           block:^(NSTimer * _Nonnull timer) {
            [ws test];
        }] fire];
    
    • GCD定时器
      NSTimer定时器依赖runloop,如果runloop任务繁重可能导致定时器不准时。可以使用GCD定时器来解决此问题:
        dispatch_queue_t queue = dispatch_queue_create("timer_queue", DISPATCH_QUEUE_CONCURRENT);
        //创建定时器
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        //设置定时器时间
        dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(2 * NSEC_PER_SEC)), (uint64_t)(2 * NSEC_PER_SEC), 0);
        //设置回调
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"%s",__func__);
        });
        //启动定时器
        dispatch_resume(timer);
    
    • ios程序的内存布局
      地址从低到高依次是:保留区、代码段、数据段、堆区、栈区、内核区。需要注意的是堆区地址从小到大分配,栈区是从大到小分配。

    • tagged pointer技术
      此技术是从64位架构开始,用于优化NSNumber、NSString等ios小对象的内存问题。此技术之前ios中小对象存储也是跟其他类是一样的,动态分布内存、维护引用计数等。使用此技术后ios中的小对象使用tag+data的方式存储,也就是将对象的值直接存储在对象指针中,不会区堆区开辟内存空间了。当指针无法存储数据后才会动态分配内存来存储数据。objc_ msgSend能够识别指针类型是否是使用了此技术,若使用此技术会直接从指针中获取数据,节省了方法调用开销,大大提升了ios的运行效率。通过源码可以看到mac平台上指针地址最低位用来标记是否使用此技术,ios平台使用最高位(64bit)来标记。

    • 对象内存管理
      在iOS中,使用引用计数来管理OC对象的内存。一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它。想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1。copy不可变对象为浅拷贝,不会开辟新的内存空间;copy可变对象或者使用mutaleCopy都是深拷贝,深拷贝会开辟新的内存空间,生成新对象;对象的copy需要遵守NSCopy协议,使用copyWithZone方法进行copy。

    可以通过申明以下函数来查看自动释放池中的内存情况

    extern void _objc_autoreleasePoolPrint(void);
    
    • 对象的引用计数
      在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
    struct SideTable {
        spinlock_t slock;
        RefcountMap refcnts;
        weak_table_t weak_table;
    }
    

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

    • dealloc流程
      dealloc-> _objc_rootDealloc-> rootDealloc-> object_dispose->objc_destructInstance
    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
    
            // This order is important.
            if (cxx) object_cxxDestruct(obj);
            if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
            obj->clearDeallocating();
        }
    
        return obj;
    }
    
    • 自动释放池
      自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage。调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。AutoreleasePoolPage数据结构:
    class AutoreleasePoolPage;
    struct AutoreleasePoolPageData
    {
        magic_t const magic;
        __unsafe_unretained id *next;// 指向当前page的下一个存储对象
        pthread_t const thread;
        AutoreleasePoolPage * const parent;//指向上一个page,root节点为空nil
        AutoreleasePoolPage *child;//指向下一个page,最后一个节点为nil
        uint32_t const depth;
        uint32_t hiwat;
    };
    

    所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起。每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量(56字节),剩下的空间用来存放autorelease对象的地址(4040字节)。调用objc_autoreleasePoolPush方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址。调用objc_autoreleasePoolPop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY。id *next指向了下一个能存放autorelease对象地址的区域。

    • runloop与autorelease
      iOS在主线程的Runloop中注册了2个Observer,第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush(),第2个Observer监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush(),也监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()。

    相关文章

      网友评论

          本文标题:ios内存管理

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