iOS内存管理

2020-03-31  本文已影响0人  6ffd6634d577
1.weak的实现原理?SideTable的结构是什么样的

weak:其实是runtime全局维护的一个hash表结构,其中的key是所指对象的地址,value是weak的指针数组,weak表示的是弱引用,不会对对象引用计数+1,当引用的对象被释放的时候,其值被自动设置为nil,一般用于解决循环引用的。

weak的实现原理

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak()函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

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

参考这篇文章

2.关联对象的应用?系统如何实现关联对象的
应用:
关联对象实现原理:

系统通过管理一个全局哈希表,通过对象指针地址和传递的固定参数地址来获取关联对象。根据setter传入的参数协议,来管理对象的生命周期。
关联对象的值实际上是通过AssociationsManager对象负责管理的,这个对象里有个AssociationsHashMap静态表,用来存储对象的关联值的,关于AssociationsHashMap存储的数据结构如下:

AssociationsHashMap:
------添加属性对象的指针地址(key):ObjectAssociationMap(value:所有关联值对象)
ObjectAssociationMap:
------关联值的key:关联值的value


image.png

所以:关联对象的值它不是存储在自己的实例对象的结构中,而是维护了一个全局的结构AssociationManager

具体runtime的方法实现请参考这篇文章

3.关联对象的如何进行内存管理的?关联对象如何实现weak属性

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性   

内存管理方面是通过在赋值的时候设置一个policy,根据这个policy的类型对设置的对象进行retain/copy等操作。
当policy为OBJC_ASSOCIATION_ASSIGN的时候,设置的关联值将是以弱引用的方式进行内存管理的。

具体的可以看这篇

4.Autoreleasepool的原理?所使用的的数据结构是什么
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

///而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。
void *
objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

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

AutoreleasePool的是通过AutoreleasePoolPage类实现的

image.png
    magic_t const magic; //用来校验 AutoreleasePoolPage 的结构是否完整;
    id *next; //指向栈顶,也就是最新入栈的autorelease对象的下一个位置;
    pthread_t const thread; //指向当前线程
    AutoreleasePoolPage * const parent; //指向父节点
    AutoreleasePoolPage *child; //指向子节点
    uint32_t const depth; //表示链表的深度,也就是链表节点的个数
    uint32_t hiwat;
AutoreleasePool的释放有如下两种情况:
  1. 一种是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。
  2. 手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool或者@autoreleasepool{}执行完释放
AutoreleasePool 和 RunLoop 有什么联系?

因为在iOS应用启动后会注册两个Observer管理和维护AutoreleasePool

第一个Observer会监听RunLoop的进入,它会回调objc_autoreleasePoolPush()向当前的AutoreleasePoolPage增加一个哨兵对象标志创建自动释放池。这个Observer的order是-2147483647优先级最高,确保发生在所有回调操作之前
第二个Observer会监听RunLoop的进入休眠和即将退出RunLoop两种状态

当runloop即将休眠的时候会把之前的自动释放池释放,然后重新创建一个新的释放池
主线程的其他操作通常均在这个AutoreleasePool之内(main函数中),以尽可能减少内存维护操作(当然你如果需要显式释放【例如循环】时可以自己创建AutoreleasePool否则一般不需要自己创建)。

参考这篇文章

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

参考这篇文章

6.ARC下哪些情况会造成内存泄漏
image.png

有人可能有疑问,为什么都同样是target-action方式button就不会出现循环引用的问题,有去研究的同学应该都知道UIControl的内部做了weak操作,即真正持有的时候是weak的并没有导致retain加1,而NSTimer由于runloop的原因并没有做weak操作。

NSTimer
解决循环引用的方法
  1. invalidate方法
    invalidate方法有2个功能:
    一是将timer从runloop中移除
    二是timer本身也会释放它持有资源,比如target

  2. 引入中间者, 借助runtime给对象添加消息处理的能力

    _target = [[NSObject alloc] init];
    class_addMethod([_target class], @selector(fire), class_getMethodImplementation([self class], @selector(fire)), "v@:");
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(fire) userInfo:nil repeats:YES];
  1. 通过消息转发的方法的方式
    创建一个集成自NSProxy的类PHJProxy 声明一个target
    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>

    @interface PHJProxy : NSProxy
    @property (nonatomic, weak) id target;
    @end

PHJProxy的实现

@implementation PHJProxy
// 发送给target
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
// 给target注册一个方法签名
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

@end

PHJProxy 和 NSTimer的使用

   self.proxy = [PHJProxy alloc];
   self.proxy.target = self;
   self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self.proxy 
   selector:@selector(fire) userInfo:nil repeats:YES];

在iOS中基本上90%的类都是集成NSObject,但是有一个类NSProxy就很特别,它就没有继承NSobject,但是这个类实现了协议
NSProxy是一个虚类,你可以通过继承它,并重写这两个方法以实现消息转发到另一个实例。说白了,NSProxy转为代理而生(负责将消息转发到真正的target的代理类)。从类名来看是代理类,专门负责代理对象转发消息的。相比NSObject类来说NSProxy更轻量级,通过NSProxy可以帮助Objective-C间接的实现多重继承的功能。

- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

思考:设置weak能解决循环引用吗?

不能,runloop是强持有timer的,声明为weak只是vc不持有

日常如何检查内存泄露?

Laek Memory 这种是忘记 Release 操作所泄露的内存。
Abandon Memory 这种是循环引用,无法释放掉的内存。

Memory Leaks

Leaks 工具只负责检测 Leaked Memory,而不管 Abandoned Memory。
MRC 时代 Leaked memory 很常见,因为很容易忘了调用 release,但在 ARC时代更常见的内存泄露是循环引用导致的 Abandoned Memory,Leaks 工具查不出这类内存泄露,应用有限

Alloctions

对于 Abandoned memory,可以用 Instrument 的 Allocations 检测出来。
检测方法:每次点击 Mark Generation 时,Allocations 会生成当前 App 的内存快照,而且 Allocations 会记录从上回内存快照到这次内存快照这个时间段内,新分配的内存信息。

我们可以不断重复 push 和 pop 同一个 UIViewController,理论上来说,push 之前跟 pop 之后,app 会回到相同的状态。因此,在 push 过程中新分配的内存,在 pop 之后应该被 dealloc 掉,除了前几次 push 可能有预热数据和 cache 数据的情况。如果在数次 push 跟 pop 之后,内存还不断增长,则有内存泄露。
用这种方法来发现内存泄露还是很不方便的:
1、首先,你得打开 Allocations
2、其次,你得一个个场景去重复的操作
3、无法及时得知泄露,得专门做一遍上述操作,十分繁琐

Analyse

静态分析工具: 可以通过Product ->Analyze菜单项启动
Analyze主要分析以下四种问题:
1、逻辑错误:访问空指针或未初始化的变量等;
2、内存管理错误:如内存泄漏等;
3、声明错误:从未使用过的变量;
4、API调用错误:未包含使用的库和框架。
这里使用Analyze静态分析查找出来的泄漏点,称之为"可疑泄漏点"。之所以称之为"可疑泄漏点",是因为这些点未必一定泄露,确认这些点是否泄露, 还要通过Instruments动态分析工具的 Leaks和Allocations跟踪模板。 Analyze静态分析只是一个理论上的预测过程.

Debug Memory Graph

可以看看这篇文章

MLeaksFinder

MLeaksFinder 是腾讯WeRead团队开源的一款检测 iOS 内存泄漏的框架,其使用非常简单,只需将文件加入项目中,如果有内存泄漏,3秒后自动弹出 alert 来捕捉循环引用。具有无侵入性、
可构建泄漏堆栈、白名单机制等优点。
目前只检测ViewControllerView对象(可扩展,MLCheck())

总体思路:当一个 ViewController 被 pop 或 dismiss 之后,我们认为该 ViewController,包括它上面的子 ViewController,及它的 View,View 的 subView 等,都很快会被释放,如果某个 View 或者 ViewController 没释放,我们就认为该对象泄漏了。

具体的做法:为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在3秒后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接弹框提醒该对象可能存在内存泄漏。
UIViewController的分类中,使用 Method Swizzling,hook掉了
viewDidDisappear:viewWillAppear:dismissViewControllerAnimated:completion:等方法,让他们都执行willDealloc方法,这样,在不入侵开发代码的情况下,为UIViewController添加了检查内存泄露的功能(AOP

查找循环引用链:
Facebook 开源了一个循环引用检测工具 FBRetainCycleDetector。当传入内存中的任意一个 OC 对象,FBRetainCycleDetector 会递归遍历该对象的所有强引用的对象,以检测以该对象为根结点的强引用树有没有循环引用。
我们知道,很多循环引用是 block 的使用不当造成的。而 FBRetainCycleDetector 最大的技术亮点,正在于如何找出一个 block 的所有强引用对象
当 MLeaksFinder 与 FBRetainCycleDetector 结合使用时,正好能达到很好的效果。我们先通过 MLeaksFinder 找到内存泄漏的对象,然后再过 FBRetainCycleDetector 检测该对象有没有循环引用即可。

wereadteam
参考这篇文章

上一篇 下一篇

猜你喜欢

热点阅读