(0019)iOS 开发之关于__weak修饰NSString以及内存管理的问题
来源:互联网 发布:滚动屏编辑软件 编辑:程序博客网 时间:2024/06/05 13:26
前言:写这篇文章的初衷,是对阅读别人的博客提出的疑问,一路探索得来的。同时也要加强对内存管理以及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 = [NSStringstring];
temp000 = @"string";
NSString * temp111 = [[NSStringalloc]initWithString:@"string111111"];
NSString * temp222 = [NSStringstringWithString:@"string"];
NSString * temp333 = [[NSStringalloc]initWithFormat:@"First Name"];
NSString * temp444 = [NSStringstringWithFormat:@"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内存管理-所有权修饰符:__strong, __weak/__unsafe_unretained, __autoreleasing
- 一个关于NSString内存管理的问题
- IOS开发(77)之iOS高级内存管理:比较__unsafe_unretain、__strong、__weak、__autoreleasing
- NSString的内存管理问题
- iOS内存管理1-__weak与__unsafe_unretained
- NSString 内存管理问题
- NSString 类的内存管理问题
- iOS开发指令篇—__weak修饰符详解
- iOS开发之 __block 与 __weak的区别理解
- iOS开发之 内存管理以及深浅拷贝
- NSString的内存管理
- NSString的内存管理
- [iOS]__weak与__block修饰符的区别
- 关于IOS的多任务以及内存管理
- 关于IOS的多任务以及内存管理
- block 中使用__weak 和__strong修饰符的问题
- block 中使用__weak 和__strong修饰符的问题
- 算法概论 - 8.15
- tar命令(LINUX中常用命令)
- JS常见实用算法,不断更新中,欢迎大家提意见
- 深度学习数据集
- Android Studio 中使用 PullToRefresh 框架
- (0019)iOS 开发之关于__weak修饰NSString以及内存管理的问题
- 用sklearn和tensorflow做boston房价的回归计算的比较(1)--经典的sklearn集成模型
- 寒假计划
- 大数据究竟是什么?一篇文章让你认识并读懂大数据
- 一个很棒的caffe框架系列的基础学习博客(网址)
- iOS身份证号码识别
- 六、ServletContext和ServletConfig学习
- 用个小例子来介绍一下JDK8的CompletableFuture
- Ehcache是现在最流行的纯Java开源缓存框架