Fork me on GitHub

NSObject Runtime 杂货铺

Blog 介绍

关联Github

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

Runtime Blog

待补充

Runtime实例代码

对象绑定

1
2
3
4
5
6
7
8
// 对象绑定
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)

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

// 删除对象的所有绑定(可以使用 objc_setAssociatedObject 绑定 nil 来解除指定 key 的绑定)
objc_removeAssociatedObjects(<#id object#>)

获取类的属性列表

1
2
3
4
5
6
7
8
9
10
11
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);

获取类的成员变量列表

1
2
3
4
5
6
7
8
9
10
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);

获取类的方法列表

1
2
3
4
5
6
7
8
9
10
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);

获取类的协议列表

1
2
3
4
5
6
7
8
9
10
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
保证只交换一次

交换实例方法

1
2
3
4
5
6
7
8
9
10
11
12
void swizzleInstanceMethod(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);
}
}

交换类方法

其实可以看着为交换类的元类的实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void swizzleClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
// 0.获取class的元类
Class metaClass = objc_getMetaClass(class_getName(class));
// 1.获取旧 Method
Method originalMethod = class_getInstanceMethod(metaClass, originalSelector);
// 2.获取新 Method
Method swizzledMethod = class_getInstanceMethod(metaClass, swizzledSelector);
// 3.交换方法
if (class_addMethod(metaClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(metaClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}

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

1
object_setClass(self, BM_Class);

动态创建类

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

动态添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Person : NSObject

- (void)eat;

@end

@implementation Person

@end

void eat(id self, SEL _cmd) {
NSLog(@"%@ %@", NSStringFromSelector(_cmd) ,self);
}

// 添加方法的 types参数 可以参考Apple官方文档
// 构建 Type Encodings (方法参数格式)
Method clazzSetMethod = class_getInstanceMethod(Person.class, NSSelectorFromString(@"eat"));
const char *settypes = method_getTypeEncoding(clazzSetMethod);

// 添加方法
class_addMethod(Person.class, NSSelectorFromString(@"eat"), (IMP) eat, settypes);

添加实例方法

imp 使用函数强转

1
2
3
4
5
 void addInstanceMethod(Class clas, SEL name, IMP imp) {
Method clazzSetMethod = class_getInstanceMethod(clas, name);
const char *settypes = method_getTypeEncoding(clazzSetMethod);
class_addMethod(clas, name, (IMP) imp, settypes);
}

添加实类方法

imp 使用函数强转

1
2
3
4
5
6
void addClassMethod(Class clas, SEL name, IMP imp) {
Class metaClass = objc_getMetaClass(class_getName(clas));
Method clazzSetMethod = class_getInstanceMethod(metaClass, name);
const char *settypes = method_getTypeEncoding(clazzSetMethod);
class_addMethod(metaClass, name, (IMP) imp, settypes);
}

手动实现KVO

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

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

代码实现KVO

  • NSObject 增加一个分类 BMKVO 添加如下方法:
1
2
3
typedef void(^BMKVOChangedBlock)(id newValue, id oldCalue);

- (BOOL)bm_addKVOWithKeyPath:(NSString *)keyPath changedBlock:(BMKVOChangedBlock)changedBlock;
  • 为 .m 文件实现如下:
1
2
3
4
5
6
7
8
// 构建中间类名 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];
  • 添加方法
1
2
3
4
5
6
// 构建 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是一个函数名,函数实现如下:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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. 处理监控回调
}

把指定对象的NSString属性初始化为 @””

最近遇到这样一个需求,项目中创建的一个模型,服务器返回的数据可能导致模型中的属性为nil,但我们在使用时必须保证所有的NSString属性如果是nil就使用@”” 代替,有一个常规的方法就是重写getter方法做nil判断,但是如果属性较都时较麻烦,而且增加一个属性又需要修改代码,那么有没有简洁的方法呢?答案是肯定有的,对,肯定是使用runtime来实现。下面的的思路是在init方法中把所有的NSString属性设置为@””。

  • 获取到所有属性
  • 使用kvc取值来
  • 判断是否为nil
  • 如果是nil在判断是否为NSString类型
  • 如果是NSString就使用kvc设置为@””
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

@implementation BMModel

static const char *getPropertyType(objc_property_t property) {
const char *attributes = property_getAttributes(property);
char buffer[1 + strlen(attributes)];
strcpy(buffer, attributes);
char *state = buffer, *attribute;
while ((attribute = strsep(&state, ",")) != NULL) {
// 非对象类型
if (attribute[0] == 'T' && attribute[1] != '@') {
// 利用NSData复制一份字符串
return (const char *) [[NSData dataWithBytes:(attribute + 1) length:strlen(attribute) - 1] bytes];
// 纯id类型
} else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
return "id";
// 对象类型
} else if (attribute[0] == 'T' && attribute[1] == '@') {
return (const char *) [[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];
}
}
return "";
}

- (instancetype)init {
if (self = [super init]) {
unsigned int count = 0;
// 1.获取属性列表
objc_property_t *propertys = class_copyPropertyList(self.class, &count);
// 2.遍历属性
while (count--) {
// 取属性
objc_property_t property = propertys[count];
// 获取属性名称
const char *propertyName = property_getName(property);
// 获取属性名称
NSString *propertyNameStr = [NSString stringWithUTF8String:propertyName];
// 获取属性类型
const char *propertyTypeName = getPropertyType(property);
// 使用kvc获取值
id propertyValue = [self valueForKey:propertyNameStr];
if (!propertyValue) {
// 如果值是nil
if (strncmp("NSString", propertyTypeName, strlen("NSString")) == 0) {
// 如果属性类型是 NSString
[self setValue:@"" forKey:propertyNameStr];
}
}
}
// 3.释放 propertys
free(propertys);
}
return self;
}

@end
  • 基于上面的代码,那么就可以自定义各种场景了吧,对模型做各种自定义初始化就不在话下了。
  • 以上代码参考自:这里

参考

- END -
扫一扫上面的二维码图案,加我微信

文章作者:梁大红

特别声明:若无特殊声明均为原创,转载请注明,侵权请联系

版权声明:署名-非商业性使用-禁止演绎 4.0 国际