反汇编分析NSString,你印象中的NSString是这样吗
我们先来定义三个NSString
-(void) testNSString { NSString* a = @"abc"; NSString* b = [NSString stringWithUTF8String:"abc"]; NSString* c = [@"ab" stringByAppendingString:@"c"]; }
大家都明白,a, b, c 都equalsToString:@"abc"。但是它们是指向同一个对象实例呢,还是根本就不是同一种类型。我们来看一下反汇编代码:
function_args`-[ViewController testNSString]: 0x10bd48590 <+0>: pushq %rbp 0x10bd48591 <+1>: movq %rsp, %rbp 0x10bd48594 <+4>: subq $0x40, %rsp 0x10bd48598 <+8>: leaq 0x1ac9(%rip), %rax ; @"abc" 0x10bd4859f <+15>: movq %rdi, -0x8(%rbp) ; // save %rdi 0x10bd485a3 <+19>: movq %rsi, -0x10(%rbp) ; // save %rsi 0x10bd485a7 <+23>: movq %rax, %rdi 0x10bd485aa <+26>: callq 0x10bd48b1c ; symbol stub for: objc_retain 0x10bd485af <+31>: leaq 0x616(%rip), %rdx ; "abc" 0x10bd485b6 <+38>: movq %rax, -0x18(%rbp) ; // NSString* a -> 0x10bd485ba <+42>: movq 0x277f(%rip), %rax ; (void *)0x000000010c05db20: NSString // (Class)$rax = NSString 0x10bd485c1 <+49>: movq 0x2730(%rip), %rsi ; "stringWithUTF8String:" 0x10bd485c8 <+56>: movq %rax, %rdi 0x10bd485cb <+59>: callq 0x10bd48b0a ; symbol stub for: objc_msgSend 0x10bd485d0 <+64>: movq %rax, %rdi 0x10bd485d3 <+67>: callq 0x10bd48b28 ; symbol stub for: objc_retainAutoreleasedReturnValue 0x10bd485d8 <+72>: leaq 0x1aa9(%rip), %rdx ; @"ab" 0x10bd485df <+79>: leaq 0x1ac2(%rip), %rsi ; @"'c'" 0x10bd485e6 <+86>: movq %rax, -0x20(%rbp) ; // NSString* b 0x10bd485ea <+90>: movq 0x270f(%rip), %rax ; "stringByAppendingString:" 0x10bd485f1 <+97>: movq %rdx, %rdi ; // lea 0x1aa9(<+79>) -> %rdx -> %rdi 0x10bd485f4 <+100>: movq %rsi, -0x40(%rbp) 0x10bd485f8 <+104>: movq %rax, %rsi ; // lea 0x270f(<+97>) -> %rax -> %rsi 0x10bd485fb <+107>: movq -0x40(%rbp), %rdx ; // 0x1ac2(<+86>) -> %rsi -> -0x40(%rbp) -> %rdx 0x10bd485ff <+111>: callq 0x10bd48b0a ; symbol stub for: objc_msgSend 0x10bd48604 <+116>: movq %rax, %rdi 0x10bd48607 <+119>: callq 0x10bd48b28 ; symbol stub for: objc_retainAutoreleasedReturnValue 0x10bd4860c <+124>: movq %rax, -0x28(%rbp) ; // NSString* c 0x10bd48610 <+128>: movq -0x18(%rbp), %rax 0x10bd48614 <+132>: movq %rax, %rdi 0x10bd48617 <+135>: callq 0x10bd48b22 ; symbol stub for: objc_retainAutorelease
NSString* a = @"abc"; 的反汇编片段如下:
0x10bd48598 <+8>: leaq 0x1ac9(%rip), %rax ; @"abc" 0x10bd4859f <+15>: movq %rdi, -0x8(%rbp) ; // save %rdi 0x10bd485a3 <+19>: movq %rsi, -0x10(%rbp) ; // save %rsi 0x10bd485a7 <+23>: movq %rax, %rdi 0x10bd485aa <+26>: callq 0x10bd48b1c ; symbol stub for: objc_retain 0x10bd485b6 <+38>: movq %rax, -0x18(%rbp) ; // NSString* a
如大家所知,@"abc"是一个全局实例,并且对其retain了一次(,因为是ARC环境),然后由a指向了它。
剩下的b和c却是从stringXXX调用中返回的,到底返回了什么,在没有调试过之前,你我都不好一口定金说它返回的就是什么。好就由调试器lldb告诉我们。
0x7fff53eb59a8: 0x00007fce7b706230 // NSString* c {retainCount:2, hash:516202353} (__NSCFString *) 0x7fff53eb59b0: 0xa000000006362613 // NSString* b {retainCount:-1, hash:516202353} (NSTaggedPointerString *) 0x7fff53eb59b8: 0x000000010bd4a068 // NSString* a {retainCount:-1, hash:516202353} (__NSCFConstantString *)
你断言对了吗?lldb给我们的信息真不少,真是可靠的小伙伴。
首先三者指向的目标不一样。
第二retainCount唯独c是可数的,表明它们生命周期不同。
第三它们的hash一致,表明它们的内容一样。
最后真真切切打印,它们不同类,是同宗。
我们先分析指针,第一列绿字地址,表明a,b,c是在堆栈中指针变量,而且地址相邻,和我的定义代码一致。然后看第二列地址,是a,b,c分别指向的地址,c指向堆栈下向某处,没错那就是堆;而b是一个无理头地址,看过我上一篇的介绍就会知道这是一个tagged指针;至于a中规中矩说出它就是全局实例。
原来a,b,c都不是同一样实例。但是结论可能定得太早。
现在我们再来分析它们的生命周期,它们的生命周期受谁掌控。c毫无疑问是受retain/release控制的。然而a,b岂不在retain的时候就被dealloc? 通过反汇编跟踪b@tagged指针忽略retain/release,a@__NSCFConstantString的retain/release不做任何处理。表明了a,b是与程序一样长寿的寿星公。那么这两位寿星公的关系几何呢,它们有上一腿吗?你会怀疑它们是同一身份吗?
不要净是我在说,如果你还不知道真相的话,也有兴趣弄明白就请阁下亲自验证一下了。
最后我们看一下lldb给出的NSString的类的父子链
NSString
NSMutableString
__NSCFString
__NSCFConstantString
NSString
NSTaggedPointerString
另外要清楚一点,objc中的继承是接口继承,不一定是成员继承,大家都知道声明都用@interface,class是可以动态构造的。
最后,又一次多谢大家的观看,更多的objc分析请关注后面的文章。
下一篇大家将会看到幽灵一样的指针TaggedPointer。