- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. 内存布局
内存五大区
栈: 存放局部变量,参数,指针,函数,地址一般以0x7开头。栈区的内存通过sp寄存器来定位的。堆:存放alloc、new开辟内存的对象,地址一般以0x6开头。全局区(bss):未初始化的全局变量和静态变量,地址一般以0x1开头。常量区(data):已初始化的全局变量和静态变量,地址一般以0x1开头。代码区(text):主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的
除了内存五大区,还有内核区和保留区,内核区是系统用来进行内核处理操作的区域,而保留区则是预留给系统处理nil等。oxc0000000只代表了3个G,也就是说4个G的内存里面,分给了内核区一个G。
2. 内存管理方案
内存管理方案除了MRC和ARC,还有以下三种
TaggedPointer: 用来储存小对象的指针例如NSNumber,NSDate等,指针上还存了tag和内容,优化了内存和访问速度。
NONPOINTER_ISA: 非指针型isa散列表: 引用计数表,弱引用表
2.1 模拟器调试TaggedPointer
这里看到NSString 的类型有三个:
NSCFConstantString:字符串常量,是一种编译时常量,retainCount值很大,对其操作,不会引起引用计数变化,存储在字符串常量区NSCFString:是在运行时创建的NSString子类,创建后引用计数会加1,存储在堆上NSTaggedPointerString:标签指针,是苹果在64位环境下对NSString、NSNumber等对象做的优化。对于NSString对象来说- 当字符串是由
数字、英文字母组合且长度小于等于9时,会自动成为NSTaggedPointerString类型,存储在常量区 - 当有
中文或者其他特殊符号时,会直接成为__NSCFString类型,存储在堆区
- 当字符串是由
接下来去源码搜索TaggedPointer。看到这里有关于playload的位置运算,说明这里有加密和解密的过程。那么接下来就去搜索decode。
这里看到了_objc_decodeTaggedPointer这个方法,_objc_decodeTaggedPointer还调用了_objc_decodeTaggedPointer_noPermute对pointer进行了异或objc_debug_taggedpointer_obfuscator的操作。
搜索一下objc_debug_taggedpointer_obfuscator,看到其在initializeTaggedPointerObfuscator进行了初始化。这里如果开启了混淆,那么就会得到一个随机的objc_debug_taggedpointer_obfuscator值,否则就为0。
在看到这里encode的时候也是异或objc_debug_taggedpointer_obfuscator,这样两次异或就能得到原来的值。
而之前的左移右移操作,则是在_objc_makeTaggedPointer里面。这里可以看出来先进行移动,在进行加密。
接下来打印NSTaggedPointerString的地址,向右移三位去掉tag。由于是小端模式,所以从后往前读,读出107和99,分别对应k 和 c。
NSTaggedPointerString的地址第一位的1代表的是这个指针是target pointer,而后面的010也就是2,则代表的是NSString 类型。
那么如果是NSNumber类型的,则是011,也就是3。
2.2 真机调试TaggedPointer
这里看到前面只是标记这个为TaggedPointer,类型的标记跑到最后去了。而前面还有一个0010代表着2,这个是什么呢?是不是代表字符长度呢?
看到这里asd 和 asdf 分别打印出来的是0011和0100,代表 3 和 4。这里说明确实是代表着字符长度。
那么在NSNumber里面这个代表着啥呢?这里看到6的值是2,6.0的值是5。那么是不是代表着类型呢?经过一番实验,得出char 为0,short 为1,int 为2,long 为3, float 为4,double 为5。