Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
192 lines (140 sloc) 6.96 KB

NSObject-Runtime-杂货铺

关联blog

##介绍 1.收集一些关于Runtime的Blog
2.一些Runtime实例代码包括介绍

Runtime Blog

待补充

Runtime实例代码

对象绑定

// 对象绑定
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)

// 获取绑定的对象
objc_getAssociatedObject(<#id object#>, <#const void *key#>)

// 删除绑定
objc_removeAssociatedObjects(<#id object#>)

获取类的属性列表

unsigned int count = 0;
// 1.获取属性列表
objc_property_t *propertys = class_copyPropertyList(UIViewController.class, &count);

// 2.遍历属性
while (count--) {
    const char *propertyName = property_getName(propertys[count]);
    NSLog(@"属性 :%@", [NSString stringWithUTF8String:propertyName]);
}
// 3.释放 propertys
free(propertys);

获取类的成员变量列表

unsigned int count = 0;
// 1.获取成员变量列表
Ivar *ivar = class_copyIvarList(UIViewController.class, &count);
// 2.遍历成员变量列表
while (count--) {
    const char *ivarName = ivar_getName(ivar[count]);
    NSLog(@"成员变量 :%@", [NSString stringWithUTF8String:ivarName]);
}
// 释放 ivar
free(ivar);

获取类的方法列表

unsigned int count = 0;
// 1.获取方法列表
Method *method = class_copyMethodList(UIViewController.class, &count);
// 2.遍历方法列表
while (count--) {
    SEL sel = method_getName(method[count]);
    NSLog(@"方法 :%@", NSStringFromSelector(sel));
}
// 释放 method
free(method);

获取类的协议列表

unsigned int count = 0;
// 1.获取遵守的列表
__unsafe_unretained Protocol **protocols = class_copyProtocolList(UIViewController.class, &count);
// 2.遍历遵守的遵守列表
while (count--) {
    const char *protocolName = protocol_getName(protocols[count]);
    NSLog(@"协议 :%@", [NSString stringWithUTF8String:protocolName]);
}
// 3.释放 method
free(protocols);

交换 2 个方法的调用参考1参考2参考3

在实际使用时需使用
+ (void)load {} + dispatch_once
保证只交换一次

void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    // 1.获取旧 Method
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    // 2.获取新 Method
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    // 3.交换方法
    if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

修改一个对象所属于的类型 isa 指针

object_setClass(self, BM_Class);

动态创建类

Class clas = NSClassFromString(@"BMClass");
if (clas)return; // 类已存在
// 1.创建类 BMClass
clas = objc_allocateClassPair(NSObject.class, "BMClass", 0);
// 2.注册类 BMClass
objc_registerClassPair(clas);

KVO

当我们对一个Obj添加 KVO时, Apple 会动态的为此对象的 Class 添加一个子类KVO_Class,然后把Objisa指向KVO_Class(Obj已是KVO_Class类型的对象),在为KVO_Class增加监控的属性的setter方法,当监控的属性有改变时,就会触发新加setter,便实现了KVO功能。

核心技术
动态创建一个类 修改对象所属的类型 动态添加方法 消息转发

代码实现KVO

  • NSObject 增加一个分类 BMKVO 添加如下方法:

      typedef void(^BMKVOChangedBlock)(id newValue, id oldCalue);
    
      - (BOOL)bm_addKVOWithKeyPath:(NSString *)keyPath changedBlock:(BMKVOChangedBlock)changedBlock;
    
  • 为 .m 文件实现如下:

      // 构建中间类名 BMKVO_原类名
      NSString *kvoClassName = [NSString stringWithFormat:@"BMKVO_%@", [NSString stringWithUTF8String:class_getName(self.class)]];
      // 创建中间类
      Class kvoClass = objc_allocateClassPair(self.class, kvoClassName.UTF8String, 0);
      // 构建 setter 方法名
      unichar c = [keyPath characterAtIndex:0];
      NSString *str = [keyPath stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[NSString stringWithFormat:@"%c", c-32]];
      str = [NSString stringWithFormat:@"set%@:", str];
    
  • 添加方法

      // 构建 Type Encodings (方法参数格式)
      Method clazzSetMethod = class_getInstanceMethod(self.class, NSSelectorFromString(str));
      const char *settypes = method_getTypeEncoding(clazzSetMethod);
      
      // 添加方法    
      class_addMethod(kvoClass, NSSelectorFromString(str), (IMP)bm_setter, settypes);
    

其中 Type Encodings 参考官方文档

  • bm_setter 讲解

上面的 bm_setter 是一个函数名,函数实现如下:

static void bm_setter(id self, SEL _cmd, id newValue) {
	// 当修改了监控的属性时,会调到此函数,可在这里面做相关的操作
}

上面基本完成了 KVO 的监控,虽然我们可以检测到属性的修改,但是目前是无法修改的,所以需要我们在 bm_setter 函数里面做监控回调操作,同时修改属性。这里需要使用 objc_msgSendSuper() 来发送消息修改属性。具体实现如下:

static void bm_setter(id self, SEL _cmd, id newValue) {

    NSString *str = NSStringFromSelector(_cmd);

    // 1. 去掉set
    NSRange range = [str rangeOfString:@"set"];
    NSString *subStr1 = [str substringFromIndex:range.location + range.length];
    
    // 2. 首字母转换成大写
    unichar c = [subStr1 characterAtIndex:0];
    NSString *subStr2 = [subStr1 stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[NSString stringWithFormat:@"%c", c+32]];

    // 3. 去掉最后的:
    NSRange range2 = [subStr2 rangeOfString:@":"];
    NSString *getter = [subStr2 substringToIndex:range2.location];
    
    // 4. 取出旧值
    NSString *oldeValue = [self valueForKey:getter];
    
    // 5. 构建 objc_super
    struct objc_super superClazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    
    // 6. 发送消息(给 self 发送 _cmd 消息 参数为 newValue)
    ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
    
    // 7. 处理监控回调
}

参考

待补充

You can’t perform that action at this time.