先上代码,我们平时用的定时器,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、自动释放池相关推荐

  1. 【iOS高级资深工程师面试篇】④、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 内存管理部分2/2 引用计数-弱引用-自动释放池-循环引用

    iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...

  2. iOS内存管理 —— 自动释放池和runloop

    iOS内存管理 -- 自动释放池和runloop 1. 自动释放池 1.1 自动释放池介绍 1.2 自动释放池底层原理 objc_autoreleasePoolPush autoreleaseNoPa ...

  3. C++:内存管理:C++内存管理详解

    C++语言内存管理是指:对系统的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果.本文将从系统内存的分配.创建出发,并且结合例子来说明内存管理不当会造成的结果 ...

  4. 【C 语言必知必会】内存管理、动态分配内存、野指针

    C 语言内存管理.动态分配内存.野指针 文章目录 C 语言内存管理.动态分配内存.野指针 前言: 1.内存分区 1.1 代码区 1.2.1 全局初始化数据区(静态数据区data段) 1.2.2 未初始 ...

  5. 属性与内存管理(属性与内存管理都是相互关联的)

    <span style="font-size:18px;"> 属性与内存管理(属性与内存管理都是相互关联的)第一部分一,属性:属性是OC2.0之后出来的新语法,用来取代 ...

  6. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  7. 【深入Cocos2d-x】探索Cocos2d-x中的内存管理-引用计数和自动释放池

    2019独角兽企业重金招聘Python工程师标准>>> #深入Cocos2d-x-探索Cocos2d-x中的内存管理-引用计数和自动释放池 ###引用计数(Reference Cou ...

  8. 第六讲:Obj-C 内存管理4 - 自动释放池

    转:http://tigercat1977.blog.163.com/blog/static/2141561122012111294616203/ 第六讲:Obj-C 内存管理4 - 自动释放池 主要 ...

  9. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

最新文章

  1. Oracle导入导出dmp文件
  2. Ubuntu死机解决方法汇总
  3. Linux之mount命令详解
  4. 数据库系统概论:第七章 数据库设计
  5. 计算机基础--认识CPU
  6. JAVA 从菜鸟成长为大牛的必经之路
  7. 读书,上学,上名校!!!!!
  8. mysql延迟注入br,实验3—SQL注入漏洞的攻击与防御(基于时间延迟的盲注)
  9. C#------引用System.Data.Entity后DbContext依然无法继承解决方法
  10. 【Vue】—创建组件
  11. sql2005没有服务器名称
  12. linux 开机启动项文件夹,linux开机启动项设置
  13. C# WinForm界面设计教程
  14. JAVA反射--通过反射对pojo进行UT覆盖率测试
  15. 怎样高效利用PPT模板网站找到适合自己的PPT模板
  16. 史上MySQL安装配置教程最细,一步一图解
  17. JavaScript 常见安全漏洞和自动化检测技术
  18. 浅谈建站经验之网站建设的流程与步骤
  19. Yii2 常用操作总结
  20. easyui combobox 查询传递json对象

热门文章

  1. python中time()时间的相关问题
  2. 第三讲:WCF介绍(3)
  3. tornado 学习笔记15 _ServerRequestAdapter分析
  4. Swift - 使用网格(UICollectionView)的自定义布局实现复杂页面
  5. Daily Scrum02 12.07
  6. 某云数据中心网络解决方案(分享二十一)
  7. 【问题解决】移动端rem适配的时候会出现打开页面时先缩小(放大)后恢复到正常页面的问题
  8. vss error reading from file 解决方法
  9. Adobe Flash player 10 提示:Error#2044:未处理的IOErrorEvent. text=Error#2036:加载未完成 的解决方法
  10. 在Android中,如何以编程方式在dp中设置边距?