1、什么是isa指针
概念:
Every object has an isa instance variable that identifies the object's class. The runtime uses this pointer to determine the actual class of the object when it needs to.
每个对象都有一个标识对象类的isa实例变量。运行时使用此指针来确定对象需要时的实际类。
这就好比把isa拆开成 is a(是什么类的实例)的意思。
代码中的isa:
在objc.h文件中有这样定义:
从图中我们可以看出三点:
1、 id类型是一个objc_object结构体的指针。
2、objc_object结构体包含一个Class 类型的变量isa。
3、 Class是objc_class结构体的指针。
事实上在objc的runtime中,类是用 objc_class 结构体表示的,对象是用 objc_object 结构体表示的。这也就解释了为什么id类型可以指向OC中任意对象类型了。
到了这里我们只需要再明白objc_class结构体的内容就可以了。在runtime.h文件中objc_class结构体定义如下:
struct objc_class {
Class isa //所属类的指针
Class super_class//指向父类的指针
const char *name //类名
long version // 版本
long info //供运行期使用的一些位标识。
long instance_size //实例大小
struct objc_ivar_list *ivars //成员变量数组
struct objc_method_list **methodLists //方法列表
struct objc_cache *cache//指向最近使用的方法.用于方法调用的优化
struct objc_protocol_list *protocols//协议的数组
}
当看到objc_class结构体的第一个变量也是Class类型的指针时,是不是很崩溃。不必难过,其实这正好验证了万物皆对象的事实。类也是对象,他是meteClass(元类)的实例。
到这里我们来整理下思路:
- 实例对象在运行时被表示成objc_object类型结构体,结构体内部有个isa指针指向objc_class结构体。
- objc_class内部保存了类的变量和方法列表以及其他一些信息,并且还有一个isa指针。这个isa指针会指向meteClass(元类),元类里保存了这个类的类方法列表。
-
为了完整性,其实元类里也有一个isa指针,这个isa指针,指向的是根元类,根元类的isa指针指向自己
大致如下面逻辑:
实例对象--(runtime)-->objc_object--(isa)-->objc_class--(isa)-->元类--isa-->根元类--isa-->自己。
然而值得一提的是:当我们调用某个类的方法时,如果这个类的方法列表里没有该方法,则会去找这个类的父类的方法列表。这种机制就是通过objc_class的第二个变量super_class指针实现的。并且这种继承关系会扩展到元类。最终类似于下图的一种关系:
2、KVO的实现原理
key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
从Apple文档中我们大概可以了解到:
KVO是通过"isa-swizzling"技术来实现的,当一个对象注册观察者时,这个对象的isa指针被修改指向一个中间类。
KVO 的实现依赖于 Objective-C 强大的 runtime(这里不详细讲解,我准备开一篇结合实例讲解下runtime,喜欢我文章的可以关注下O(∩_∩)O~~)。当观察A类型的对象时,在运行时会创建了一个集成自A类的NSKVONotifying_A类,且为NSKVONotifying_A重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察者属性值的更改情况。
假设A类有个name属性,NSKVONotifying_A重写setName方法:
- (void) setName:(NSString *)name
{
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}
被观察属性发生改变之前,willChangeValueForKey:
被调用,通知系统该 keyPath 的属性值即将变更,来保存旧值;当改变发生后,didChangeValueForKey:
被调用,通知系统该 keyPath 的属性值已经变更;之后,observeValueForKey:ofObject:change:context:
就会被调用。
3、手动触发KVO
以上我们说的都是自动触发,只要我们注册了观察者。只要被观察的属性值一改变就会调用observeValueForKey:ofObject:change:context:
方法,而有时候我们在特定的情况下,才去通知观察者被观察的属性改变了。这就需要我们手动触发KVO。大致步骤:
1、取消自动触发:重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
2、重写属性的setter方法,根据需求判断是否需要调用willChangeValueForKey:
和didChangeValueForKey:
方法。
代码如下:
- (void)setName:(NSString *)name{
if ([name isEqualToString:@"小白"]) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}else{
_name = name;
}
}
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
// 这里只是取消了name属性的自动触发
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
这样写之后,只有当Dog类对象的name属性等于"小白"时才会通知观察者属性改变了。
注意:取消自动触发的方法如果直接返回NO,那么这个类的对象的所有属性值都会取消自动触发。所以最好根据需要自己判断。
从上面的介绍中我们看到KVO的调用其实很繁琐,如果有个带有block的方法就好了,这篇文章手把手教你封装一个带有block的KVO方法。
-------------完--------------
最后:喜欢我文章的可以多多点赞和关注,您的鼓励是我写作的动力。O(∩_∩)O~