ios内存管理

2021-12-13  本文已影响0人  斐波那契程序员

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

    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
    [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

注意点:会对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];
    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);

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

extern void _objc_autoreleasePoolPrint(void);
struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}

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

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;
}
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对象地址的区域。

上一篇 下一篇

猜你喜欢

热点阅读