iOS stringWithFormat中%@的占位原理

2020-08-07  本文已影响0人  TIGER_XXXX

我们先实现几个string看看底层实现

实现三个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方法会将%@占位符对应的参数当做地址,去对应地址处获取相应的对象。


公众号

欢迎关注公众号,留言讨论

上一篇 下一篇

猜你喜欢

热点阅读