(0019)iOS 开发之关于__weak修饰NSString以及内存管理的问题
前言:写这篇文章的初衷,是对阅读别人的博客提出的疑问,一路探索得来的。同时也要加强对内存管理以及block 的管理和使用。
ARC指南1 - strong和weak指针 写到:打印出来是"(null)" __weak NSString *str = [[NSString alloc] initWithFormat:@"1234"];
NSLog(@"%@", str);
粘贴代码到工程结果: 打印出来并不是(null)。于是又百度一番:发现:转向ARC的说明——翻译Apple官方文档 这样写:
NSString * __weak string2 = [[NSString alloc] initWithFormat:@"First Name"];
NSLog(@"string: %@", string);
打印的才是 "(null)”。
看到这个结果是不是大吃一惊,__weak 修饰的 string 不都是为null吗?为什么会前者是@“1234”。后者是"(null)”,是不是ARC 内存管理有问题呢?
于是我就查看了官方文档:https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
Take care when using __weak variables on the stack. (要注意在栈上的变量)
Although string is used after the initial assignment, there is no other strong reference to the string object at the time of assignment; it is therefore immediately deallocated. The log statement shows that string has a null value.
结果:__weak 修饰用initWithFormat 初始化的对象时,两种写法是都是对的。重点在于打不打印 null 跟@“1234” 这个字符串的长度有关,长的话最好超过10 ,就可以打印 null ,小于9 就能正常打印。为什么会有这样的区别?
结论:
如果:用__weak 修饰 @"" 和 WithString 方式构造的字符串会正常打印的,不会@“null”。
因为:这种初始化的string是常量,编译后放到程序的常量区(属于内存静态区),所以引用计数不会生效,放在常量区的数据是跟程序生命周期绑定的,存在内存静态区,不会被销毁,输出自然会有结果。
首先看一下NSString 的关于string初始化的方法
1.
#pragma mark *** Initializers ***
+ (instancetype)string;
+ (instancetype)stringWithString:(NSString *)string;
+ (instancetype)stringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
- (instancetype)initWithString:(NSString *)aString;
- (instancetype)initWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
1.1 准备(引用isa来获取其所属类)
关于isa提一点:isa是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。对象所属类的结构体指针类型!更多isa的可以自己去了解。
// 为了方便测试,我先写了个宏,用来打印NSString的值、类、内存地址。
#define TLog1(_var) ({ NSString *name = _var; NSLog(@"%@: %@ -> %p", name, [_var class], _var);})
1.2 NSString的创建
NSString * temp000 = [NSString string];
temp000 = @"string";
NSString * temp111 = [[NSString alloc]initWithString:@"string111111"];
NSString * temp222 = [NSString stringWithString:@"string"];
NSString * temp333 = [[NSString alloc]initWithFormat:@"First Name"];
NSString * temp444 = [NSString stringWithFormat:@"string"];
TLog1(temp000);
TLog1(temp111);
TLog1(temp222);
TLog1(temp333);
TLog1(temp444);
其实上面的代码:temp111、temp222 会报 Using 'initWithString:' with a literal is redundant ,什么意思尼?
就是用这两种创建字符串的方法是多此一举,因为它有更方便的方法 NSString * temp111 = @"string111111";
以下是控制台的打印:
:string: __NSCFConstantString -> 0x10f73c410
:string111111: __NSCFConstantString -> 0x10f73c430
:string: __NSCFConstantString -> 0x10f73c410
:First Name: __NSCFString -> 0x600000424800
:string: NSTaggedPointerString -> 0xa00676e697274736
看到上面这段测试代码,我们可以发现几点同我们想象不同的地方:
- temp000、temp111、temp222 对象的 isa 都是__NSCFConstantString(常量字符串);
· temp333、temp444 对象的 isa 有的 是NSTaggedPointerString,有的是__NSCFString;
· 后两种在string的值长度大于某一个阀值(经测试 = 9)时,是__NSCFString类型,(>9)时成了,NSTaggedPointerString。
· temp333、temp444 创建的NSString和创建其他objc对象类似的,在堆上分配内存。
· 前3种的 内存地址码 长度短;后两种长。
1.3 测试2:如果把初始化的 string 都是设置 @“string”的话,再次运行,得到以下结论:
· 前3种初始化相同的string时,它们的isa和内存地址都是一样的;后2种初始化相同的string时,它们的isa和内存地址也是一样的;
· 证明两种方式初始化相同的string并不会重新申请新的地址存放同样的字符串的。编译器不再生成一个重复的字符串而是返回该字符串的地址。
结论:
对一个__NSCFConstantString进行retain和copy操作都还是自己,没有任何变化,对其mutableCopy操作可将其拷贝到堆上,retainCount为1。对__NSCFString进行retain和mutableCopy操作时,其特性符合正常的对象特性。但是对其copy时,它却变成了一个_NSCFConstantString对象!(http://www.360doc.com/content/15/0304/17/9200790_452525916.shtml)
用 initWithFormat 初始化字符串:NSString *str = [[NSString alloc] initWithFormat:@"First Name"];
如果字符串的长度较短,那么的指针类型是NSTaggedPointerString,用指针地址的富余位存变量值,
实际上是把常量区的复制了一份在堆中,所以你可以release了。但是 @"First Name" 这个变量本身还是存放在常量区了,所以并没有省内存,反而是增加了内存的使用。
所以,如果直接用一个字符串产生另一个字符串,尽量少用 initWithFormat,而不是initWithString。
指针存在栈,它存的是变量的地址。变量可能在堆,可能在栈,也可能在常量区!
在栈内存。栈内存的变量由系统自动释放回收,无需担心。block在栈,可以copy到堆的,所以self ,@property (copy) 对其引用。
1.4 测试3:把
NSString * temp555 = [NSString stringWithFormat:@"string"];
NSString * temp666 = [NSString stringWithFormat:@"stringstring”];
TLog1(temp555);
TLog1(temp666);
:string: NSTaggedPointerString -> 0xa00676e697274736
:stringstring: __NSCFString -> 0x608000238880
__NSCFString 对象类型,在堆上,如果用__weak 修饰 stringWithFormat 的对象 ,为什么不管长短也能打印出来尼?
结论:
string的值长度大于某一个阀值(经测试 = 9)时,是NSTaggedPointerString类型,(>9)时成了,__NSCFString。但是stringWithFormat是类办法,内存资源上是autorelease的,不会立即释放,所以能打印。
http://bbs.51cto.com/thread-843658-1.html 这个解释了__weak 修饰 stringWithFormat 的对象 ,为什么不管长短也能打印出来的问题?
1、initWithFormat是实例办法
只能经由过程 NSString* str = [[NSString alloc] initWithFormat:@"%@",@"Hello World"] 调用,然则必须手动release来开释内存资源
2、stringWithFormat是类办法
可以直接用 NSString* str = [NSString stringWithFormat:@"%@",@"Hello World"] 调用,内存资源上是autorelease的,并不是立即释放的,所以可以打印出来,不需要手动显式release!
Apple 的 NSTaggedPointerString?
NSTaggedPointerString 它的作用就是:节省短的变量的内存。用指针地址的富余位存变量值。
对象在内存中是对齐的,它们的地址总是指针大小的整数倍,通常为16的倍数。对象指针是一个64位的整数,而为了对齐,一些位将永远是零。Tagged Pointer利用了这一现状,它使对象指针中非零位有了特殊的含义。在苹果的64位Objective-C实现中,若对象指针的最低有效位为1(即奇数),则该指针为Tagged Pointer。这种指针不通过解引用isa来获取其所属类,而是通过接下来三位的一个类表的索引。该索引是用来查找所属类是采用Tagged Pointer的哪个类。剩下的60位则留给类来使用。
应用:Tagged Pointer有一个简单的应用,那就是NSNumber。它使用60位来存储数值。最低位置1。剩下3位为NSNumber的标志。在这个例子中,就可以存储任何所需内存小于60位的数值。从外部看,Tagged Pointer很像一个对象。它能够响应消息,因为objc_msgSend可以识别Tagged Pointer。假设你调用integerValue,它将从那60位中提取数值并返回。这样,每访问一个对象,就省下了一次真正对象的内存分配,省下了一次间接取值的时间。同时引用计数可以是空指令,因为没有内存需要释放。对于常用的类,这将是一个巨大的性能提升。
NSString似乎并不适合Tagged Pointer,因为它的长度即可变,又可远远超过60位。然而,Tagged Pointer是可以与普通类共存的,即对一些值使用Tagged Pointer,另一些则使用一般的指针。例如,对于NSNumber,大于2^60-1的整数就不能采用Tagged Pointer来存储,而需要在内存中分配一个NSNumber的对象来存储。只要创建对象的代码编写正确,就没有问题。
NSString也是如此。对于那些所需内存小于60位的字符串,它可以创建一个Tagged Pointer。其余的则被放置在真正的NSString对象里。这使得常用的短字符串的性能得到明显的提升。实际代码就是如此吗?似乎Apple是这么认为的,因为他们这么做了并实现了它。
关于Tagged Pointer 的知识,这里有一篇文章帮助理解:采用Tagged Pointer的字符串(http://www.cocoachina.com/ios/20150918/13449.html)
参考链接:
https://blog.cnbluebox.com/blog/2014/04/16/nsstringte-xing-fen-xi-xue-xi/
(0019)iOS 开发之关于__weak修饰NSString以及内存管理的问题相关推荐
- 【iOS高级资深工程师面试篇】④、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 内存管理部分2/2 引用计数-弱引用-自动释放池-循环引用
iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...
- IOS第四节:OC中的内存管理机制
[自动释放池] [autorelease方法和自动释放池] 每一个对象都有一个autorelease方法,调用autorelease方法之后不会像调用release方法一样马上释放对象, 当一个对象调 ...
- ios开发读取剪切板的内容_iOS中管理剪切板的UIPasteboard粘贴板类用法详解
一.自带剪切板操作的原生UI控件在iOS的UI系统中,有3个控件自带剪切板操作,分别是UITextField.UITextView与UIWebView.在这些控件的文字交互处进行长按手势可以在屏幕视图 ...
- iOS开发——常见BUG——window决定程序的状态栏管理问题
Xcode7升级之后遇到的问题 问题一: 老项目在Xcode6上运行没有任何问题,但在Xcode7上运行直接崩了! 经过一波分析: 发现是因为我顶部状态栏处添加了topWindow,用于处理Table ...
- linux下的驱动大小,(转)Linux驱动开发需要注意的点/KO大小/内存管理
1.不要想不通就写代码,不要为了存粹的解决问题而加代码,不要忽视任何一种场景可能,因为躲得了一时,躲不了一波,BUG迟早会被挖出来的,到时候更加苦逼. 2.内存是个很严肃的问题,不要直接调用系统函数, ...
- iOS开发中各种关键字的区别
1.一些概念 1.浅Copy:指针的复制,只是多了一个指向这块内存的指针,共用一块内存. 深Copy:内存的复制,两块内存是完全不同的, 也就是两个对象指针分别指向不同的内存,互不干涉. 2.atom ...
- iOS开发-retain/assign/strong/weak/copy/mutablecopy/autorelease区别
依旧本着尊重原创和劳动者的原则,将地址先贴在前面: http://www.cnblogs.com/nonato/archive/2013/11/28/3447162.html,作者Nonato 以下内 ...
- MacOS-Mac开发和iOS开发的区别
iOS是从MacOS衍生出来的专门未为移动设备(iphone)推出的操作系统. iOS和Mac OS的UI观念有很大差别,iOS主要支持手势操作,包括多触点技术等等. 从开发人员观点看,iOS和mac ...
- 【iOS沉思录】iOS内存管理试题总结与详解
"iOS中的GC垃圾回收机制与内存管理机制以及block" 问题:僵尸对象.野指针.空指针分别指什么,有什么区别? 僵尸对象:一个OC对象引用计数为0被释放后就变成僵尸对象了,僵尸 ...
最新文章
- 更改IE的默认搜索引擎
- 如何将 Spring Boot Actuator 的指标信息输出到 InfluxDB 和 Prometheus
- UTL_FILE包用法小记
- 关于创业公司产品开发原则
- Vue指令篇_v-for_列表渲染
- mysql执行sql流程_MySQL架构与SQL执行流程
- 一种用于亚洲大豆锈病黄化和坏死严重程度评估的自动植物病理测量系统
- Collection 属性ArrayList.add方法内部调用过程
- 买了一个鼠标一个交换机,京东分别快递了发票
- 【java与移动设备】CH06 资源的使用
- 同一个接口返回多种数据类型
- 钓鱼比赛(百度笔试)
- windows屏幕分辨率获取方式
- 【ROM制作工具】V1.0.0.23新版全新发布啦
- 数值分析基础工具使用Matlab绘制双曲线
- 淘宝店铺动销率对店铺有什么影响,怎样提高店铺动销率
- 京东有鸿蒙系统app,刘强东力挺华为鸿蒙,安卓版京东与鸿蒙版京东,差距不是一点点...
- Neo4j 4.0 / 4.1 配置快速参考 - 单服务器模式,适用于社区版和企业版
- iOS客户端的title不显示解决方案
- 无锡的太湖水臭了!!!