(转)谈weak对象、对象缓存以及Tagged Pointer

简介: (转)谈weak对象、对象缓存以及Tagged Pointer

1.weak对象什么时候释放

2.系统对象的缓存

3.Tagged Pointer对象

__weak NSArray *arr = [NSArray new];这么声明一个弱引用变量,常理来说arr应该为nil,但实际不是这样的。我发现所有不可变类型这么使用,都是有值的,包括NSString,NSArray,NSDictionary,是否和存储的内存区域有关?

对于这个问题,我也实验了一下,如下代码,确实能够正常输出100:

__weak NSNumber*number= [NSNumber numberWithInt:100]; NSLog(@"number =%@", number);

从汇编代码中看,以上代码在创建number变量时,是通过objc_loadWeak方法进行的。而根据Clang的官方文档,objc_loadWeak方法会retain并autorelease这个对象。所以给一个weak对象赋值,它并不会马上释放,而是会放到autorelease pool中,与autorelease pool一起释放。

id objc_loadWeak(id *object) {

returnobjc_autorelease(objc_loadWeakRetained(object));

}


为了验证这个回答,我们又做了一个有趣的例子来验证,如下所示:

__weak NSNumber*number;@autoreleasepool{

number = [NSNumber numberWithInt:100];

}

NSLog(@"number =%@", number);

在上面这个例子中,果然如我们所料,number变成在NSLog时,变成了nil。

讨论二:关于NSNumber对象的缓存

我们在做以上实验时,发现一个有趣的现象,如果你把100变成了10,则number变成在NSLog时,就能够输出值来,不再是nil了。如下是测试代码:

__weak NSNumber*number;@autoreleasepool{

number = [NSNumber numberWithInt:10];

}

NSLog(@"number =%@", number);

经过 onevcat 的实验,从-1 ~ 12都是可以输出的,而其它值却会变成nil。于是我们猜测是系统对这些常见值的对象做了缓存,于是我们写了如下代码来验证。

结果果然是这样,多次创建值为10的NSNumber对象,其地址都是一样的。而多次创建值为100的NSNumber对象,每次创建获得的对象地址都是不一样的。

NSNumber*number= [NSNumber numberWithInt:10]; NSNumber*another= [NSNumber numberWithInt:10]; NSLog(@"%p%p", number, another);number = [NSNumber numberWithInt:100]; another = [NSNumber numberWithInt:100]; NSLog(@"%p%p", number, another);

讨论三:64位系统与Tagged Pointer对象

讨论本来已经结束了,结果我在写这篇博客的时候,手贱又测试了一下,发现在64位的模拟器下,无论是怎么样,所有的NSNumber对象地址都是一样的!

这个时候我突然想起WWDC中介绍的64位系统引放的Tagged Pointer,恍然大悟。

在WWDC2013的《Session 404 Advanced in Objective-C》视频中,苹果介绍了Tagged Pointer。Tagged Pointer的存在主要是为了节省内存。我们知道,对象的指针大小一般是与机器字长有关,在32位系统中,一个指针的大小是32位(4字节),而在64位系统中,一个指针的大小将是64位(8字节)。

在64位系统中,如果我们真正使用一个指针来存储NSNumber实例,那么我们首先需要一个8字节的指针,另外需要一块内存存储NSNumber实例,这通常又是8字节。这样的内存开销是比较大的。苹果对于NSNumber和NSDate对象,改成了用Tagged Pointer来存储。

Tagged Pointer主要有以下3个特点:

Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate

Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已!所以,它的内存并不存储在堆中,也不需要malloc和free。

在内存读取上有着3倍的效率(以前是寻址->发消息->获取值,现在直接获取值),创建时比以前快106倍。

image.png

但Tagged Pointer的引入也带来了问题,即Tagged Pointer因为并不是真正的对象,而是一个伪对象,所以你如果完全把它当成对象来使,可能会让它露马脚。比如我在《Objective-C对象模型及应用》一文中就写道,所有对象都有 isa 指针,而Tagged Pointer其实是没有的,因为它不是真正的对象。

所以你直接访问Tagged Pointer的isa成员的话,在编译时将会有如下警告:

image.png

应该换成相应的方法调用,如isKindOfClass和object_getClass,如下图所示:

image.png

至此,所有疑问都已经解决,开心~

致敬:原创已经找不到出处,向原创致敬!


相关文章
|
4月前
|
存储 缓存 算法
同时使用线程本地变量以及对象缓存的问题
【7月更文挑战第15天】同时使用线程本地变量和对象缓存需小心处理以避免数据不一致、竞争条件及内存泄漏等问题。线程本地变量使各线程拥有独立存储,但若与对象缓存关联,可能导致多线程环境下访问旧数据。缺乏同步机制时,多线程并发修改缓存中的共享对象还会引起数据混乱。此外,若线程结束时未释放对象引用,可能导致内存泄漏。例如,在Web服务器场景下,若一更新缓存而另一线程仍获取旧数据,则可能返回错误信息;在图像处理应用中,若多线程无序修改算法对象则可能产生错误处理结果。因此,需确保数据一致性、避免竞争条件并妥善管理内存。
|
4月前
|
存储 缓存 算法
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
59 0
|
6月前
|
缓存 小程序
uniapp读取(获取)缓存中的对象值(微信小程序)
uniapp读取(获取)缓存中的对象值(微信小程序)
177 1
|
6月前
|
存储 缓存 小程序
【微信小程序3】本地缓存:一次性存储多个对象值
【微信小程序3】本地缓存:一次性存储多个对象值
130 0
|
缓存 关系型数据库 MySQL
高性能内存对象缓存Memcached
高性能内存对象缓存Memcached案例
|
6月前
|
缓存 NoSQL Java
Spring Data Redis对象缓存序列化问题
在使用 Redis 时,有没有遇到同我一样,对象缓存序列化问题的呢?
128 6
Spring Data Redis对象缓存序列化问题
|
6月前
|
缓存 NoSQL Java
Redis实现商品信息对象缓存
Redis实现商品信息对象缓存
229 0
|
6月前
|
存储 设计模式 Java
Mybatis源码细节探究:二级缓存Cache对象是在什么时候创建的?
Mybatis源码细节探究:二级缓存Cache对象是在什么时候创建的?
|
存储 缓存 Java
Spring Boot集成Caffeine Cache时遇到获取到的缓存对象和当初设置的对象不同的问题...
Spring Boot集成Caffeine Cache时遇到获取到的缓存对象和当初设置的对象不同的问题...
269 0
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
68 6