iOS内存管理

CADisplayLink、NSTimer使用注意

  • CADisplayLinkNSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
  • 解决方案
    • 初始化TImer时,采用block的形式初始化
    • 使用代理对象(NSProxy), 其主要思想是引入第三方对象,timer对第三方对象强引用,第三方对象弱引用。第三方对象采用消息转发给到target,执行对应的方法

第三方代理对象的两种形式

  • 直接继承NSObject, 消息转发时直接 forwardingTargetForSelector返回target对象,这种方式的缺点是在相应其他方法时,会返回 Proxy对象(比如 isMemberOf),照理说应该返回target对象才对, 要到这种效果,必须在Proxy类中重写对应的方法才能办到 (比如isMemberOf),所以这样就导致了这个代理类会重写NSObject中许多其他的方法,这样就比较冗余麻烦了,因为它的作用仅仅是起一个代理转发的作用
@interface Proxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

#import "Proxy.h"
@implementation Proxy

+ (instancetype)proxyWithTarget:(id)target
{
  Proxy *proxy = [[Proxy alloc] init];
  proxy.target = target;
  return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
  return self.target;
}

@end
  • 继承NSProxy, NSProxy这个类本身就是专门用来做代理转发的,它类本身已经对自己的方法进行了消息转发,我们只需要关系本类对象中的taget的消息转发过程, 使用起来比直接继承NSObject的那种方案要便捷些
#import 

@interface Proxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

#import "Proxy.h"
@implementation Proxy

+ (instancetype)proxyWithTarget:(id)target
{
  // NSProxy对象不需要调用init,因为它本来就没有init方法
  Proxy *proxy = [Proxy alloc];
  proxy.target = target;
  return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
  return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
  [invocation invokeWithTarget:self.target];
}

@end

GCD定时器

  • NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
  • 而GCD的定时器会更加准时

#import "GCDTimer.h"

@implementation GCDTimer

static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        timers_ = [NSMutableDictionary dictionary];
        semaphore_ = dispatch_semaphore_create(1);
    });
}

+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
    if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
    
    // 队列
    dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
    
    // 创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 设置时间
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                              interval * NSEC_PER_SEC, 0);
    
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    // 定时器的唯一标识
    NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
    // 存放到字典中
    timers_[name] = timer;
    dispatch_semaphore_signal(semaphore_);
    
    // 设置回调
    dispatch_source_set_event_handler(timer, ^{
        task();
        
        if (!repeats) { // 不重复的任务
            [self cancelTask:name];
        }
    });
    
    // 启动定时器
    dispatch_resume(timer);
    
    return name;
}

+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
    if (!target || !selector) return nil;
    
    return [self execTask:^{
        if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [target performSelector:selector];
#pragma clang diagnostic pop
        }
    } start:start interval:interval repeats:repeats async:async];
}

+ (void)cancelTask:(NSString *)name
{
    if (name.length == 0) return;
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    
    dispatch_source_t timer = timers_[name];
    if (timer) {
        dispatch_source_cancel(timer);
        [timers_ removeObjectForKey:name];
    }

    dispatch_semaphore_signal(semaphore_);
}

@end

iOS程序的内存布局

iOS程序的内存布局图
  • 代码段存放编译之后的二进制代码
  • 数据段:
    • 存放字符串常量
    • 已初始化的全部变量,静态变量
    • 未初始化的全局变量,静态变量
  • 堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大
  • 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小

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)
    • Mac平台,最低有效位是1

#if TARGET_OS_OSX && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif

#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
#   define _OBJC_TAG_MASK 1UL
#endif

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
  • 思考以下2段代码能发生什么事?有什么区别?
- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}

- (void)test {
  dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            // 加锁
            self.name = [NSString stringWithFormat:@"abcdefghijk"];
            // 解锁
        });
    }
  • 上述代码会出现多线程访问崩溃问题,因为给name属性赋值相当于调用了其setter方法,ARC会被编译器自动转换为MRC,在MRC中,会先释放之前的_name, 然后在retain新传入进来的,出现崩溃就是因为多个线程调用release方法,出现重复释放问题。解决这个崩溃的主要方法可以直接把name属性修饰符改为atomic,或者在多线程中直接加锁
- (void)test {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abc"];
        });
    }
}

  • 上述代码就不会出现崩溃,因为abc这个字符比较短,直接采用tag pointer就能存储下来,相当于abc直接存储在name的地址中,不存在内存的释放问题
- (void)test {
    NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
    NSString *str2 = [NSString stringWithFormat:@"ab"];
    
    NSLog(@"%@ %@", [str1 class], [str2 class]); // __NSCFString NSTaggedPointerString
}
  • iOS12之前打印指针的值能很清晰的看到数据等信息,iOS12之后系统则打印的完全看不懂了,看了源代码发现苹果是做了混淆,让我们不能直接得到值,从而避免我们去很容易就伪造出一个tagged pointer对象

OC对象的内存管理

  • 在iOS中,使用引用计数来管理OC对象的内存
  • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
  • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
  • 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
  • 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
  • 可以通过以下私有函数来查看自动释放池的情况: extern void _objc_autoreleasePoolPrint(void);

Copy关键字

  • copy,不可变拷贝,产生不可变副本
  • mutableCopy,可变拷贝,产生可变副本
  • 深拷贝和浅拷贝
    • 深拷贝:内容拷贝,产生新的对象
    • 浅拷贝:指针拷贝,没有产生新的对象


@interface Person : NSObject
@property (copy, nonatomic) NSMutableArray *data;
@property (strong, nonatomic) NSMutableArray *data1
@end

#import "Person.h"

@implementation Person

- (void)setData:(NSArray *)data {
    if (_data != data) {
        [_data release];
        _data = [data copy]; // 调用copy之后, _data 变为NSArray, 如果外部对dat数组进行添加元素,会出现unrecognise selector崩溃
    }
}

- (void)setData1:(NSMutableArray *)data1 {
    if (!_data1 != data1) {
        [_data1 release];
        _data1 = [data1 retain] // 用strong修饰之后,data1的引用计数加1,依然是NSMutableArray
    }
}

- (void)dealloc {
    self.data = nil;
    [super dealloc];
}

@end

自定义对象的copy

  • 对用于自定义对象要调用copy方法,必须实现NSCopy协议, 在 - (id)copyWithZone:(NSZone *)zone方法中,执行相应的操作
  • 自定义对象不想要调用 mutableCopy方法,因为拿到了自定义对象的指针之后,就可以直接去访问的内存数据

引用计数的存储

  • 在64bit中,引用计数可以直接存储在优化过的isa指针中,从isa的结构可以看出,extra_rc里面存储的值是引用计数器减1,has_sidetable_rc引用计数器是否过大无法存储在isa中, 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
};

struct SideTable {
    // 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}
  • refcnts是一个存放着对象引用计数的散列表
  • weak_table weak引用的全局hash表
inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

SideTable

struct SideTable {
    // 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}

weak 表 --- weak_table_t

struct weak_table_t {
    //  hash数组,用来存储弱引用对象的相关信息weak_entry_t
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};
  • weak_entries: hash数组,用来存储弱引用对象的相关信息weak_entry_t

  • num_entries: hash数组中的元素个数

  • mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)

  • max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)

  • weak_table_t是一个典型的hash结构。weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息。使用不定类型对象的地址作为 key ,用 weak_entry_t 类型结构体对象作为 value 。

weak_entry_t

struct weak_entry_t {
    DisguisedPtr referent; // 被弱引用的对象
    union {
        struct {
            weak_referrer_t *referrers;  // 弱引用该对象的对象指针地址的hash数组
            uintptr_t        out_of_line_ness : 2; // 是否使用动态hash数组标记位
            uintptr_t        num_refs : PTR_MINUS_2; // hash数组中的元素个数
            uintptr_t        mask;  // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。
            uintptr_t        max_hash_displacement; //  // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};
  • 可以看到在weak_entry_t 的结构定义中有联合体,在联合体的内部有定长数组inline_referrers[WEAK_INLINE_COUNT]和动态数组weak_referrer_t *referrers两种方式来存储弱引用对象的指针地址。通过out_of_line()这样一个函数方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。当超过WEAK_INLINE_COUNT时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。

  • 弱引用表的结构是一个hash结构的表,Key是对象的地址,对象的地址从SideTables中取出SideTable,在SideTable取出weak_table,weak_table中的weak_entry_t指针指向的就是这个对象所有的弱引用信息

  • weak对象的初始化流程

    • objc_initWeak
    • storeWeak主要包括新旧散列表的创建,使用weak_unregister_no_lock清除旧值,使用weak_register_no_lock分配新值
static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    // 声明两个SideTable
    // 新旧散列表的创建
    SideTable *oldTable;
    SideTable *newTable;

    // 获得新值和旧值的锁位置
    // 通过地址来建立索引标识,防止桶重复
    // 下面的指向操作会改变旧值
 retry:
    if (haveOld) {
        // 更改指针,获得以oldObj为索引所存储的值地址
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        // 更改指针,获得以newObj为索引所存储的值地址
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // 加锁操作,防止多线程中竞争冲突
    SideTable::lockTwo(oldTable, newTable);
    // location应该与oldObj保持一致,如果不同,说明当前的location已经处理过oldObj,可是又被其他线程修改
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(oldTable, newTable);
        goto retry;
    }

    // 防止弱引用间死锁
    // 并且通过+initialize初始化构造器所保证所有弱引用的isa非空指向
    if (haveNew  &&  newObj) {
        // 获取新对象的isa
        Class cls = newObj->getIsa();
        // 判断isa非空且已经初始化
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 解锁
            SideTable::unlockTwo(oldTable, newTable);
            // 对isa指针进行初始化
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            // 如果该类已经完成执行 +initialize 方法是最理想情况
            // 如果该类 +initialize 在线程中
            // 例如 +initialize 正在调用 storeWeak 方法
            // 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
            previouslyInitializedClass = cls;
            // 重新尝试
            goto retry;
        }
    }

    // 清除旧值
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 分配新值
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
    
        // 如果弱引用被释放 weak_register_no_lock 方法返回 nil
        // 在引用计数表中设置若引用标记位
        if (newObj  &&  !newObj->isTaggedPointer()) {
            // 弱引用位初始化操作
            // 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
            newObj->setWeaklyReferenced_nolock();
        }
        // 之前不要设置 location 对象,这里需要更改指针指向
        *location = (id)newObj;
    }
    else {
        // 没有新值,则无需更改
    }
    
    SideTable::unlockTwo(oldTable, newTable);

    return (id)newObj;
}

旧对象解除注册操作 weak_unregister_no_lock

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    // 用指针去访问 oldObj 和 *location
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;
    // 如果其对象为 nil,无需取消注册
    if (!referent) return;
    // weak_entry_for_referent 根据首对象查找 weak_entry
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 通过地址来解除引用关联
        remove_referrer(entry, referrer);
        bool empty = true;
        // 检测 out_of_line 位的情况
        // 检测 num_refs 位的情况
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            // 将引用表中记录为空
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        // 从弱引用的 zone 表中删除
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
    // 这里不会设置 *referrer = nil,因为 objc_storeWeak() 函数会需要该指针
    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

新对象添加注册操作 weak_register_no_lock

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{   // 用指针去访问 oldObj 和 *location
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    // 检测对象是否生效、以及是否使用了 tagged pointer 技术
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    // 保证引用对象是否有效
    // hasCustomRR 方法检查类(包括其父类)中是否含有默认的方法
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        // 检查 dealloc 状态
        deallocating = referent->rootIsDeallocating();
    }
    else {
        // 会返回 referent 的 SEL_allowsWeakReference 方法的地址
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    // 由于 dealloc 导致 crash ,并输出日志
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // 记录并存储对应引用表 weak_entry
    weak_entry_t *entry;
    // 对于给定的弱引用查询 weak_table
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //  增加弱引用表于附加对象上
        append_referrer(entry, referrer);
    } 
    else {
        // // 自行创建弱引用表
        weak_entry_t new_entry(referent, referrer);
        // 如果给定的弱引用表满容,进行自增长
        weak_grow_maybe(weak_table);
        // 向对象添加弱引用表关联,不进行检查直接修改指针指向
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}
  • weak对象的销毁轨迹
    • dealloc
    • _objc_rootDealloc
    • rootDealloc
    • object_dispose
    • objc_destructInstance
    • clearDeallocating
    • clearDeallocating_slow
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
   assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

   // 获取当前对象的散列表对象
   SideTable& table = SideTables()[this];
   table.lock();
   if (isa.weakly_referenced) {
       //如果isa中标记了是若引用,则 从weak_table中清除当前对象的弱引用
       weak_clear_no_lock(&table.weak_table, (id)this);
   }
   if (isa.has_sidetable_rc) {
       // 如果isa中标记了是has_sidetable_rc,则 从引用计数散列表中清除当前的引用计数
       table.refcnts.erase(this)
   }
   table.unlock();
}

自动释放池

  • 自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
  • 调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
  • 在main函数中做测试:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 编译为C++后是
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */
    {
        __AtAutoreleasePool __autoreleasepool;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_crl5bnj956d806cp7d3ctqhm0000gn_T_main_d37e0d_mi_0);
     }//大括号对应释放池的作用域
     
     return 0;
}

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
    
  ~__AtAutoreleasePool() {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
    
  void * atautoreleasepoolobj;
};
  • 等价于
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}
  • 源码分析
    • clang重写@autoreleasepool
    • objc4源码:NSObject.mm
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
    
  ~__AtAutoreleasePool() {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
    
  void * atautoreleasepoolobj;
};
  • 在NSObject.mm中
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}



  • 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
  • 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起


AutoreleasePoolPage的结构

class AutoreleasePoolPage 
{
    magic_t const magic;                  //校验AutoreleasePagePoolPage结构是否完整
    id *next;                             //指向新加入的autorelease对象的下一个位置,初始化时指向begin()
    pthread_t const thread;               //当前所在线程,AutoreleasePool是和线程一一对应的
    AutoreleasePoolPage * const parent;   //指向父节点page,第一个结点的parent值为nil
    AutoreleasePoolPage *child;           //指向子节点page,最后一个结点的child值为nil
    uint32_t const depth;                 //链表深度,节点个数
    uint32_t hiwat;                       //数据容纳的一个上限
}


static inline void *push()
 {
     id *dest;
     if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
           dest = autoreleaseNewPage(POOL_BOUNDARY);
      } else {
           dest = autoreleaseFast(POOL_BOUNDARY);
      }
     assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
 }
  • AutoreleasePoolPage中拥有parentchild指针,分别指向上一个和下一个page;当前一个page的空间被占满(每个AutorelePoolPage的大小为4096字节)时,就会新建一个AutorelePoolPage对象并连接到链表中,后来的 Autorelease对象也会添加到新的page中
  • next== begin()时,表示AutoreleasePoolPage为空
  • next == end(),表示AutoreleasePoolPage已满
  • POOL_BOUNDARY的作用:起一个标识作用,每当自动释放池初始化调用objc_autoreleasePoolPush方法时,总会通过AutoreleasePoolPagepush方法,将POOL_BOUNDARY放到当前page的栈顶,并且返回这个边界对象;而在自动释放池释放调用objc_autoreleasePoolPop方法时,又会将边界对象以参数传入,这样自动释放池就会向释放池中对象发送release消息,直至找到第一个边界对象为止。

理解objc_autoreleasePoolPush方法

  • 其调用轨迹
    • objc_autoreleasePoolPush
    • AutoreleasePoolPage::push()
    • autoreleaseFast
    • add
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

  static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        // page存在且没有满,直接添加
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            // page存在且已满,则重新初始化一个新的page
            return autoreleaseFullPage(obj, page);
        } else {
            // page不存在,则创建一个新的hotPage
            return autoreleaseNoPage(obj);
        }
    }

  // 当前hotPage已满时调用
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        setHotPage(page);
        return page->add(obj);
    }

 // 当前hotPage不存在时调用
    id *autoreleaseNoPage(id obj)
    {
        assert(!hotPage());
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            return setEmptyPoolPlaceholder();
        }
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);
    }

 // 压栈操作:将对象加入AutoreleaseNoPage并移动栈顶的指针
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }
  • 每次调用push其实就是一个创新的AutoReleasePool,在对应的AutoReleasePage中插入一个POOL_BOUNDARY,并且返回插入的POOL_BOUNDARY的内存地址
  • push方法内部调用的是autoreleaseFast方法,并传入边界对象(POOL_BOUNDARY)hotPage可以理解为当前正在使用的AutoreleasePoolPage
  • 自动释放池最终都会通过page->add(obj)方法将边界对象添加到释放池中,而这一过程在autoreleaseFast方法中被分为三种情况:
    • 当前page存在且不满,调用page->add(obj)方法将对象添加至page的栈中,即next指向的位置
    • 当前page存在但是已满,调用autoreleaseFullPage初始化一个新的page,调用page->add(obj)方法将对象添加至page的栈中
    • 当前page不存在时,调用autoreleaseNoPage创建一个hotPage,再调用page->add(obj) 方法将对象添加至page的栈中

objc_autoreleasePoolPop方法

  • AutoreleasePool释放调用的是objc_autoreleasePoolPop方法,此时需要传入边界对象作为参数
static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            if (hotPage()) {
                pop(coldPage()->begin());
            } else {
                setHotPage(nil);
            }
            return;
        }
        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
            } else {
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();
        // 向栈中的对象发送release消息,直到约到第一个POOL_BOUNDARY
        page->releaseUntil(stop);
        // 删除空掉的节点
        if (DebugPoolAllocation  &&  page->empty()) {
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

  • 上述代码中,首先根据传入的边界对象地址找到边界对象所处的page;
  • 然后选择当前page中最新加入的对象一直向前清理,可以向前跨越若干个page,直到边界所在的位置;清理的方式是向这些对象发送一次release消息,使其引用计数减一;

autorelease方法

  • 其调用栈
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)
  • 如上所示,autorelease方法最终也会调用上面提到的 autoreleaseFast方法,将当前对象加到AutoreleasePoolPage
  • autorelease函数和push函数一样,关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,不过push函数入栈的是一个边界对象,而autorelease函数入栈的是一个具体的Autorelease的对象

RunLoop和线程的关系

  • RunLoop是用于控制线程生命周期并接收事件进行处理的机制,其实质是一个do-While循环。
  • RunLoop与线程是一一对应关系,每个线程(包括主线程)都有一个对应的RunLoop对象;其对应关系保存在一个全局的Dictionary里;
  • 主线程的RunLoop默认由系统自动创建并启动;而其他线程在创建时并没有RunLoop,若该线程一直不主动获取,就一直不会有RunLoop;
  • 苹果不提供直接创建RunLoop的方法;所谓其他线程Runloop的创建其实是发生在第一次获取的时候,系统判断当前线程没有RunLoop就会自动创建;
  • 当前线程结束时,其对应的Runloop也被销毁;

RunLoop和自动释放池的关系

  • 主线程的NSRunLoop在监测到事件响应开启每一次event loop之前,会自动创建一个autorelease pool,并且会在event loop结束的时候执行drain操作,释放其中的对象。

Thread和自动释放池的关系

  • 包括主线程在内的所有线程都维护有它自己的自动释放池的堆栈结构。新的自动释放池被创建的时候,它们会被添加到栈的顶部,而当池子销毁的时候,会从栈移除。对于当前线程来说,Autoreleased对象会被放到栈顶的自动释放池中。当一个线程线程停止,它会自动释放掉与其关联的所有自动释放池。

Runloop和Autorelease

  • App启动后,苹果在主线程RunLoop里注册了两个Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler();
  • 第一个Observer监视的事件是Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()创建自动释放池。order = -2147483647(即32位整数最小值)表示其优先级最高,可以保证创建释放池发生在其他所有回调之前;
  • 第二个Observer监视了两个事件BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop()来释放自动释放池。order = 2147483647(即32位整数的最大值)表示其优先级最低,保证其释放池子发生在其他所有回调之后;
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄漏,开发者也不必显示创建AutoreleasePool了;
    image.png
  • 程序启动到加载完成后,主线程对应的RunLoop会停下来等待用户交互
  • 用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件。
  • RunLoop检测到事件后,就会创建自动释放池;
  • 所有的延迟释放对象都会被添加到这个池子中;
  • 在一次完整的运行循环结束之前,会向池中所有对象发送release消息,然后自动释放池被销毁;

AutoreleasePool子线程上的释放时机

  • 每一个线程都会维护自己的 Autoreleasepool栈,所以子线程虽然默认没有开启RunLoop,但是依然存在AutoreleasePool,在子线程退出的时候会去释放autorelease对象。
  • ARC会根据一些情况进行优化,添加__autoreleasing修饰符,其实这就相当于对需要延时释放的对象调用了autorelease方法。从源码分析的角度来看,如果子线程中没有创建AutoreleasePool ,而一旦产生了Autorelease对象,就会调用autoreleaseNoPage方法自动创建hotpage,并将对象加入到其栈中。所以,一般情况下,子线程中即使我们不手动添加自动释放池,也不会产生内存泄漏。

AutoreleasePool需要手动添加的情况

  • 编写的不是基于UI框架的程序,例如命令行工具;
  • 通过循环方式创建大量临时对象;
  • 使用非Cocoa程序创建的子线程;
- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0; i < 1000000; i++) {
        NSObject *obj = [[NSObject alloc] init];
    }
 }
  • 上述代码中,obj因为离开作用域所以会被加入最近一次创建的自动释放池中,而这个释放池就是主线程上的RunLoop管理的;因为for循环在当前线程没有执行完毕,Runloop也就没有完成当前这一次的迭代,所以导致大量对象被延时释放。释放池中的对象将会在viewDidAppear方法执行前就被销毁。在此情况下,我们就有必要通过手动干预的方式及时释放不需要的对象,减少内存消耗;优化的代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0; i < 1000000; i++) {
        @autoreleasepool{
             NSObject *obj = [[NSObject alloc] init];
        }
    }
 }

你可能感兴趣的:(iOS内存管理)