arc ios 打印引用计数
2017-03-08 17:02:00 weixin_34122810 阅读数 18

引用计数基本概念

当创建一个新对象的时候,引用计数加1,当某个指针不再指向这个对象时,引用计数减1,当对象的引用计数为0时,系统自动将对象销毁,回收内存。


引用《Pro Multithreading and Memory Management for iOS and OS X》中的图片

我们为什么需要引用计数

ARC模式下,在同一个函数内,我们通常不需要手动处理引用计数,此时的使用场景比较简单。引用计数真正发挥作用的场景在面向对象的程序设计架构中,用于对象之间的传递和共享数据。

例如:对象A生成对象M,调用对象B的一个方法将M作为参数传递。根据(谁申请谁释放)原则,应该由A在B不需要M的时候来销毁它。
那么问题来了,B何时才不需要M是不确定的。

Method 1:非常暴力的在A调用完对象B之后立刻销毁对象M,那么对象B就很懵逼的需要将参数另外复制一份M2,自己来管理对象M2的生命周期。
这样有一个很大的问题:大量的内存申请,复制,释放操作,实在太影响性能。
Method 2:使用引用计数,哪些对象需要长时间使用M,就把它的引用计数加1,用完之后再把引用计数减1,所有对象都遵循这个原则,生命周期管理的重任就交给引用计数了。

不要向已经释放的对象发送消息
手动release一个对象,打印其引用计数为什么不是0?

理论上说,一个被release的对象的引用计数值是不确定的,而且如果该对象所占有的内存已经被复用,那么打印其引用计数值程序可能会异常崩溃。

当系统确定一个对象要被release后,就没有必要对它进行引用计数减1了。因为肯定会被回收,回收后,它所拥有的内存区域,包括retainCount值都变得没有意义。
不进行减1可以减少内存操作,加速对象回收。

Perfect But 问题又来了
reference cycles 循环引用

这也是ARC的一个痛点,对象A与对象B如果互相持有,就会造成循环引用,即使没有任何指针访问它们,也不会被释放。这样的情况也多发生在多个对象互相持有,block的使用 NSTimer等。
Method 1:明确会造成循环引用的位置,主动断开其中一环。
Method 2:使用weak reference(弱引用)
持有对象,但不增加引用计数,避免循环应用的产生。比如delegate通常是弱引用的。

关于block中的循环引用

在block中的循环引用一般会被编译器所提醒,但并不是使用到self才会造成循环引用,只要用到self所拥有的东西都会。需要使用其它指针来避免block对自身强引用,如下:

__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下强引用,避免weakSelf被释放掉
NSLog(@"%@", strongSelf->_xxView); //不会导致循环引用.
};

这是针对ARC的方式,MRC下要用__block

通常情况下,GCD 中的block并不会出现针对self循环引用的情况,因为self并没有对GCD中的block进行持有。

参考资料

1.《iOS 开发进阶》 唐巧
2.iOS中block的循环引用问题 - 简书

2018-09-12 15:35:00 weixin_33725722 阅读数 16

iOS ARC中引用计数的实现
iOS weak 的实现
ARC中的数据结构以及寻址方式

一、 alloc
  1. alloc---->_objc_rootAlloc---->callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
/*1*/     id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
/*2*/     return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

// 实际对象创建函数
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
/*1*/   obj = (id)calloc(1, size);
/*2*/   if (!obj) return nil;
/*3*/   obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
  1. if (fastpath(!cls->ISA()->hasCustomAWZ())) 判断有没有自定义的alloc方法,默认没有所以if条件为真, canAllocFast方法固定返回false,所以会执行 class_createInstance 方法
  2. class_createInstance---->_class_createInstanceFromZone
  3. _class_createInstanceFromZone中调用calloc分配空间
  4. initInstanceIsa--->initIsa 为对象结构体中的 isa 赋值
  5. alloc结束 总得来说就是 分配空间、为isa赋值。init默认返回自身 没有额外操作
  6. alloc不会使引用计数+1 此时isa中的引用计数为0,retainCount方法在返回值上+1,所以创建完对象引用计数就会变成1。
...
//  isa 赋值
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;

二、 retain
  1. retain---->rootRetain----->rootRetain(bool tryRetain, bool handleOverflow)
// 由retain调用至此的两个参数 为false   只保留部分关键代码
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
// TaggedPointer 是苹果针对64架构的优化,不使用引用计数,所以直接返回本身
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;

        ...

        uintptr_t carry;
/*1*/   newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            // 第一次调用此法时handleOverflow为false,所以溢出时 调用 rootRetain_overflow 方法内部重新调用 rootRetain,此时handleOverflow为true
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
/*2*/       sideTableLocked = true;
/*3*/       transcribeToSideTable = true;
/*4*/       newisa.extra_rc = RC_HALF;
/*5*/       newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
/*6*/   sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
  1. addc(newisa.bits, RC_ONE, 0, &carry) 对 isa中保留的引用计数extra_rc +1
  2. 如果在上一步操作中 引用计数溢出(64位操作系统中,引用计数占8位),则将extra_rc设置为RC_HALF extra_rc 总共为 8 位,RC_HALF = 0b10000000 代表一半的引用计数,设置has_sidetable_rc为true(代表有额外的引用计数保存在SiteTables里面), 然后调用sidetable_addExtraRC_nolock(RC_HALF)方法,在SiteTables中保存一条记录,将多余的一半引用计数保存进去
  3. 总结:此方法分为两种情况
    未溢出:则在extra_rc的基础上进行+1
    溢出:总大小为8位,所以将一半的引用计数RC_HALF,保存到extra_rc中,一半的引用计数保存到SideTables
  4. SideTables保存对象额外的引用计数和弱引用,后面在单独分析

二、 release
  1. release---->rootRelease----->rootRelease(bool performDealloc, bool handleUnderflow)
正常情况的 release:
//  rootRelease(true, false)    performDealloc = true   handleUnderflow = false

bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
    isa_t oldisa;
    isa_t newisa;

    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;

        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
    } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));

    return false;
}
  1. LoadExclusive 获取 isa
  2. subc 对 extra_rc -1
  3. StoreReleaseExclusive 保存最新的 isa.bits
发生 underflow 的 release
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
    isa_t oldisa;
    isa_t newisa;

    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;

        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
        if (carry) goto underflow;
    } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));
    
    ...

 underflow:
    newisa = oldisa;

    if (newisa.has_sidetable_rc) {
        if (!handleUnderflow) {
            return rootRelease_underflow(performDealloc);
        }

        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        if (borrowed > 0) {
            newisa.extra_rc = borrowed - 1;
            bool stored = StoreExclusive(&isa.bits, oldisa.bits, newisa.bits);

            return false;
        } 
    }
}
  1. 在正常 release 的基础下,如果发生underflow,则调用goto underflow
  2. 判断 newisa.has_sidetable_rc 是否保存有引用计数在 SideTables
  3. 从SideTables中取出 RC_HALF个引用计数在 -1 之后赋值给isa.bits
  4. 后续根据stored判断是否赋值成功,如果没有成功在进行补救操作或者回归操作
release 调用dealloc
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;

        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
        if (carry) goto underflow;
    } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));

    ...

 underflow:
    newisa = oldisa;

    if (newisa.deallocating) {
        return overrelease_error();
    }
    newisa.deallocating = true;
    StoreExclusive(&isa.bits, oldisa.bits, newisa.bits);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}
  1. 如果没有SideTables存储引用计数,并且发生underflow,则说明对象已经没有引用,需要被释放
  2. 直接调用 objc_msgSend 向当前对象发送 dealloc 消息。
  3. 利用isa中的deallocating标志位,保证dealloc只会被调用一次

二、 retainCount
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();
}
  1. retainCount = 1 + bits.extra_rc + sidetable_getExtraRC_nolock
  2. 引用计数 = 1 + isa中存储的数量 + SideTables中存储的引用计数

二、 Dealloc
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

当引用计数为0时,系统会自动调用dealloc方法来释放资源

  1. 如果对象没有弱引用、没有关联对象、没有c++析构函数、没有额外的引用计数存在在SideTables中,则直接调用free函数释放空间
  2. 否则调用object_dispose方法
static id 
_object_dispose(id anObject) 
{
    if (anObject==nil) return nil;

    objc_destructInstance(anObject);
    
    anObject->initIsa(_objc_getFreedObjectClass ()); 

    free(anObject);
    return nil;
}

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);
        obj->clearDeallocating();
    }

    return obj;
}
  1. 调用objc_destructInstance, 调用c++析构函数,移除关联对象,移除SideTables表中相关联的所有数据
  2. 将isa存储的类指针设为nil
  3. 调用free函数释放空间

总得来说,对象从创建到销毁总共经历以下几个阶段。
1️⃣ alloc,分配空间,并且初始化isa,init返回自身,此时retainCount默认为1
2️⃣ retain, 增加对象引用计数,增加isa中的bits.extra_rc,如果溢出,则保存到SideTables中
3️⃣ release,减少对象引用计数,减少isa中的bits.extra_rc,如果溢出,则取出SideTables中的引用计数,如果SideTables中没有存储,则对象释放,调用 dealloc方法
4️⃣ dealloc,释放对象,移除对象存储的相关信息,包括关联对象、引用计数、弱引用表。

2017-06-30 16:27:00 weixin_33711641 阅读数 38

1.了解ARC

  • 自动引用计数(Automatic Refrence Counting)是Objective-C默认的内存管理机制,其针对堆上的对象,由编译器自动生成操作引用计数的指令(retain或者release),来管理对象的创建与释放。
  • 哪些对象受ARC管理:
    • OC对象指针;
    • Block指针;
    • 使用__attribute__((NSObject)) 定义的typedef
  • 哪些对象不受ARC管理:
    • 值类型(简单值类型,C语言struct);
    • 使用其它方式分配的堆对象(如使用malloc分配方式);
    • 非内存资源。

2.引用计数管理

  • 新创建(使用alloc,new,copy等)一个引用类型对象,引用计数为1;

  • 对象引用计数加1————retain操作:

    • 将对象引用赋值给其他变量或者常量;
    • 将对象引用赋值给其他属性或者实例变量;
    • 将对象传递给函数参数,或者返回值;
    • 将对象加入集合中;
  • 对象引用计数减1————release操作;

    • 将局部变量或者全局变量赋值为nil或者其他值;
    • 将属性赋值为nil或者其他值;
    • 实例属性所在对象被释放;
    • 参数或者局部变量离开函数;
    • 将对象从集合中删除
  • 引用计数变为0时,内存自动被释放

3.代码演示

Github 传送门
1)main.m文件

#import <Foundation/Foundation.h>
#import "ARCLibrary.h"

OBJC_EXTERN int _objc_rootRetainCount(id);

void arcDemo();

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, world!");
        arcDemo();
    }
    return 0;
}

void printLog(NSObject *obj) {
    NSLog(@"%@'s ARC count = %d",[obj className] ,_objc_rootRetainCount(obj));
}

void arcDemo() {
    NSLog(@"ARC Demo Begin here ============");
    ARCLibrary *library = [[ARCLibrary alloc] init];

/*
 *  对象引用计数加1————retain操作:
 */
    //1.新创建   retainCount = 1
    __block ARCBook *book1 = [[ARCBook alloc] init];
    NSLog(@"Step1 book1's ARC count = %ld",CFGetRetainCount((__bridge CFTypeRef)(book1)));
    
    //2.赋值属性  retainCount = 2
    ARCBook *book2 = book1;
    NSLog(@"Step2 book1's ARC count = %d",_objc_rootRetainCount(book1));
    
    //3.赋值实例变量    retainCount = 3
    library.book = book1;
    NSLog(@"Step3 book1's ARC count = %d",_objc_rootRetainCount(book1));
    
    //4.传递给函数参数   retainCount = 4; 离开函数 retainCount = 3;
    printLog(book1);
    NSLog(@"Step4 book1's ARC count = %d",_objc_rootRetainCount(book1));
    
    //5.加入数组中   retainCount = 4
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:book1];
    NSLog(@"Step5 book1's ARC count = %d",_objc_rootRetainCount(book1));
    
/*
 * 对象引用计数减1————release操作;
 */
    
    //1.将对象从集合中删除 retainCount = 3
    [array removeObject:book1];
    NSLog(@"Step6 book1's ARC count = %d",_objc_rootRetainCount(book1));

    //2.实例属性赋值为nil retainCount = 2
    library.book = nil;
    NSLog(@"Step7 book1's ARC count = %d",_objc_rootRetainCount(book1));
    
    //3.属性赋值为nil retainCount = 1
    book2 = nil;
    NSLog(@"Step8 book1's ARC count = %d",_objc_rootRetainCount(book1));

    //4.属性赋值为nil retainCount = 0
    //如果把下面这行代码注释掉,则ARCBook的dealloc方法中打印的信息将会在”ARC demo finished here!“完成之后显示
    book1 = nil;
    
    NSLog(@"ARC demo finished here!============");
}

2)控制台打印的信息


2409226-3b49a280644619f9.png
ARC Demo1.png
2409226-0b177f3b6f74b675.png
ARC Demo2.png
2016-06-26 10:49:17 iuyo89007 阅读数 8329

注意:以下方法只可用于debug,而且在多线程等情况下返回值不是100%可信。

1.使用KVC

[obj valueForKey:@"retainCount"]

2.使用私有API

OBJC_EXTERN int _objc_rootRetainCount(id);
_objc_rootRetainCount(obj)

3.使用CFGetRetainCount

CFGetRetainCount((__bridge CFTypeRef)(obj))


没有更多推荐了,返回首页