二叉查找树(Binary Search Treee),也称二叉搜索树,是指一课空树或者具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
- 任意节点的左、右子树也分别为二叉搜索树;
二叉搜索树相比其他数据结构的优势在于查找、插入的时间复杂度较低。为O(log n)
二叉查找树(Binary Search Treee),也称二叉搜索树,是指一课空树或者具有下列性质的二叉树:
二叉搜索树相比其他数据结构的优势在于查找、插入的时间复杂度较低。为O(log n)
变量类型 | 是否捕获到 block 内部 | 访问方式 |
---|---|---|
全局变量 | 否 | 直接访问 |
局部变量(auto 类型) | 是 | 值传递 |
局部变量(static 类型) | 是 | 指针传递 |
几个关于block的问题
这是一次失败的安装,起因是前几天面试被问到,有没有用过iDA这个工具
OC的消息传递,可以通过Method Swizzle进行Hook,但是遇到共享库里的C函数呢,如何Hook?
计算机组成原理里可知,32位CPU对应32位OS,它的寻址空间是2的32次方,约为4GB,意味着在32位的计算机系统中,理论支持的物理最大寻址空间是4GB。
1 | 32位CPU只有32根地址总线,所以最大的寻址能力是2的32次方 |
1 | NSInteger i = 0xFFFFFFFFFFFFFF; |
由于NSNumber
继承自NSObject
,所有它有isa
指针,加上内存对齐的处理,系统给NSNumber
对象分配了 32 个字节内存。通过 LLDB
指令读取它的内存,实际上它并没有用完 32 个字节。
iPhone 5s开始采用64bit cpu架构,也就是指针占用4个字节,32bit的寻址范围是4G,显然64bit的指针存在空间浪费,对于小对象,比如NSNumer
,NSDate
,NSString
,TaggeddPointer
不再是地址,而是真正的值。这种方式可以节省内存,提高执行效率
引入 Tagged Pointer
技术之后NSNumber
等对象的指针中存储的数据变成了Tag+Data形式(Tag为特殊标记,用于区分NSNumber
、NSDate
、NSString
等对象类型;Data
为对象的值)。这样使用一个NSNumber
对象只需要 8 个字节指针内存。当指针的 8 个字节不够存储数据时,才会在将对象存储在堆上。
1 | - (void)viewDidLoad { |
number1
~number3
指针为Tagged Pointer
类型,可以看到对象的值都存储在了指针中,对应倒数第二位开始的1、2、4f。而number4
由于数据过大,指针的8个字节不够存储,所以在堆中分配了内存。
最后一位用来表示数据类型。
第一位b
的二进制为1011
,其中第一位1是Tagged Pointer
标识位。后面的011
是类标识位,对应十进制为3
,表示NSNumber
类。
下图是iOS下NSNumber
的Tagged Pointer
位视图
1 | @property (nonatomic, strong) NSString * name; |
1 | @property (nonatomic, strong) NSString * name; |
第一段代码Crash,而第二段却没有问题。
分别打印两段代码的self.name
类型看看,原来第一段代码中self.name为__NSCFString
类型,而第二段代码中为NSTaggedPointerString
类型。
__NSCFString
存储在堆上,它是个正常对象,需要维护引用计数的。self.name
通过setter
方法为其赋值。而setter方法的实现如下:
1 |
|
异步并发执行setter
方法,可能就会有多条线程同时执行[_name release]
,连续release
两次就会造成对象的过度释放,导致Crash
。
解决办法:
1 | @property (atomic, strong) NSString * name; |
第二段代码中的NSString为NSTaggedPointerString
类型,在objc_release
函数中会判断指针是不是TaggedPointer
类型,是的话就不对对象进行release
操作,也就避免了因过度释放对象而导致的Crash
,因为根本就没执行释放操作。
1 | __attribute__((aligned(16), flatten, noinline)) |
class
地址class
地址同理64位存储一个内存地址是种浪费,isa
是一个公用体结构union
,NONPOINTER_ISA
是给对象分配了内存空间,但是不使用sideTable
管理引用计数,而是把引用计数存在了isa
当中。
1 | union isa_t |
SideTable
包含了引用计数表,弱引用计数表,以及一个自旋锁。结构如下
1 | struct SideTable { |
是用hash
表实现的,引用计数会存在多张sideTable
中,修改引用计数,需要经过两次hash
算法,第一次是从sideTables
中找到具体的sideTable
,第二次是从sideTable
中找到对应的引用计数。之所以设计成多张sideTable
而不是一张sideTables
,是因为每次操作都需要加锁,减锁操作,多张可以分离锁,加快操作速度。
1 | SideTable &table = SideTables()[this]; |
苹果使用sideTables
保存所有的weak
引用,key就是对象地址,weak_entry_t
作为值,weak_entry_t
中保存了所有指向该对象的弱引用。
1 | struct weak_table_t { |
目前OC管理内存的方式有两种Taged Pointer 和 isa指针
1 | @interface ViewController : UIViewController |