当前位置: 代码网 > it编程>App开发>苹果IOS > iOS内存管理引用计数示例分析

iOS内存管理引用计数示例分析

2024年05月18日 苹果IOS 我要评论
内存管理机制目前流行的内存管理机制主要有gc和rc两种。gc (garbage collection):垃圾回收机制,定期查找不再使用的对象,释放对象占用的内存。rc (reference count

内存管理机制

目前流行的内存管理机制主要有gcrc两种。

  • gc (garbage collection):垃圾回收机制,定期查找不再使用的对象,释放对象占用的内存。
  • rc (reference counting):引用计数机制。采用引用计数来管理对象的内存,当需要持有一个对象时,使它的引用计数 +1;当不需要持有一个对象的时候,使它的引用计数 -1;当一个对象的引用计数为 0,该对象就会被销毁。

objective-c支持三种内存管理机制:arcmrcgc,但objective-cgc机制有平台局限性,仅限于macos开发中,ios开发用的是rc机制,从mrc到现在的arc

一个新创建的oc对象引用计数默认是1,当引用计数减为0,oc对象就会销毁,释放其占用的内存空间

调用retain会让oc对象的引用计数+1,调用release会让oc对象的引用计数-1

内存管理的经验总结

  • 当调用allocnewcopymutablecopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
  • 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
  • 可以通过以下私有函数来查看自动释放池的情况

extern void _objc_autoreleasepoolprint(void);

以上我们对 “引用计数” 这一概念做了初步了解,objective-c 中的 “对象” 通过引用计数功能来管理它的内存生命周期。那么,对象的引用计数是如何存储的呢?它存储在哪个数据结构里?

首先,不得不提一下isa

isa

  • isa指针用来维护 “对象” 和 “类” 之间的关系,并确保对象和类能够通过isa指针找到对应的方法、实例变量、属性、协议等;
  • 在 arm64 架构之前,isa就是一个普通的指针,直接指向objc_class,存储着classmeta-class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;
  • 从 arm64 架构开始,对isa进行了优化,用nonpointer表示,变成了一个共用体(union)结构,还使用位域来存储更多的信息。将 64 位的内存数据分开来存储着很多的东西,其中的 33 位才是拿来存储classmeta-class对象的内存地址信息。要通过位运算将isa的值& isa_mask掩码,才能得到classmeta-class对象的内存地址。
// objc.h
struct objc_object {
    class isa;  // 在 arm64 架构之前
};
// objc-private.h
struct objc_object {
private:
    isa_t isa;  // 在 arm64 架构开始
};
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    class cls;
    uintptr_t bits;
#if support_packed_isa
    // extra_rc must be the msb-most field (so it matches carry/overflow flags)
    // nonpointer must be the lsb (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + rc_one is equivalent to extra_rc + 1
    // rc_half is the high bit of extra_rc (i.e. half of its range)
    // future expansion:
    // uintptr_t fast_rr : 1;     // no r/r overrides
    // uintptr_t lock : 2;        // lock for atomic property, @synch
    // uintptr_t extrabytes : 1;  // allocated with extra bytes
# if __arm64__  // 在 __arm64__ 架构下
#   define isa_mask        0x0000000ffffffff8ull  // 用来取出 class、meta-class 对象的内存地址
#   define isa_magic_mask  0x000003f000000001ull
#   define isa_magic_value 0x000001a000000001ull
    struct {
        uintptr_t nonpointer        : 1;  // 0:代表普通的指针,存储着 class、meta-class 对象的内存地址
                                          // 1:代表优化过,使用位域存储更多的信息
        uintptr_t has_assoc         : 1;  // 是否有设置过关联对象,如果没有,释放时会更快
        uintptr_t has_cxx_dtor      : 1;  // 是否有c++的析构函数(.cxx_destruct),如果没有,释放时会更快
        uintptr_t shiftcls          : 33; // 存储着 class、meta-class 对象的内存地址信息
        uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
        uintptr_t weakly_referenced : 1;  // 是否有被弱引用指向过,如果没有,释放时会更快
        uintptr_t deallocating      : 1;  // 对象是否正在释放
        uintptr_t has_sidetable_rc  : 1;  // 如果为1,代表引用计数过大无法存储在 isa 中,那么超出的引用计数会存储在一个叫 sidetable 结构体的 refcountmap(引用计数表)散列表中
        uintptr_t extra_rc          : 19; // 里面存储的值是对象本身之外的引用计数的数量,retaincount - 1
#       define rc_one   (1ull<<45)
#       define rc_half  (1ull<<18)
    };
......  // 在 __x86_64__ 架构下
};

如果isanonpointer,即 arm64 架构之前的isa指针。由于它只是一个普通的指针,存储着classmeta-class对象的内存地址,所以它本身不能存储引用计数,所以以前对象的引用计数都存储在一个叫sidetable结构体的refcountmap(引用计数表)散列表中。

如果isanonpointer,则它本身可以存储一些引用计数。从以上union isa_t的定义中我们可以得知,isa_t中存储了两个引用计数相关的东西:extra_rchas_sidetable_rc

  • extra_rc:里面存储的值是对象本身之外的引用计数的数量,这 19 位如果不够存储,has_sidetable_rc的值就会变为 1;
  • has_sidetable_rc:如果为 1,代表引用计数过大无法存储在isa中,那么超出的引用计数会存储sidetablerefcountmap中。

所以,如果isanonpointer,则对象的引用计数存储在它的isa_textra_rc中以及sidetablerefcountmap中。

sidetable

// nsobject.mm
struct sidetable {
    spinlock_t slock;        // 自旋锁
    refcountmap refcnts;     // 引用计数表(散列表)
    weak_table_t weak_table; // 弱引用表(散列表)
    ......
}

sidetable存储在sidetables()中,sidetables()本质也是一个散列表,可以通过对象指针来获取它对应的(引用计数表或者弱引用表)在哪一个sidetable中。在非嵌入式系统下,sidetables()中有 64 个sidetable。以下是sidetables()的定义:

// nsobject.mm
static objc::explicitinit<stripedmap<sidetable>> sidetablesmap;
static stripedmap<sidetable>& sidetables() {
    return sidetablesmap.get();
}

所以,查找对象的引用计数表需要经过两次哈希查找:

  • ① 第一次根据当前对象的内存地址,经过哈希查找从sidetables()中取出它所在的sidetable
  • ② 第二次根据当前对象的内存地址,经过哈希查找从sidetable中的refcnts中取出它的引用计数表。

使用多个sidetable+分离锁技术方案是为了保证线程安全的同时兼顾访问效率

以上就是ios内存管理引用计数示例分析的详细内容,更多关于ios内存管理引用计数的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com