由一道题引发的思考——深浅复制isa指针TaggedPointer

先上题目

NSMutableString *tempMutablevString = [NSMutableString stringWithString:@"abc"];
    NSString *stringCopy = [tempMutablevString copy];
    NSString *stringMutableCopy = [tempMutablevString mutableCopy];
    NSMutableString *mutStringCopy = [tempMutablevString copy];
    NSMutableString *MutStringMutableCopy = [tempMutablevString mutableCopy];
    NSLog(@"tempMutableString:%p\nstringCopy:%p\nstringMutableCopy:%p\nmutStringCopy:%p\nMutStringMutableCopy:%p\n",tempMutablevString,stringCopy, stringMutableCopy, mutStringCopy, MutStringMutableCopy);

打印发现

tempMutableString:0x6000020e9e90
stringCopy:0xfe074d82f814fd66
stringMutableCopy:0x6000020ea070
mutStringCopy:0xfe074d82f814fd66
MutStringMutableCopy:0x6000020e9ce0

为什么可变字符串的copy和不可变字符串的mutablecopy出的对象是同一个?
有基础的直接看https://blog.csdn.net/KevinAshen/article/details/103394681
记录一下这道题的探索过程

首先当然是回顾复制

非容器类的可变对象不论是copy还是mutablecopy,都是深复制,这句话好像并没有解决问题,反而更加疑惑了。
那我在搜搜有没有源码
有个大神说他看了源码
NSString和NSMutableString的copy和mutablcopy还是有区别的
它俩首先继承了NSObject的俩个方法:

- (id)copy {
 return [(id)self copyWithZone:nil];
}

- (id)mutableCopy {
 return [(id)self mutableCopyWithZone:nil];
}

然后NSString重写了mutableCopyWithZone和copyWithZone:

- (id)copyWithZone:(NSZone *)zone {
 if (NSStringClass == Nil)
 NSStringClass = [NSString class];
 return RETAIN(self);
}

- (id)mutableCopyWithZone:(NSZone*)zone {
 return [[NSMutableString allocWithZone:zone] initWithString:self];
}

但是NSMutableString头铁,它重写了copy和mutablecopy

-(id)copy {
 return [[NSString alloc] initWithString:self];
}

-(id)copyWithZone:(NSZone*)zone {
 return [[NSString allocWithZone:zone] initWithString:self];
}

那么可变字符串执行mutablecopy的话,会调用NSString的mutableCopyWithZone方法。
看完这个我只是更加确信那道题都是深复制。

看了看isa指针

我了解到:
任何直接或间接继承了NSObject的类,它的实例对象(instacne objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。
类对象也有isa指针,指向他的元类
这是一个铺垫,来看一下学长的文章吧

TaggedPointer

对象在内存中是对齐的,它们的地址总是指针大小的整数倍,通常为16的倍数。对象指针是一个64位的整数,而为了对齐,一些位将永远是零。

Tagged Pointer利用了这一现状,它使对象指针中非零位有了特殊的含义。在苹果的64位Objective-C实现中,若对象指针的最低有效位为1(即奇数),则该指针为Tagged Pointer。这种指针不通过解引用isa来获取其所属类,而是通过接下来三位的一个类表的索引。该索引是用来查找所属类是采用Tagged Pointer的哪个类。剩下的60位则留给类来使用。

Tagged Pointer有一个简单的应用,那就是NSNumber。它使用60位来存储数值。最低位置1。剩下3位为NSNumber的标志。在这个例子中,就可以存储任何所需内存小于60位的数值。
一个小实验:

 for (long int i = 0; i < 10; i++) {
        NSNumber *number = [NSNumber numberWithLong:i];
        NSLog(@"%@ %p",[number class],number);
    }

打印:

__NSCFNumber 0xf995c9038181f0ce
__NSCFNumber 0xf995c9038181f0de
__NSCFNumber 0xf995c9038181f0ee
__NSCFNumber 0xf995c9038181f0fe
__NSCFNumber 0xf995c9038181f08e
 __NSCFNumber 0xf995c9038181f09e
__NSCFNumber 0xf995c9038181f0ae
__NSCFNumber 0xf995c9038181f0be
__NSCFNumber 0xf995c9038181f04e
__NSCFNumber 0xf995c9038181f05e

最低4位一直位e代表了这是一个long的类型,如果是int,它将会是a。
最高4位为f,代表这是一个NSnumber类型。
这里的e为16进制,转化为二进制为4位,1110。
这里我遇到了问题,最低位不是1,原文说 NSNumber最低4位是TaggedPointer指针,只有当数值很大时,才会全部存储数值,这里是怎么回事呢?
另一个实验:

NSString *a = @"avc";
     NSString *b = [[a mutableCopy] copy];
     NSLog(@"%p %p %@", a, b, [b class]);

打印:0x10f1ba040 0xe7fb81e893e1da71 NSTaggedPointerString
这里发现b的所属类为 NSTaggedPointerString,而地址最后一位也为1,代表是一个TaggedPointer指针。
回到我们的开始

打印试验解释

NSLog(@"%@\n%@\n",[stringCopy class], [mutStringCopy class]);
    NSLog(@"%@\n%@\n",[stringMutableCopy class ], [stringMutableCopy class]);

打印结果:

NSTaggedPointerString
NSTaggedPointerString
__NSCFString
__NSCFString

在这个试验之前我还发现了其他字符串的类型__NSCFConstantString

三个字符串类型?

__NSCFConstantString

字符串常量,是一种编译时常量,它的 retainCount 值很大,是 4294967295,在控制台打印出的数值则是 18446744073709551615==2^64-1,测试证明,即便对其进行 release 操作,retainCount 也不会产生任何变化。是创建之后便是放不掉的对象。相同内容的 __NSCFConstantString 对象的地址相同,也就是说常量字符串对象是一种单例。

这种对象一般通过字面值 @"…"、CFSTR("…") 或者 stringWithString: 方法(需要说明的是,这个方法在 iOS6 SDK 中已经被称为redundant,使用这个方法会产生一条编译器警告。这个方法等同于字面值创建的方法)产生。

这种对象存储在字符串常量区。

__NSCFString

和 __NSCFConstantString 不同, __NSCFString 对象是在运行时创建的一种 NSString 子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。

通过 NSString 的 stringWithFormat 等方法创建的 NSString 对象一般都是这种类型。

这种对象被存储在堆上。

NSTaggedPointerString

理解这个类型,需要明白什么是标签指针,这是苹果在 64 位环境下对 NSString,NSNumber 等对象做的一些优化。简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8 位足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。从他的引用计数可以看出,这货也是一个释放不掉的单例常量对象。在运行时根据实际情况创建。

对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型,如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 )__NSCFString 类型。

这种对象被直接存储在指针的内容中,可以当作一种伪对象
特殊的就是这个copy

copy:
copy方法的本质就是用NSString的stringWithFormat方法创建的不可变字符串
它遵循其看人下菜的本质,对于不同的字符串,会生成不同的类型【其实也就__NSCFString和NSTaggedPointerString两种】.

参考文章

1.通过源码分析copy和mutablecopy
2.iOS里的TaggedPointerNSString篇
4.NSString的内存管理之 __NSCFConstantString、NSTaggedPointerString、__NSCFString

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值