MJiOS底层笔记--内存管理

栏目: IOS · 发布时间: 5年前

内容简介:本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。CADisplayLink(
MJiOS底层笔记--内存管理

本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。

别的

CADisplayLink与NSTimer

CADisplayLink( 保证调用频率和屏幕的刷帧频率一致,60FPS(60次/s) )、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用

target导致循环引用

如下代码是释放不掉的

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 保证调用频率和屏幕的刷帧频率一致,60FPS
    self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}


- (void)linkTest
{
    NSLog(@"%s", __func__);
}
复制代码

__weak为什么解决target的强引用

block是捕获变量,timer是传递参数。

  1. block在捕获变量时根据变量类型自行进行若引用处理。
  2. timer作为参数传递时,内部接收到的都是对象的地址值,无法获取引用类型。

不过如果是NSTimer的block版本用__weak是可以的

中间代理

MJiOS底层笔记--内存管理
  1. 用代理隔离self与timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
复制代码
  1. 用消息转发将selector发送回self
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
复制代码

其他方式释放timer

比如removeSuperView时之类吧

NSObject与NSProxy

NSProxy专门用来做消息转发

  1. 消息转发速度快

NSProxy在本类没有该方法的情况下会直接进入消息转发( methodSignatureForSelector:) 与 (forwardInvocation: ),并不会去查找父类,动态方法解析等等。

  1. 大部分方法都能正确转发

原生方法如果未主动实现,内部直接进入消息转发。比如class,isKindOfClass等等

GCD定时器

GCD的定时器会更加准时

NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时

而GCD定时器依赖于系统内核,并不依赖Runloop

内存布局

MJiOS底层笔记--内存管理

Tagged Pointer

从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储

  1. 在没有使用Tagged Pointer之前,NSNumber与正常的OC对象一样:

    需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值。

  2. 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中

MJiOS底层笔记--内存管理
  1. 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据

  2. objc_msgSend能识别Tagged

  3. Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销

  4. 如何判断一个指针是否为Tagged Pointer? class

    iOS平台,最高有效位是1(第64bit)

    Mac平台,最低有效位是1(16进制下为7)

    通常来讲,判断最后一位不是0即可

NSLog(@"Person实例的内存地址=%p---指针变量p的内存地址=%p---指针变量p保存的内存地址=%p", p, &p, p);
复制代码

MRC的内存管理

  1. 在iOS中,使用引用计数来管理OC对象的内存
  2. 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
  3. 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1

内存管理的经验总结

  1. 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
  2. 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1

MRC

使用return关键字只会管理setget方法中的内存,dealloc中仍然需要自己释放。

copy和mutableCopy

MJiOS底层笔记--内存管理

引用计数

  1. 在64bit中,引用计数可以直接存储在优化过的isa指针中
  2. 如果引用计数过大,isa中改为1并且将计数存储到SideTable中

SideTable

一个全局table

MJiOS底层笔记--内存管理

refcnts是一个存放着对象引用计数的散列表 weak_table存放着若引用的指针与对象

weak

当一个对象A被若引用指针持有,将会以[&A,weak指针表]的形式添加进SideTable中

当对象A被释放,可以根据&A查找到所有指向他的weak指针并进行释放

- (void)dealloc {
    _objc_rootDealloc(self);
}


_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  //新isa指针
                 !isa.weakly_referenced  &&   //查看该对象是否被若引用了
                 !isa.has_assoc  &&   //关联对象
                 !isa.has_cxx_dtor  &&  //c++析构器
                 !isa.has_sidetable_rc)) //大额引用计数
    {
        assert(!sidetable_present());
        free(this); //直接释放
    } 
    else {
        object_dispose((id)this); 
    }
}


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(); //将指向当前对象的弱指针置位nil
    }

    return obj;
}


objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this]; //获得全局的SideTable
    table.lock();
    if (isa.weakly_referenced) {
        //从表中根据对象地址,释放所有指向他的弱引用指针
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

复制代码

AutoreleasePool

@autoreleasepool {
    for (int i = 0; i < 1000; i++) {
        MJPerson *person = [[[MJPerson alloc] init] autorelease];
    }
}
复制代码

cpp中

{ 
    __AtAutoreleasePool __autoreleasepool; //结构体变量
    MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}


 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
 
    ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
 
    void * atautoreleasepoolobj;
 };
复制代码

所以本质上就等于

atautoreleasepoolobj = objc_autoreleasePoolPush(); //创建释放池

MJPerson *person = [[[MJPerson alloc] init] autorelease];

objc_autoreleasePoolPop(atautoreleasepoolobj); //释放释放池
复制代码

AutoreleasePool的结构

每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址

MJiOS底层笔记--内存管理

push,pop,autorelease

  1. 在调用 objc_autoreleasePoolPush() 时,插入 POLL_BOUNDARY 并返回地址0x1038

  2. 每个调用 autorelease 的对象都会被插入到AutoreleasePoolPage中

  3. 在调用 objc_autoreleasePoolPop(0x1038) 时,从当前位置到 0x1038 所有的对象都会被执行 release 操作。

可以通过以下私有函数来查看自动释放池的情况

extern void _objc_autoreleasePoolPrint(void);
复制代码

AutoreleasePool的维护

  1. 始终有一个被标记 hotPage 的活跃 AutoreleasePoolPage 被系统持有
  2. page 之间通过双向链表链接
  3. 如果 push/autorelease 操作时当前page已满,将会创建一个 page 或跳转到下一个 page

runloop与AutoreleasePool

iOS在主线程的Runloop中注册了2个Observer,监听了三个状态。并适时操作AutoreleasePool

  1. 第1个Observer监听了kCFRunLoopEntry事件

    在进入runloop时,会调用objc_autoreleasePoolPush()

  2. 第2个Observer监听了kCFRunLoopBeforeExit事件

    在退出runloop时,会调用objc_autoreleasePoolPop()

  3. 第2个Observer还监听了kCFRunLoopBeforeWaiting事件

    在当前循环结束,准备休眠时时,会调用objc_autoreleasePoolPop()随后再调用一次objc_autoreleasePoolPush()

autorelease对象

借用群里一位大佬的解释

一般除了init其他基本上都是autorelease的,包括C函数返回对象

也就是说init方法放回的对象,默认是会被 retain/release ,而其他的对象默认会 autorelease

很显然的,二者的释放时机不同,所以才会有如下情况发生。

MJiOS底层笔记--内存管理

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning Google Maps API 3

Beginning Google Maps API 3

Gabriel Svennerberg / Apress / 2010-07-27 / $39.99

This book is about the next generation of the Google Maps API. It will provide the reader with the skills and knowledge necessary to incorporate Google Maps v3 on web pages in both desktop and mobile ......一起来看看 《Beginning Google Maps API 3》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具