KVO原理浅析


  • KVO的实现

  • 窥探isa指针


说在前面

KVO作为观察者模式的一种实现,为Cocoa框架中实现Binding的一部分,在ReactiveCocoa框架未出现之前为MVVM模式的实现提供了基础。不过KVO饱受诟病,提供的API不易维护,严重依赖String,不过还好有ReactiveCocoa框架:)

KVO的实现

KVO的实现基于Runtime,在文档《Objective-C Runtime Programming Guide》中有这么一句:

The runtime system acts as a kind of operating system for the Objective-C language

Runtime为Objective-C扮演了一种操作系统的角色。OC中各种黑魔法均通过强大的Runtime来实现,Runtime让OC这一门上古语言迎来了第二春。KVO自然也得通过Runtime来实现。

观察之前

在添加观察者之前对象的isa指针指向了原始类。isa指针用于告诉Runtime该对象是属于哪个类。在这个阶段被观察对象仍然属于原始类

观察之后

在添加观察者以后:
- 系统通过Runtime动态的创建一个中间类,继承自原始类

  • 实现中间类的四个方法

    • class 返回值为原始类

    • setter 用于通知观察者值已经发生改变

    • _isKVOA 私有方法_isKVOA 是用来标示该类是一个KVO 机制声称的类

    • delloc 处理一些收尾工作

  • 将被观察对象的isa指针指向中间类

至此,被观察对象就神奇的变成了原始类的子类的实例

下面我们用代码一一进行验证


#import <objc/runtime.h>
#import <objc/objc.h>

@interface ViewController ()
@property (nonatomic, strong)Person *person;

@property (nonatomic, assign)IMP originalMethod;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [Person person];
    //纪录原始方法地址
    self.originalMethod = method_getImplementation(class_getInstanceMethod(object_getClass(self.person),@selector(setName:)));
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    //打印对象信息
    [self logObjectInfo:(__bridge struct objc_object *)(self.person)];
    //延时改变观察值
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        self.person.name = @"changeName";
    });
}

- (void)logObjectInfo:(struct objc_object * )object
{

    Class object_class = [(__bridge id)object class];

    NSLog(@"--------------\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nclass:%s\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nisa:%s\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nisa_superClass:%@\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nisa_methods:%@\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nIMP_original_setName:%p\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\nIMP_setName:%p\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n----------------",
          class_getName(object_class),
          class_getName(object->isa),
          class_getSuperclass(object->isa),
          [[self classMethodsList:object->isa] componentsJoinedByString:@"   |   "],
          self.originalMethod,
          method_getImplementation(class_getInstanceMethod(object->isa,@selector(setName:))));

}

- (NSArray *)classMethodsList:(Class )class
{
    NSMutableArray *array = [NSMutableArray array];
    int methodCount = 0;
    Method *methodList = class_copyMethodList(class, &methodCount);
    int i;
    for(i = 0; i < methodCount; i++) {
        [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
    }

    free(methodList);

    return array;
}

struct objc_object * 其实是id的全称,打开 objc/object.h 头文件可以看到一行定义

typedef struct objc_object *id;

打印结果:

 2016-10-27 21:08:34.327 KVO-深入理解[4815:1764521]
--------------------------------------------------------------------------
class:                  Person                                            |
--------------------------------------------------------------------------
isa:                    NSKVONotifying_Person                             |
--------------------------------------------------------------------------
isa_superClass:         Person                                            |
--------------------------------------------------------------------------
isa_methods:            setName:   |   class   |   dealloc   |   _isKVOA  |
--------------------------------------------------------------------------
IMP_original_setName:   0x10b44f7a0                                       |
--------------------------------------------------------------------------
IMP_setName:            0x10b5524ed                                       |
--------------------------------------------------------------------------

苹果为了最大限度的还原被观察对象可谓是用心良苦啊,重写了class方法以后返回的是Person类,只有通过isa指针才能获取到真实类NSKVONotifying_Person.通过对比两个setName方法的地址,可以判断setName方法被重写了。

窥探isa指针

神奇的isa指针可以起到改变对象身份的作用,我们来扒扒isa指针的定义:

  • isa 是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身;

在runtime头文件中可以找到对class 以及 object 的定义


//苹果限制了这一部分的使用,如果是Objective-C 2.0编译将不通过

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

对象的本质其实就是一个结构体,类也是一个结构体,可以看到类和对象都有一枚isa指针。不过在arm64架构的设备中,object的isa已经不再是一个指针了!这是因为64位架构中,runtime为了节约空间,会将其余的空间用于储存析构状态,引用计数,被其他 weak 变量引用情况,
下面列出了一些isa 的结构体定义

(最低有效位)
1 bit indexed //0代表原始isa,1代表非指针isa
1 bit has_assoc //对象是否拥有关联对象,如果没有对象可以析构的更快
1 bit has_cxx_dtor //对象是否拥有c++或者ARC析构函数,如果没有对象可以析构的更快
30   bits shiftcls // 类指针
9 bits magic //固定值为 0xd2,用于在调试时分辨真实对象是否未初始化
1 bit weakly_referenced //对象是否有过 weak 对象,如果没有,则析构时更快
1 bit deallocating //对象是否正在析构
1 bit has_sidetable_rc //对象的引用计数值是否过大无法存储在 isa 中
19   bits extra_rc //存储引用计数值减一后的结果
(最高有效位)  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值