内存管理-定时器循环、内存布局、tagged pointer、weak指针、copy、自动释放池
先上代码,我们平时用的定时器,cadisplaylink、nstimer,CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
{CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(test) userInfo:nil repeats:YES];
}- (void)test{}
解决方案就是,nstimer的时候使用block执行任务,cadisplaylink只能使用代理对象来解决循环引用,使用NSProxy这个类或者nsblock,前者效果更好,或者我们使用gcd的定时器,这是跟内核打交道的,更加精准。
{__weak typeof(self) weakSelf = self;self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {[weakSelf test];}];
}@interface FGTimer : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;@end@implementation FGTimer
+ (instancetype)proxyWithTarget:(id)target{FGTimer *timer = [self alloc];timer.target = self;return timer;
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{[invocation invokeWithTarget:self.target];
}@end
如果是nsobject,他找不到方法的时候会调用消息发送,动态方法解析、消息转发,而nsproxy直接是消息转发,提高笑了,代理对象就是专门做这个的。
iOS程序的内存布局
Tagged Pointer
- 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
- 在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
- objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
- 如何判断一个指针是否为Tagged Pointer? iOS平台,最高有效位是1(第64bit)
这是源码里面判断是否是tagged pointer指针,下面是一道关于tagged pointer有关的面试题,问分别执行代码1和代码2,会发生生么?
// 代码1
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i<1000; i++) {dispatch_async(queue, ^{self.name = [NSString stringWithFormat:@"abcdefghijkl"];});
}// 代码2
dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
for (int i = 0; i<1000; i++) {dispatch_async(queue1, ^{self.name = [NSString stringWithFormat:@"abc"];});
}// set方法底层实现
- (void)setDog:(Dog *)dog
{if (_dog != dog) {[_dog release];_dog = [dog retain];}
}
这两段代码区别就是字符串的长度了,先说会发生生么吧,代码一会crash,坏内存访问: *** -[CFString release]: message sent to deallocated instance 0x600001967020,代码2,正常执行,原因呢,就是我们学的tagged pointer,代码1里面的字符串就不再是常量区了,而是变成一个oc对象,而set方法底部实行是先release前对象,再retain一次,因为线程异步执行,导致release的对象被释放了,又进行一次release,所以发生这个现象,而代码2,保存在常量区,是直接赋值,所以不会发生。
OC对象的内存管理
- 在iOS中,使用引用计数来管理OC对象的内存
- 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
- 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
- 可以通过以下私有函数来查看自动释放池的情况 extern void _objc_autoreleasePoolPrint(void);
copy
面试中,我们经常遇到问,深拷贝,浅拷贝,深拷贝就是内容拷贝,产生了新对象,浅拷贝就是指针拷贝,都指向同一块内存地址
copy |
mutableCopy |
|
NSString |
NSString 浅拷贝 |
NSMutableString 深拷贝 |
NSMutableString |
NSString 深拷贝 |
NSMutableString 深拷贝 |
NSArray |
NSArray 浅拷贝 |
NSMutableArray 深拷贝 |
NSMutableArray |
NSArray 深拷贝 |
NSMutableArray 深拷贝 |
NSDictionary |
NSDictionary 浅拷贝 |
NSMutableDictionary 深拷贝 |
NSMutableDictionary |
NSDictionary 深拷贝 |
NSMutableDictionary 深拷贝 |
dealloc
objc4 源码
- (void)dealloc {_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{assert(obj);obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{if (isTaggedPointer()) return; // fixme necessary?if (fastpath(isa.nonpointer && // 被优化过的指针!isa.weakly_referenced && // 是否弱指针!isa.has_assoc && // 是否有关联对象!isa.has_cxx_dtor && // 是否含有c++的析构函数!isa.has_sidetable_rc)) // 是否内存指针保存不够,存在sidetable中{assert(!sidetable_present());free(this);} else {object_dispose((id)this);}
}//
struct SideTable {spinlock_t slock;RefcountMap refcnts;weak_table_t weak_table;
}NEVER_INLINE void objc_object::clearDeallocating_slow()
{assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));SideTable& table = SideTables()[this];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();
}
在释放对象前,他会先判断,具体写在代码里,然后如果有weak指针,会调用objc_object::clearDeallocating_slow(),会在散列表里面将对应的value设置为nil,这也就是我们weak指针的实现原理,weak修饰的对象会存放在散列表中,地址作为key,value为对象,当对象销毁时,会以地址为key搜索到对应的对象,释放掉,这样就避免僵尸对象。
自动释放池
自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage;
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的,下面是源码里面的结构
每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址,所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
具体使用为
- 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
- 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
void *
objc_autoreleasePoolPush(void)
{return AutoreleasePoolPage::push();
}void
objc_autoreleasePoolPop(void *ctxt)
{AutoreleasePoolPage::pop(ctxt);
}static inline void *push() {id *dest;if (DebugPoolAllocation) {// Each autorelease pool starts on a new pool page.dest = autoreleaseNewPage(POOL_BOUNDARY);} else {dest = autoreleaseFast(POOL_BOUNDARY);}assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;}static inline void pop(void *token) {AutoreleasePoolPage *page;id *stop;if (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Popping the top-level placeholder pool.if (hotPage()) {// Pool was used. Pop its contents normally.// Pool pages remain allocated for re-use as usual.pop(coldPage()->begin());} else {// Pool was never used. Clear the placeholder.setHotPage(nil);}return;}page = pageForPointer(token);stop = (id *)token;if (*stop != POOL_BOUNDARY) {if (stop == page->begin() && !page->parent) {// Start of coldest page may correctly not be POOL_BOUNDARY:// 1. top-level pool is popped, leaving the cold page in place// 2. an object is autoreleased with no pool} else {// Error. For bincompat purposes this is not // fatal in executables built with old SDKs.return badPop(token);}}if (PrintPoolHiwat) printHiwat();page->releaseUntil(stop);// memory: delete empty childrenif (DebugPoolAllocation && page->empty()) {// special case: delete everything during page-per-pool debuggingAutoreleasePoolPage *parent = page->parent;page->kill();setHotPage(parent);} else if (DebugMissingPools && page->empty() && !page->parent) {// special case: delete everything for pop(top) // when debugging missing autorelease poolspage->kill();setHotPage(nil);} else if (page->child) {// hysteresis: keep one empty child if page is more than half fullif (page->lessThanHalfFull()) {page->child->kill();}else if (page->child->child) {page->child->child->kill();}}}最后的结构就是atautoreleasepoolobj = objc_autoreleasePoolPush();FGPerson *person = [[[FGPerson alloc] init] autorelease];objc_autoreleasePoolPop(atautoreleasepoolobj);
那么问问题来了,什么时候去调用呢,也就是说什么时候释放对象呢?这里牵扯到nsrunloop了,我们打印NSLog(@"%@",[NSRunLoop currentRunLoop]);
// 监听0x1 为kCFRunLoopEntry"<CFRunLoopObserver 0x600001f14140 [0x7fff805ed4e0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4715bddc), context = <CFArray 0x600002058c30 [0x7fff805ed4e0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ff238802038>\n)}}",// 0xa0 kCFRunLoopExit + kCFRunLoopBeforeWaiting
"<CFRunLoopObserver 0x600001f141e0 [0x7fff805ed4e0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4715bddc), context = <CFArray 0x600002058c30 [0x7fff805ed4e0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ff238802038>\n)}}"typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0), // 1kCFRunLoopBeforeTimers = (1UL << 1), // 2kCFRunLoopBeforeSources = (1UL << 2), // 4kCFRunLoopBeforeWaiting = (1UL << 5), // 32kCFRunLoopAfterWaiting = (1UL << 6), //64kCFRunLoopExit = (1UL << 7), // 128kCFRunLoopAllActivities = 0x0FFFFFFFU};
iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()。
内存管理-定时器循环、内存布局、tagged pointer、weak指针、copy、自动释放池相关推荐
- 【iOS高级资深工程师面试篇】④、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 内存管理部分2/2 引用计数-弱引用-自动释放池-循环引用
iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...
- iOS内存管理 —— 自动释放池和runloop
iOS内存管理 -- 自动释放池和runloop 1. 自动释放池 1.1 自动释放池介绍 1.2 自动释放池底层原理 objc_autoreleasePoolPush autoreleaseNoPa ...
- C++:内存管理:C++内存管理详解
C++语言内存管理是指:对系统的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果.本文将从系统内存的分配.创建出发,并且结合例子来说明内存管理不当会造成的结果 ...
- 【C 语言必知必会】内存管理、动态分配内存、野指针
C 语言内存管理.动态分配内存.野指针 文章目录 C 语言内存管理.动态分配内存.野指针 前言: 1.内存分区 1.1 代码区 1.2.1 全局初始化数据区(静态数据区data段) 1.2.2 未初始 ...
- 属性与内存管理(属性与内存管理都是相互关联的)
<span style="font-size:18px;"> 属性与内存管理(属性与内存管理都是相互关联的)第一部分一,属性:属性是OC2.0之后出来的新语法,用来取代 ...
- 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )
文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...
- 【深入Cocos2d-x】探索Cocos2d-x中的内存管理-引用计数和自动释放池
2019独角兽企业重金招聘Python工程师标准>>> #深入Cocos2d-x-探索Cocos2d-x中的内存管理-引用计数和自动释放池 ###引用计数(Reference Cou ...
- 第六讲:Obj-C 内存管理4 - 自动释放池
转:http://tigercat1977.blog.163.com/blog/static/2141561122012111294616203/ 第六讲:Obj-C 内存管理4 - 自动释放池 主要 ...
- JVM自动内存管理机制——Java内存区域(下)
一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...
最新文章
- Oracle导入导出dmp文件
- Ubuntu死机解决方法汇总
- Linux之mount命令详解
- 数据库系统概论:第七章 数据库设计
- 计算机基础--认识CPU
- JAVA 从菜鸟成长为大牛的必经之路
- 读书,上学,上名校!!!!!
- mysql延迟注入br,实验3—SQL注入漏洞的攻击与防御(基于时间延迟的盲注)
- C#------引用System.Data.Entity后DbContext依然无法继承解决方法
- 【Vue】—创建组件
- sql2005没有服务器名称
- linux 开机启动项文件夹,linux开机启动项设置
- C# WinForm界面设计教程
- JAVA反射--通过反射对pojo进行UT覆盖率测试
- 怎样高效利用PPT模板网站找到适合自己的PPT模板
- 史上MySQL安装配置教程最细,一步一图解
- JavaScript 常见安全漏洞和自动化检测技术
- 浅谈建站经验之网站建设的流程与步骤
- Yii2 常用操作总结
- easyui combobox 查询传递json对象
热门文章
- python中time()时间的相关问题
- 第三讲:WCF介绍(3)
- tornado 学习笔记15 _ServerRequestAdapter分析
- Swift - 使用网格(UICollectionView)的自定义布局实现复杂页面
- Daily Scrum02 12.07
- 某云数据中心网络解决方案(分享二十一)
- 【问题解决】移动端rem适配的时候会出现打开页面时先缩小(放大)后恢复到正常页面的问题
- vss error reading from file 解决方法
- Adobe Flash player 10 提示:Error#2044:未处理的IOErrorEvent. text=Error#2036:加载未完成 的解决方法
- 在Android中,如何以编程方式在dp中设置边距?