内存管理

233 阅读11分钟

weak的实现原理?SideTable的结构是什么样的

在iOS开发中,我们经常使用weak关键字来避免对象之间的强引用循环,从而避免内存泄漏的问题。weak关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放时自动被设置为nil。下面我们来详细了解一下weak的实现原理。

  1. weak初探

    • weak关键字的使用场景是为了避免强引用循环导致的内存泄漏问题。
    • weak关键字修饰的指针不会增加所引用对象的引用计数,并在引用对象被释放时自动设置为nil。
  2. objc_initWeak方法

    • 在使用weak关键字时,底层库会调用objc_initWeak方法[1]
    • objc_initWeak方法接收两个参数,分别是__weak指针的地址和所引用的对象。
    • 在该方法内部调用了storeWeak方法。
  3. storeWeak方法

    • storeWeak方法是weak实现的核心方法之一。
    • storeWeak方法接收三个模板参数,分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,以及如果被弱引用的对象正在析构,是否应该crash。
    • storeWeak方法维护了oldTable和newTable两个SideTable的实例,分别表示旧的引用弱表和新的弱引用表。
    • 如果weak指针之前指向了一个弱引用,则调用weak_unregister_no_lock方法将旧的weak指针地址移除。
    • 如果weak指针需要指向一个新的引用,则调用weak_register_no_lock方法将新的weak指针地址添加到弱引用表中。
    • 调用setWeaklyReferenced_nolock方法修改新引用对象的bit标志位。
    • 最后,将新的weak指针地址赋值给location。
  4. SideTable结构

    • SideTable是一个结构体,用于存储OC对象的引用计数和弱引用信息。
    • SideTable包含三个成员:
      • spinlock_t slock:自旋锁,用于上锁/解锁SideTable。
      • RefcountMap refcnts:用于存储OC对象的引用计数的hash表。
      • weak_table_t weak_table:用于存储对象弱引用指针的hash表,是weak功能的核心数据结构。

Learn more:

  1. iOS底层原理:weak的实现原理 - 掘金
  2. iOS底层weak的实现原理 - 简书
  3. iOS底层学习--weak实现原理和销毁过程 - 掘金

Autoreleasepool的原理?所使用的的数据结构是什么

Autoreleasepool是Objective-C中的一个机制,用于自动释放对象。它的原理是基于引用计数的自动处理器,通过管理对象的引用计数来控制对象的生命周期。当对象被创建时,引用计数被设为1。可以通过发送retain消息来增加对象的引用计数,而当对象接收到release消息时,引用计数会减1。当引用计数减到0时,对象会调用自己的dealloc方法进行销毁。

Autoreleasepool的数据结构是由多个AutoreleasePoolPage以双向链表的形式组合而成。每个AutoreleasePoolPage对象占用4096字节的内存空间,其中除了一些实例变量外,剩余的空间用来存储autorelease对象的地址。AutoreleasePoolPage对象通过parent和child指针连接成链表,parent指向父节点,child指向子节点。第一个AutoreleasePoolPage的parent指针为nil,最后一个AutoreleasePoolPage的child指针为nil。

当向Autoreleasepool中添加对象时,会将对象的地址存储在当前AutoreleasePoolPage的栈顶位置,通过next指针进行管理。当一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,并将其与链表连接起来,后续的autorelease对象会加入到新的page中。

Autoreleasepool的使用是通过调用objc_autoreleasePoolPush()和objc_autoreleasePoolPop()函数来实现的。这两个函数是对AutoreleasePoolPage的简单封装。在每次调用objc_autoreleasePoolPush()时,会向当前的AutoreleasePoolPage中添加一个哨兵对象,值为0(即nil),并返回这个哨兵对象的地址。而在调用objc_autoreleasePoolPop()时,会根据传入的哨兵对象地址找到哨兵对象所处的AutoreleasePoolPage,并将晚于哨兵对象插入的所有autorelease对象发送一次release消息,并移动next指针到正确的位置。

总结一下Autoreleasepool的原理和使用的数据结构:

  • Autoreleasepool是通过引用计数的自动处理器来管理对象的生命周期。
  • Autoreleasepool的数据结构是由多个AutoreleasePoolPage以双向链表的形式组合而成。
  • Autoreleasepool的使用是通过调用objc_autoreleasePoolPush()和objc_autoreleasePoolPop()函数来实现的,这两个函数是对AutoreleasePoolPage的简单封装。

Learn more:

  1. 从数据结构理解Autoreleasepool 原理 - 掘金
  2. iOS面试题:Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么? - 简书
  3. Autoreleasepool的原理?所使用的的数据结构是什么 - 黄增松 - 博客园

ARC的实现原理?ARC下对retain & release做了哪些优化

ARC(Automatic Reference Counting)是iOS中的一种内存管理技术,它通过自动管理对象的引用计数来实现内存的自动回收。ARC的实现原理是由编译器和运行时库共同完成的。

ARC的实现原理可以从以下几个方面来理解:

  1. 编译器优化:

    • 编译器在编译阶段会根据代码中的引用修饰符(如__strong__weak__unsafe_unretained等)来生成相应的引用计数管理代码。
    • 编译器会在适当的位置插入retainrelease等方法的调用,以确保对象的引用计数正确地增加和减少。
  2. 引用计数管理:

    • 在ARC下,每个对象都有一个引用计数,用于记录有多少个指针指向该对象。
    • 当有新的指针指向对象时,引用计数会自动增加;当指针不再指向对象时,引用计数会自动减少。
    • 当对象的引用计数为0时,对象会被自动释放。
  3. 引用修饰符的优化:

    • ARC对于不同的引用修饰符有不同的优化策略。
    • __strong修饰符是默认的修饰符,它会自动进行引用计数的增加和减少。
    • __weak修饰符用于避免循环引用问题,它不会增加对象的引用计数。
    • __unsafe_unretained修饰符用于声明一个不安全的非强引用,它不会增加对象的引用计数,也不会将指针置为nil。
  4. 运行时库的支持:

    • 运行时库(objc4)提供了一些方法来辅助ARC的实现,如objc_retainobjc_release等。
    • objc_retain方法用于增加对象的引用计数,objc_release方法用于减少对象的引用计数。
    • 运行时库还提供了一些其他的方法来处理特殊情况,如处理弱引用、自动释放池等。

在ARC下,对于retainrelease方法,编译器进行了一些优化,主要包括:

  • 编译器会根据代码的上下文来判断是否需要插入retainrelease方法的调用,以减少不必要的引用计数操作。
  • 编译器会对连续的retainrelease方法进行合并,以减少方法调用的次数。
  • 编译器会对循环引用进行检测,并自动插入适当的代码来解决循环引用问题。

总之,ARC通过编译器的优化和运行时库的支持,实现了自动管理对象的引用计数,从而简化了内存管理的工作。


Learn more:

  1. 理解 ARC 实现原理 - 掘金
  2. iOS面试题:简述ARC 以及 ARC 实现的原理。-腾讯云开发者社区-腾讯云
  3. 理解 ARC 实现原理

ARC下哪些情况会造成内存泄漏

在ARC(Automatic Reference Counting)下,内存泄漏是一个相对严重的问题。虽然ARC可以自动管理Objective-C对象的内存,但仍然存在一些情况会导致内存泄漏。以下是一些可能导致内存泄漏的情况:

  1. 循环引用:循环引用是指两个或多个对象之间相互持有对方的强引用,导致它们无法被释放。在ARC下,循环引用可能发生在以下情况下:

    • Block引起的循环引用:当一个对象将Block作为属性,并且在Block的方法体中又使用了该对象本身时,会导致循环引用。解决方法是使用__weak关键字来避免循环引用[1]
    • 引用大循环:当多个对象之间形成一个大循环引用时,可能会导致内存泄漏。这通常发生在复杂的对象关系中,需要仔细检查并解决循环引用问题[1]
  2. NSTimer的使用:NSTimer会对其target对象持有强引用,如果NSTimer没有被释放,就会一直持有target的强引用,导致内存泄漏。需要注意在使用NSTimer时,及时取消或释放它[1]

  3. 单例模式:如果一个单例对象持有一个block,并且block内部使用了当前这个对象,会导致循环引用。因为单例对象不会被释放,它会一直持有block,从而导致block所在的对象无法释放[1]

  4. performSelector的内存问题:在ARC下,使用performSelector方法可能会导致内存泄漏。由于编译器无法确定performSelector的返回值类型和内存管理规则,可能导致返回的对象无法正确释放。如果使用了performSelector方法并且有返回值,需要手动处理返回值的内存释放[1]

  5. 其他情况:除了上述情况外,还有一些其他可能导致内存泄漏的情况,如使用Core Foundation对象时需要手动释放内存、未及时取消延时操作等。在开发过程中,需要注意这些潜在的内存泄漏问题,并采取相应的解决措施[1]

综上所述,虽然ARC可以自动管理Objective-C对象的内存,但仍然需要开发者注意一些特殊情况,以避免内存泄漏的发生。


Learn more:

  1. 编码篇-ARC下的内存泄漏 - 简书
  2. 编码篇-ARC下的内存泄漏-腾讯云开发者社区-腾讯云
  3. ARC下内存泄露问题_addtarget传self进类方法导致内存泄露-CSDN博客

Method Swizzle注意事项

方法交换(Method Swizzle)是在Objective-C运行时中的一种特性,它允许我们在运行时修改类的方法实现。使用方法交换可以实现一些特定需求,但是在使用方法交换时需要注意以下几点:

  1. 避免交换父类方法:如果当前类未实现被交换的方法而父类实现了,交换时会导致父类的实现被交换。这可能会导致方法被交换多次而混乱,并且调用父类的方法时会发生崩溃。在交换前应先尝试为当前类添加被交换方法的新实现,如果添加成功则说明类没有实现被交换的方法,只需要替代分类交换方法的实现为原方法的实现;如果添加失败,则原类中实现了被交换的方法,可以直接进行交换[1].

  2. 交换方法应在+load方法中进行:方法交换应在调用前完成交换,+load方法在类被加载时调用,且每个类只会调用一次+load方法。调用顺序是父类、类、分类,它们之间是相互独立的,不会存在覆盖的关系。因此,将方法交换放在+load方法中可以确保在使用时已经完成交换[1].

  3. 交换方法应放到dispatch_once中执行:虽然+load方法在类被加载时只会调用一次,但为了防止手动调用+load方法而导致反复交换,应将方法交换放到dispatch_once中执行[1].

  4. 交换的分类方法应添加自定义前缀,避免冲突:为了避免分类的方法覆盖类中同名的方法,应该给交换的分类方法添加自定义前缀,以避免命名冲突[1].

  5. 交换的分类方法应调用原实现:为了保证交换的方法逻辑的完整性,应该在交换的分类方法中调用原被交换方法。注意,在调用时应该调用分类方法本身才能保证逻辑正确[1].

综上所述,使用方法交换时应注意以上几点,可以将这些注意事项封装到NSObject的分类中,方便在调用时使用[1].


Learn more:

  1. method swizzling注意点 - 掘金
  2. iOS底层原理:Method Swizzling原理和注意事项(二)-阿里云开发者社区
  3. Objc 黑科技 - Method Swizzle 的一些注意事项 - 掘金