内容简介:本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。CADisplayLink(
本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。
推荐系统直接学习小码哥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是传递参数。
- block在捕获变量时根据变量类型自行进行若引用处理。
- timer作为参数传递时,内部接收到的都是对象的地址值,无法获取引用类型。
不过如果是NSTimer的block版本用__weak是可以的
中间代理
- 用代理隔离self与timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES]; 复制代码
- 用消息转发将selector发送回self
- (id)forwardingTargetForSelector:(SEL)aSelector { return self.target; } 复制代码
其他方式释放timer
比如removeSuperView时之类吧
NSObject与NSProxy
NSProxy专门用来做消息转发
- 消息转发速度快
NSProxy在本类没有该方法的情况下会直接进入消息转发( methodSignatureForSelector:) 与 (forwardInvocation:
),并不会去查找父类,动态方法解析等等。
- 大部分方法都能正确转发
原生方法如果未主动实现,内部直接进入消息转发。比如class,isKindOfClass等等
GCD定时器
GCD的定时器会更加准时
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
而GCD定时器依赖于系统内核,并不依赖Runloop
内存布局
Tagged Pointer
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
-
在没有使用Tagged Pointer之前,NSNumber与正常的OC对象一样:
需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值。
-
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
-
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
-
objc_msgSend能识别Tagged
-
Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
-
如何判断一个指针是否为Tagged Pointer? class
iOS平台,最高有效位是1(第64bit)
Mac平台,最低有效位是1(16进制下为7)
通常来讲,判断最后一位不是0即可
NSLog(@"Person实例的内存地址=%p---指针变量p的内存地址=%p---指针变量p保存的内存地址=%p", p, &p, p); 复制代码
MRC的内存管理
- 在iOS中,使用引用计数来管理OC对象的内存
- 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
- 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
内存管理的经验总结
- 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
- 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
MRC
使用return关键字只会管理setget方法中的内存,dealloc中仍然需要自己释放。
copy和mutableCopy
引用计数
- 在64bit中,引用计数可以直接存储在优化过的isa指针中
- 如果引用计数过大,isa中改为1并且将计数存储到SideTable中
SideTable
一个全局table
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对象的地址
push,pop,autorelease
-
在调用
objc_autoreleasePoolPush()
时,插入POLL_BOUNDARY
并返回地址0x1038 -
每个调用
autorelease
的对象都会被插入到AutoreleasePoolPage中 -
在调用
objc_autoreleasePoolPop(0x1038)
时,从当前位置到0x1038
所有的对象都会被执行release
操作。
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void); 复制代码
AutoreleasePool的维护
- 始终有一个被标记
hotPage
的活跃AutoreleasePoolPage
被系统持有 -
page
之间通过双向链表链接 - 如果
push/autorelease
操作时当前page已满,将会创建一个page
或跳转到下一个page
。
runloop与AutoreleasePool
iOS在主线程的Runloop中注册了2个Observer,监听了三个状态。并适时操作AutoreleasePool
-
第1个Observer监听了kCFRunLoopEntry事件
在进入runloop时,会调用objc_autoreleasePoolPush()
-
第2个Observer监听了kCFRunLoopBeforeExit事件
在退出runloop时,会调用objc_autoreleasePoolPop()
-
第2个Observer还监听了kCFRunLoopBeforeWaiting事件
在当前循环结束,准备休眠时时,会调用objc_autoreleasePoolPop()随后再调用一次objc_autoreleasePoolPush()
autorelease对象
借用群里一位大佬的解释
一般除了init其他基本上都是autorelease的,包括C函数返回对象
也就是说init方法放回的对象,默认是会被 retain/release
,而其他的对象默认会 autorelease
。
很显然的,二者的释放时机不同,所以才会有如下情况发生。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 带你深入理解内存对齐最底层原理
- avue 1.5.2 优化大量底层代码,crud 和 form 底层公用
- Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比
- Docker 底层原理浅析
- NSDictionary底层实现原理
- PHP 数组底层实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。