必备知识架构-数据结构-①nil、isEqual
前言:你真的懂isEqual与hash?
参考文章:
eg:
1 | 1、NSMutableSet/NSSet中添加对象的时候,就会调用- (NSUInteger)hash方法。 |
通过重写isEqual,我们可以做到对象的判等可以完全由您决定, 即使两个完全不同的对象。
一、基础
1、nil, Nil,NULL 与 NSNull 的区别
- nil 指向一个对象的指针为空,在objc.h 的定义如下:
NSString *name = nil; - Nil 指向一个类的指针为空,定义如下:
Class aClass = Nil; - NULL 指向C类型的指针为空, 例如:
int*pInt = NULL; - NSNull 在Objective-C中是一个类,只是名字中有个Null,多用于集合(NSArray,NSDictionary)中值为空的对象
2、==、 isEqualToString、isEqual
2.1、背景:为什么要有isEqual方法?
答:判断两个对象是否相等(而不是判断两个对象的地址是否相等)。
对于基本类型, ==运算符比较的是值;
对于对象类型, ==运算符比较的是对象的地址(即是否为同一对象)
1 | UIColor *color1 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0]; |
总结:
1、==
判断指针指向的地址,即指针值,是否相等
①对于基本类型, ==运算符比较的是值;
②对于对象类型, ==运算符比较的是对象的地址(即是否为同一对象))2、isEqualToString
直接判断字符串内容是否相等3、isEqual
先判断两个对象的地址是否相同,再判断类型是否一致,然后再判断对象的具体内容是否一致
//情况①:地址相同则肯定相等;
//情况②:地址不同时候,如果类型一致且里面的内容相等,则也算相等
2.1、苹果官方重写isEqual 的demo
isEqual:先判断两个对象的地址是否相同(采用==),再判断类型是否一致(采用isKindOfClass),然后再判断对象的具体内容是否一致
1 | - (BOOL)isEqual:(id)object { |
附:通过重写isEqual,我们可以做到对象的判等可以完全由您决定, 即使两个完全不同的对象。
2.2、==、 isEqualToString、isEqual验证示例
四、weak
以下内容来自: iOS weak关键字实现原理
- 1、weak关键字修饰对象的【初始化及重新赋值】实现原理
- 2、weak关键字是如何保证在weak对象执行语句时内存对象不被释放的呢?
- 3、当weak对象指向的内存对象被释放后,weak对象自动置为nil。原理是怎样?
1、weak关键字修饰对象初始化及重新赋值实现原理
objc_initWeak:当初始化一个weak对象并将内存对象赋值给该weak对象时会调用该方法。
objc_storeWeak:当为一个weak对象赋新值时会调用该方法。
1 | objc_initWeak(id *location, id newObj) |
1 | objc_storeWeak(id *location, id newObj) |
由官方文档可知,无论是初始化weak对象还是为weak对象赋值,最终都会调用到 storeWeak方法,不同点在于,二者传入三个模板值不同。
如果weak对象与旧内存对象的关联,则先调用weak_unregister_no_lock,将weak对象与旧内存对象解除指向关系。
然后在将weak对象与旧内存对象的关联解除后,就调用 weak_register_no_lock方法将weak对象与新内存对象进行关联
至此,weak关键字修饰对象的初始化和重新赋值流程就完成了。
2、weak关键字是如何保证在weak对象执行语句时内存对象不被释放的呢?
问:weak关键字是如何保证【在weak对象执行语句时】内存对象【不被释放】的呢?
答:其实很简单,就是对内存对象进行计数增加。
每次在使用weak对象时,都相当于调用一次 objc_loadWeak。
在weak对象执行的语句中,weak对象所指向的内存对象计数会+1,这样就保证在语句中不会发生执行一半而释放内存对象的问题。
3、当weak对象指向的内存对象被释放后,weak对象自动置为nil。原理是怎样?
当内存对象释放时,会调用到一个叫weak_claer_no_lock的方法。在 weak_claer_no_lock方法中,会进行对weak对象的置空操作。
4、Weak-Strong搭配使用的误解
在使用Block时,我们可以使用weak关键字来避免外部变量被Block强引用而导致的循环引用,同时为了Block中的代码能够正常执行,许多开发者提出了Weak-Strong搭配使用的方式,类似如下:
1 | { |
以上代码相对于单独使用weak来说还是有好处的,使用Weak-Strong搭配的方式的话,可以保证在执行 [strongSelf test1]和 [strongSelf test2]时,是向同一对象发送消息。
为什么这么说呢?当开始执行Block语句时,若self还存在,那么strongSelf可以保证在整个Block代码块中不会被释放,即使Block中调用无数次strongSelf,strongSelf也不会因为多线程而在半途被释放;若开始执行Block时,self已经被释放,那么之后所有的消息都会被发送至nil。所以Weak-Strong搭配可以保证Block中语句被处理为一个事务。
1 | { |
所以说,Weak-Strong并不能保证Block中语句一定会被执行,它只能保证Block中语句作为一个事务被发送到同一对象处。只要理解了weak实现原理,我们就能明白何时单独使用weak也能完成代码功能,而何时必须使用Weak-Strong来保证代码事务能力。
5、weak关键字涉及到的数据结构
略。请查看 iOS weak关键字实现原理
1、SideTables
SideTables本质上是一个全局的StripedMap。而StripedMap本质是一个数组。在
SideTables中, Item类型为SideTable2、SideTable
SideTable中包含三个元素,分别是 1.自旋锁 2.记录对象引用计数的字典 3.记录对象弱引用信息的数据结构weak_table_t。3、weak_table_t
weak_table_t本质上是一个数组,其中每个Item为weak_entry_t。4、weak_entry_t
weak_entry_t就比较有意思了,它本质上是个字典。其中的key值为对象,而value对应为一个数组,数组中保存的值均为weak_referrer_t类型的数据。5、weak_referrer_t
weak_referrer_t本质上是objc_object **,即Objective-C对象的地址。
所以,weak_entry_tvalue数组中,每一个Item均为一个地址,即weak对象的地址。
在项目中有某个功能需要用到多个delegate对象,这就需要把delegate放到容器中,但又只能弱引用weak保存delegate,否则会导致delegate对象不能被释放。所以,我们的需求就是希望能在容器中只保存弱引用的delegate对象。
在OC中Foundation框架中的常用容器类(NSSet,NSDictionary,NSArray)及其可变子类在加入元素时,均会对元素进行强引用。有的时候(比如持有多个Delegate对象时),希望有对应的弱引用容器使用。
支持弱引用的容器类,有如下:
[NSHashTable weakObjectsHashTable]
[NSPointerArray weakObjectsPointerArray]
[NSPointerArray pointerArrayWithOptions:]
详见:YYKit学习笔记之NSHashTable与NSMapTable
其他参考:
*在iOS项目开发过程中,我们经常会使用到NSSet、NSArray、NSDictionary三个类,它们为我们设计较友好的数据结构时提供了很方便的方法*
对于NSSet来说,object是强引用的,和NSDictionary中的value是一样的。而NSDictionary中的key则是copy的,因此当开发者想要使NSSet的objects或者NSDictionary的values为weak,或者NSDictionary使用没有实现协议的对象作为key时,比较麻烦(需要使用NSValue的方法valueWithNonretainedObject)。
还好苹果为我们提供了相对于NSSet和NSDictionary更通用的两个类NSHashTable和NSMapTable。
关联对象实现原理
关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中,如果设置关联对象为nil,就相当于是移除关联对象。
回过头来看objc_AssociationPolicy policy 参数: 属性以什么形式保存的策略。
1 | typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { |
我们会发现其中只有RETAIN和COPY而为什么没有weak呢?
NSPointerArray
1、NSPointerArray
1 | - (instancetype)init { |
