129-Runtime41-LLVM的中间代码
LLVM 编译器 转换OC 的本质过程
OC -> 中间代码(.ll) -> 汇编、机器代码
可以使用以下命令行指令生成中间代码
clang -emit-llvm -S main.m
Runtime 相关API
创建类 和注册类,释放类及改变对象的指针
// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "YZDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
//注册类
objc_registerClassPair(newClass);
id dog = [[newClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@20 forKey:@"_weight"];
[dog run];
NSLog(@"%zd---%@------%@",class_getInstanceSize(newClass ),[dog valueForKey:@"_age"],[dog valueForKey:@"_weight"]);
成员变量必须要在注册类前添加进去,
因为根据对象的内存结构,成员变量是ro 类型的(readOnly)
对象方法变量不必在注册类前添加进去
方法列表是 class_rw_t 可读可写的。 比如动态解析方法的时候,就是随时添加的。
释放类
// 在不需要这个类的时候释放
objc_disposeClassPair(newClass);
object_setClass()
YZPerson *person = [YZPerson new];
[person run];
object_setClass(person, [YZCat class]);
[person run];
将此实例对象的指针指向 [YZCat class]
获取和设置成员变量的信息
// 获取成员变量的信息
Ivar ageIvar = class_getInstanceVariable([YZPerson class], "_age");
NSLog(@"%s--%s",ivar_getName(ageIvar),ivar_getTypeEncoding(ageIvar));
// 设置和获取成员变量的值
Ivar nameIvar = class_getInstanceVariable([YZPerson class], "_name");
YZPerson *person = [[YZPerson alloc] init];
object_setIvar(person, nameIvar, @"123");
NSLog(@"----%@",person.name);
应用 设置textField 的提示文字的颜色(已启用,用私有成员变量总是不好的)
self.textField.placeholder = @"123";
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
// 成员变量的数量
unsigned int count ;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i=0 ; i < count; i++) {
Ivar ivar = ivars[i];
NSLog(@"%s---%s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
free(ivars);
- 打印出所有的成员变量。
- 找到可能是其提示文字的label
- 通过KVC 设值
- 注意kvc 时 forkeyPath 和key 的区别
Ivars * 数组可以当做指针来用
方法
替换方法 class_replaceMethod()
YZPerson *person = [[YZPerson alloc] init];
class_replaceMethod([YZPerson class], @selector(run), (IMP)myrun , "v");
[person run];
打印
---myrun
将block 报装成imp
YZPerson *person = [[YZPerson alloc] init];
class_replaceMethod([YZPerson class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"123");
}) , "v");
[person run];
打印
123
交换方法 method_exchangeImplementations
Method runMethod = class_getInstanceMethod([YZPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([YZPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
YZPerson *person = [[YZPerson alloc] init];
[person run];
打印 person test
也可交换不同类的方法
Method runMethod = class_getInstanceMethod([YZPerson class], @selector(run));
Method runCat = class_getInstanceMethod([YZCat class], @selector(run));
method_exchangeImplementations(runMethod, runCat);
YZPerson *person = [[YZPerson alloc] init];
[person run];
打印
yzcat run
交换方法的具体应用 (交换系统的底层方法)
比如交换按钮的点击事件
- 在按钮的根方法中交换事件
- 为什么不用给button引入头文件就能实现交换方法(因为在load中交换了方法)
-
- load 中交互方法最好加 GCD Once方法保证只调用一次。
+ (void)load {
Method method1 = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod([self class], @selector(yz_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}
- (void)yz_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 会造成死循环,因为内部的实现方法已经交换
// [self sendAction:action to:target forEvent:event];
// 不会造成死循环
[self yz_sendAction:action to:target forEvent:event];
NSLog(@"123");
}
注意可能会造成的死循环
交换方法的具体应用 (交换系统的底层方法 之数组类簇)
数组中不能插入nil ,此时报的错为
[__NSArrayM insertObject:atIndex:]: object cannot be nil'
__NSArrayM 是NSMutableArray的真实类型
类簇知识扩展
- 类簇在iOS中的应用 NSNumber有两个比较常用的类方法
NSNumber *boolNumber = [NSNumber numberWithBool:YES];
NSLog(@"%@", [[boolNumber class] description]);
输出__NSCFBoolean
NSNumber *intNum = [NSNumber numberWithInt:1];
NSLog(@"%@", [[intNum class] description]);
会输出__NSCFNumber
[instance class]方法返回的当前的对象的类的名称,[NSNumber numberWithInt:1]和[NSNumber numberWithBool:YES]通过上面的验证可以看出明显不是同一个类,而且__NSCFNumber和__NSCFBoolean很明显是一个私有类。
- 为什么要这样做 ?
以NSNumber为例,我们知道NSNumber可以存储很多类型的数据,如int、Float、Double等等,具体支持哪些数据类型可以到NSNumber的头文件中查看。
- 一般情况下实现类似的效果一种方式是把NSNumber作为基类,然后分别去实现各自的子类,如下图所示
4,类簇的应用 现在很多应用需要同时兼容iOS6、iOS7目前还需要针对iOS8进行适配。为了同时支持iOS6和iOS7,也在各个系统上显示的效果符合系统原生的风格,就可以采用类簇的设计进行设计。 外界调用者并不区分具体的iOS系统而直接调用,系统的适配在.m文件中。
runtime 小结
讲一下 OC 的消息机制
1,OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
2,objc_msgSend底层有3大阶段 消息发送(当前类、父类中查找)、动态方法解析、消息转发
什么是Runtime?
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行 OC的动态性就是由Runtime来支撑和实现的.
- Runtime是一套C语言的API,封装了很多动态性相关的函数 平时编写的OC代码,底层都是转换成了Runtime API进行调用.
具体应用
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题