我们先实现几个string看看底层实现
clang之后观察具体实现
NSString *str1 = @"";
NSString *str1 = (NSString *)&__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_0;
NSString *str2 = [NSString stringWithFormat:@"%@",@""];
NSString *str2 = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_1, (NSString *)&__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_2);
NSString *str3 = [NSString stringWithFormat:@"%d",1];
NSString *str3 = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_3, 1);
NSString *str4 = [NSString stringWithFormat:@"%@",1];
NSString *str4 = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_4, 1);
通过这4组对照可以看出,字符串在底层会被构造成__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi开头的结构体。
其中
__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_1
__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_3
__NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_4
这三个结构体是标识占位符字符串。
它们对应的占位符分别是
_mi_1:@"%@"
_mi_3:@"%d"
_mi_4:@"%@"
_mi_1和_mi_4是一样的,所以我们只研究_mi_1和_mi_3。
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_1 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"%@",2};
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_zr_p1cnc5b14b7bfgk7vs7_x6fh0000gp_T_main_35ff71_mi_3 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"%d",2}
我们观察_mi_1和_mi_3的具体实现,先这两个结构体的具体结构其实是__NSConstantStringImpl,我们再来看下这个东西的结构。
struct __NSConstantStringImpl {
int *isa;
int flags;
char *str;
#if _WIN64
long long length;
#else
long length;
#endif
};
从这个结构体的结构可以看出_mi_1和_mi_3就是赋值后的__NSConstantStringImpl结构体。占位符放在了str字段中。不过这样我们还是看不出来占位符的原理是怎样的。占位符被放在__NSConstantStringImpl通过objc_msgSend函数发送进了NSString类中。
我们知道%@的占位符一般都是用来对OC对象进行占位,在获取OC对象的具体值时我们一般都是通过引用来操作,而引用的本质是指针。
我们实现下面的代码并运行。
NSString *str = [NSString stringWithFormat:@"%@",1];
这句代码摘编译过程中只会报警告,但是可以通过编译,但是在运行时却会崩溃,崩溃信息是
Thread 1: EXC_BAD_ACCESS (code=1, address=0x1)
这个崩溃信息很常见,是野指针造成的崩溃。
我们现在可以做个猜想,%@占位时会把传入的变量当做指针,去指针对应的位置获取实际对象,这里传入了1而存储空间中地址为1的位置中可能是没有对象的,此时就会造成野指针崩溃。
为了验证这个猜想我们再实现下面的代码。
NSString *str1 = @"1";
NSInteger pointer = (NSInteger)str1;
NSString *str2 = [NSString stringWithFormat:@"%@",pointer];
NSLog(@"pointrt:%ld",pointer);
NSLog(@"str2:%@",str2);
打印结果
2020-08-07 11:04:44.496693+0800 BlockPrinciple[24761:111933] pointrt:4476567608
2020-08-07 11:04:44.498160+0800 BlockPrinciple[24761:111933] str2:1
在这段代码中,我们把str1的地址强转成了NSInteger类型的变量pointer,在构造str2时将pointer和%@占位符结合使用,最后取到了str1的值“1”,这说明我们之前的猜想应该没有问题。
总结
虽然我们仍然无法确认在NSString内部是如何分析%@和%ld这些占位符之前的区别(猜测是字符串匹配),但是我们可以确认,在使用%@占位符时,stringWithFormat方法会将%@占位符对应的参数当做地址,去对应地址处获取相应的对象。
欢迎关注公众号,留言讨论