Objective-C之isa引用计数存储验证

本文从2018年12月15日周末开始,至2018年末,历经3个周末,6天时间。

引用计数原理?

- 引用计数存储在isa中和sidetable中。
- 弱引用变量的数量存储在sidetable中。

先来看arm64架构下isa的定义

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
        //isa变量是否为非指针
        uintptr_t nonpointer        : 1;
        //是否有关联对象
        uintptr_t has_assoc         : 1;
        //是否有析构函数
        uintptr_t has_cxx_dtor      : 1;
        //当前对象对应的类指针右移3位后的地址
        uintptr_t shiftcls          : 33;
        //用于在调试时分辨对象是否未完成初始化
        uintptr_t magic             : 6;
        //是否有弱引用变量指向本实例
        uintptr_t weakly_referenced : 1;
        //是否正在释放
        uintptr_t deallocating      : 1;
        //是否需要散列表存储引用计数。当extra_rc存储不下时,该值为1
        uintptr_t has_sidetable_rc  : 1;
        //引用计数数量,实际的引用计数减一
        uintptr_t extra_rc          : 19;
    };
};

再看SideTable结构

struct SideTable {
    spinlock_t slock;                
    RefcountMap refcnts;       //引用计数
    weak_table_t weak_table; //弱引用表

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

验证isa存储的引用计数

建一个iOS项目,新建一个空类Person继承NSObject.
看测试代码

//数值转二进制字符串
NSString* binaryFromInteger(NSUInteger integer){
    
    NSString *ret = @"";
    NSUInteger x = integer;
    while ( x > 0 ) {
        // 若i=14,即二进制1110,则i&1为 1110 & 0001 结果为0000
        ret = [[NSString stringWithFormat:@"%ld",x&1] stringByAppendingString:ret];
        x = x >> 1;
    }
    return ret;
}

//不直接传递NSObject对象,是为了防止引用计数+1
void printIsaValueWithObject(struct objc_object* obj){
    
    NSObject *oj = (__bridge NSObject*)obj;
    Class cls = [oj valueForKey:@"isa"];

    //isa 的值,二进制形式
    NSString *bin =
    binaryFromInteger((NSUInteger)(cls));
    NSLog(@"isa:%@",bin);
    
    //obj的引用计数
    NSNumber *retainCount = [oj valueForKey:@"retainCount"];
    NSLog(@"retainCount:%@",retainCount.stringValue);
}

void test(){
        XWPerson *per = [XWPerson new];
        printIsaValueWithObject(per);       
        
        NSMutableArray *arr1 = [NSMutableArray new];   //①
        for( NSUInteger i=0; i<524286; i++ ){
            XWPerson *per1 = per;
            [arr1 addObject:per1];
            
            if( i==0 ){
                printIsaValueWithObject(per);   //②
            }
        }
        
        XWPerson *per2 = per;
        printIsaValueWithObject(per);   
        XWPerson *per3 = per;               //③
        printIsaValueWithObject(per);   
        
        NSLog(@"%@",per);   //④
}

分别在①②③④处打断点,然后打印per所指内存中的二进制值,前8个字节即为per实例的isa的值。按照isa_t的结构,解析isa。
运行结果:

isa:11010000100000001000110111101110001100001
retainCount:1
(lldb) x per 
0x2826c4170: 61 dc 1b 01 a1 01 00 00 00 00 00 00 00 00 00 00  a...............
isa:10000011010000100000001000110111101110001100001
retainCount:3
(lldb) x per 
0x2826c4170: 61 dc 1b 01 a1 41 00 00 00 00 00 00 00 00 00 00  a....A..........
isa:1111111111111111111000011010000100000001000110111101110001100001
retainCount:524288
(lldb) x per 
0x2826c4170: 61 dc 1b 01 a1 e1 ff ff 00 00 00 00 00 00 00 00  a...............
isa:1000000000000000000100011010000100000001000110111101110001100001
retainCount:524289
(lldb) x per 
0x2826c4170: 61 dc 1b 01 a1 11 00 80 00 00 00 00 00 00 00 00  a...............

根据isa的结构体定义,可知其长度为64位,引用计数占19位,所以打印出的isa二进制的高19位,即为引用计数的值减一。分别看下引用计数为1和3时,isa的值。

引用计数为1时,isa内存中的值61 dc 1b 01 a1 01 00 00
对应的二级制:11010000100000001000110111101110001100001
补全64位:
0000 0000 0000 0000 0000 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
前19位,为引用计数的值-1. 那么看一下前19位的值:
0000 0000 0000 0000 000 结果为0,刚好符合引用计数减一.
引用计数为3时,isa内存中的值61 dc 1b 01 a1 41 00 00
对应的二进制:
0000 0000 0000 0000 0100 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
前19位,值为 2. 符合引用计数减一.
在这两种情况下,isa的对应的第20位的值都为0,表示未使用sidetable

既然isa中引用计数值占19位,那么当19位都占满时和占满之后isa值的情况如何呢?下面来看下这两种情况。

#######当isa的前19位都为1时,isa指针的值:61 dc 1b 01 a1 e1 ff ff
对应的二进制:
1111 1111 1111 1111 1110 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
此时引用计数为2的19次方加1,也就是524288.
注意此时的isa的第20位,依旧为0.
接下来,我们再增加一个对per对象的引用。则引用计数为524289.
此时,isa的值为:61 dc 1b 01 a1 11 00 80
对应的二进制:
1000 0000 0000 0000 0001 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
此时isa的高19位的值为 1000 0000 0000 0000 000
第20位值为1.说明启用了SideTabel存储引用计数。
如果,我们再次增加引用计数的话,isa的高19位的值依然会增加。
可以得出结论:当isa的存储引用计数的19位占满之后,其最高位变为1,其他位变0. 若继续增加引用计数,这19位会依旧继续增加值。

注意问题

1.只有自定义的继承NSObject类,对应实例的isa,才能查看到其存储的引用计数。
2.使用的时候,要用真机调试。模拟器和真机有区别。

思考问题?

1.strong、retain如何增加引用计数?
2.weak 实现原理?
3.assign 与weak 区别?
4.引用计数为0的对象,何时释放?
5.autorelasepool原理以及何时释放?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容