iOS之RunTime探索与实践
Runtime 概念
Runtime 相关概念
Runtime 实践
Runtime概念
Runtime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候会决定调用哪个函数。
对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
Objective-C 的 Runtime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。这就是说它在类信息(Class information) 中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime 创建了所有需要的结构体,让 Objective-C 的面相对象编程变为可能。
当你用Objective-C创建对象时
[[NSObject alloc]init];
底层runtime创建对象的核心代码如下:
((NSObject *(*)(id,SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id,SEL))(void*)objc_msgSend)((id)objc_getClass("NSObject"),sel_registerName("alloc")),sel_registerName("init"));
需要注意的是,使用objc_msgSend() sel_registerName()方法需要导入头文件<objc/message.h>
Runtime相关概念
1.Self
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
两次打印self 因为super为编译器标示符,向super发送的消息被编译成objc_msgSendSuper,但仍以self作为reveiver
2.Selector
在 Objective-C 中 selector 只是一个 C 的数据结构,用于表示一个你想在一个对象上执行的 Objective-C 方法。
typedef struct objc_selector *SEL;
SEL sel = @selector(doSomething)
Method Message 发送消息
[obj doSomething:prama];
方括号里面就是消息,obj是发送消息目标对象,doSomething是目标对象执行的方法,prama是发送的参数。
Objective-C的消息和C语言的函数调用是不同的。发送了消息之后,obj对象会检查消息发送者,后决定执行或转发消息到另一个目标对象。
下面的代码会?Compile Error / Runtime Crash / NSLog…?
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] foo];
编译运行正常,两行代码都执行-foo。 [NSObject foo]方法查找路线为 NSObject meta class –super-> NSObject class,和第二题知识点很相似。
3.Class
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
Class类的结构体和一个对象的结构体。objc_object只有一个isa pointer
指针,当向对象发送消息时,
下面的代码会?Compile Error / Runtime Crash / NSLog…?
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
@end
编译运行正常,输出ViewController中的self对象。 编译运行正常,调用了-speak方法,由于
id cls = [Sark class];
void *obj = &cls;
obj已经满足了构成一个objc对象的全部要求(首地址指向ClassObject),遂能够正常走消息机制;
由于这个人造的对象在栈上,而取self.name的操作本质上是self指针在内存向高位地址偏移(32位下一个指针是4字节),按viewDidLoad执行时各个变量入栈顺序从高到底为(self, _cmd, self.class, self, obj)(前两个是方法隐含入参,随后两个为super调用的两个压栈参数),遂栈低地址的obj+4取到了self。
objc_msgSend()
Objective-C对象执行方法时,编译器会把
[target doMethodWith:var1];
转换为
objc_msgSend(target,@selector(doMethodWith:),var);
关于objc_msgSend(),
4.IMP
typedef id (*IMP)(id self,SEL _cmd,...);
IMP是指向方法实现的函数指针,由编译器生成。
5.Class类
#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_*protocols OBJC2_UNAVAILABLE;// 遵循的协议
#endif
6.动态语言 与 静态语言
Objective-C 是面向运行时的语言,它尽可能的把编译和链接时要执行的逻辑延迟到运行时。
这个特性给了我们很大的灵活性,可以按需要把消息重定向给合适的对象,实现交换方法等
7.相关函数
objc_msgSend : 给对象发送消息
class_copyMethodList : 遍历某个类所有的方法
class_copyIvarList : 遍历某个类所有的成员变量
...
Runtime 实践
一.Method Swizzle 应用场景
1.方法交换
// 获取两个类的类方法
Method m1 = class_getClassMethod([Person class], @selector(doSomething1));
Method m2 = class_getClassMethod([Person class], @selector(doSomething2));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
2.拦截系统方法
创建一个Category类目,在分类中实现自定义方法,在load方法中实现方法交换,可以给NSObject添加分类,统计创建了多少个对象,给UIViewController添加类目,统计一共创建了多少个控制器。
二.关联对象 类目增加属性的功能 应用场景
类目中无法设置属性的,如果在类目申明中写@property 只能为其生成get 和set 方法的申明,但无法生成成员变量,虽然可以调用,但程序执行后会crash
/** step 1 */
#import <objc/runtime.h>
@property(nonatomic,copy)NSString *name;
/** step 2 重写set get 方法 */
char nameKey;
- (void)setName:(NSString *)name {
// 将某个值跟某个对象关联起来,将某个值存储到某个对象中
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &nameKey);
}
三.动态添加方法 应用场景
+ (void)createClass
{
// 动态创建类
Class cls = objc_allocateClassPair([NSObject class], "cls", 0);
// 动态添加属性
// 3.添加一个NSString的变量,参数4.对齐方式,参数5.参数类型
if (class_addIvar(cls, "name", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}
// NSUInteger size = 4;
// NSGetSizeAndAlignment("*", &size
// , &size);
//
// class_addIvar(cls, "name", size, size, "*");
// "*" 代表字符(),iOS字符为4位,并采用4位对齐cls
// 动态添加方法
// 1对象 2.已实现的方法 3.已实现的函数 4."v@:"见参数类型链接
class_addMethod(cls, @selector(method:), (IMP)method, "v@:");
// 注册这个类到runtime系统中就可以使用了
objc_registerClassPair(cls);
// 用创建的类生成一个实例化对象
id myobj = [[cls alloc]init];
// 给添加的属性赋值
NSString *str = @"参数";
[myobj setValue:str forKey:@"name"];
// 调用添加的method:方法,给myobject这个接受者发送method:消息
[myobj method:10];
}
// 这个方法并没有被调用,但必须实现,否则不会调用下面的方法
- (void)method:(int)prama{
NSLog(@" + int prama is %d",prama);
}
// 实际调用的方法
static void method(id self,SEL _cmd,int prama){
Ivar v = class_getInstanceVariable([self class], "name");
// 返回名为name的ivar 的变量的值
id o = object_getIvar(self, v);
// 成功打印出结果
NSLog(@"%@- %@ - int prama is %d",o,self,prama);
}
/* type
*"v@:@",解释v-返回值void类型,@-self指针id类型,:-SEL指针SEL类型,@-函数第一个参数为id类型
"@@:",解释@-返回值id类型,@-self指针id类型,:-SEL指针SEL类型,*/
四.获取属性列表 应用场景
1.解档归档
// 注意不要和系统方法重名重写
- (void)decode:(NSCoder *)aDecoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 如果有实现该方法再去调用
if ([self respondsToSelector:@selector(ignoredNames)]) {
if ([[self ignoredNames] containsObject:key]) continue;
}
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
c = [c superclass];
}
}
- (void)encode:(NSCoder *)aCoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 如果有实现该方法再去调用
if ([self respondsToSelector:@selector(ignoredNames)]) {
if ([[self ignoredNames] containsObject:key]) continue;
}
id value = [self valueForKeyPath:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
c = [c superclass];
}
}
2.字典转模型
以往使用KVC进行直接赋值,键值对对不上会crash,使用字典转模型,重写setValue:forUndefinedKey:方法防止crash ,但是瘦model文件过多没有什么意义,用runtime进行字典转模型。
字典和模型属性不匹配
1.键值多于模型属性数量(不用处理)
2.键值少于模型属性数量
模型中嵌套模型
数组中装着模型
- (void)setDict:(NSDictionary *)dict {
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 成员变量名转为属性名(去掉下划线 _ )
key = [key substringFromIndex:1];
// 取出字典的值
id value = dict[key];
// 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
if (value == nil) continue;
// 获得成员变量的类型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 如果属性是对象类型
NSRange range = [type rangeOfString:@"@"];
if (range.location != NSNotFound) {
// 那么截取对象的名字(比如@"Dog",截取为Dog)
type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
// 排除系统的对象类型
if (![type hasPrefix:@"NS"]) {
// 将对象名转换为对象的类型,将新的对象字典转模型(递归)
Class class = NSClassFromString(type);
value = [class objectWithDict:value];
}else if ([type isEqualToString:@"NSArray"]) {
// 如果是数组类型,将数组中的每个模型进行字典转模型,先创建一个临时数组存放模型
NSArray *array = (NSArray *)value;
NSMutableArray *mArray = [NSMutableArray array];
// 获取到每个模型的类型
id class ;
if ([self respondsToSelector:@selector(arrayObjectClass)]) {
NSString *classStr = [self arrayObjectClass];
class = NSClassFromString(classStr);
}
// 将数组中的所有模型进行字典转模型
for (int i = 0; i < array.count; i++) {
[mArray addObject:[class objectWithDict:value[i]]];
}
value = mArray;
}
}
// 将字典中的值设置到模型上
[self setValue:value forKeyPath:key];
}
free(ivars);
c = [c superclass];
}
}
+ (instancetype )objectWithDict:(NSDictionary *)dict {
NSObject *obj = [[self alloc]init];
[obj setDict:dict];
return obj;
}
五.动态跳转页面
@implementation RuntimePush
- (void)pushByPrama:(NSDictionary *)pramas{
// 动态创建类
NSString *class = [NSString stringWithFormat:@"%@",pramas[@"class"]];
const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
Class newClass = objc_getClass(className);
if (!newClass) {
// 创建一个类
Class superClass = [NSObject class];
newClass = objc_allocateClassPair(superClass, className, 0);
// 注册你创建的这个类
objc_registerClassPair(newClass);
newClass = objc_allocateClassPair(superClass, className, 0);
// 注册你创建的这个类
objc_registerClassPair(newClass);
}
// 创建对象
id instance = [[newClass alloc]init];
// 对该对象赋值属性
NSDictionary *propertys = pramas[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 检测这个对象是否存在
if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 使用KVC赋值
[instance setValue:obj forKey:key];
}
}];
// 获取导航控制器
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
[pushClassStance pushViewController:instance animated:YES];
}
// 检测是否存在该属性
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount,i;
// 获取对象里的对象
objc_property_t *properties = class_copyPropertyList([instance class], &outCount);
for (i = 0; i<outCount; i++) {
objc_property_t property = properties[i];
// 属性名改字符串
NSString *propertyName = [[NSString alloc]initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判断属性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
};
其他相关应用
插件化开发(利用runtime解耦)
Jspath 热更新
相关推荐
http://blog.sunnyxx.com/2014/11/06/runtime-nuts/
http://cocoasamurai.blogspot.jp/2010/01/understanding-objective-c-runtime.html