一下懵逼的感觉,时时刻刻都在接触runtime
但是当问起我们的时候真的就知道怎么回答这个问题。作为一个iOS开发者需要对这个概念有个系统的认识。
我对回答这个问题提出两点建议。
1.不要过早的陷入细节,从最高的层次上来阅读runtime解释出来,有一个大概的认识
在这之间说一下消息机制,message_send()。还有isa指针的指向问题,和方法搜索机制。总结就是消息机制,稍微提一下动态决议。
2.描述自己擅长的层面,体现自己的独特见解。从细节上剖析问题
宏观
runtime,运行时机制是OC的底层实现,OC的幕后支持者。他是一套C语言库:主要包含类/成员变量/方法的API接口。比如获取类里面的所有成员变量,为类动态添加成员变量,动态改变类的方法实现,为类动态添加新的方法等
runtime有啥用?
在OC对象执行一个方法时,[obj dosomething].runtime会在运行时将他转化为objc_msgsend(obj,@selector(dosomething))。转化为就要执行这个函数,运行时会根据obj对象的isa指针找到类,类内有Cache成员,在近期调用过的方法被缓存在这。运行时机制优先在Cache搜查方法dosomething,猜测使用hash实现,根据名字直接找到方法地址。没有的话就在methodLists列表继续寻找,如果有的话就加入cache。再找不到就像superclass继续寻找直至找到NSObject类(他的父类是nil)。这里说NSObject类是有技术支持的。所有对象的父类都是NSObject包括类(类也是对象)。解释下就是,类也有isa指针他指向类的元类。他的方法列表还有Cache都会存储在这个元类,所以执行类方法是和上述对象方法是一样的。既然是元类他也有父类,他的NSObject类前的父类和对象是一样的但是到了NSObject的元类他的父类就指向了NSObject类。所以我说NSObject是所有的父类。既然类是对象,那么元类也是对象那也有他isa,这样递归下去无穷无尽,所以规定元类的isa都指向NSObject的元类。自己的总结就是superclass用来找.这样runtime形成了一个完美的闭环及NSObject的元类superclass指向NSObject而NSObject父类为nil。所有的对象基类都是NSObject,所有的元类都指向NSObject的元类。
clang -rewrite-objc 源代码文件名 重写
消息转发
基础层
1>能动态产生一个类、一个成员变量、一个方法
2>能动态修改一个类、一个成员变量、一个方法
3>能动态删除一个类、一个成员变量、一个方法
4>能获取类的属性、成员变量、方法.都是这样的形式:class_copyIvarList()
提高层
1>绑定两个对象,一个对象成为另一个的“属性”
2>交换两个方法的实现
3>KVC,KVO
实际上我们编写的所有OC代码,最终都是转成了runtime库的东西,比如类转成了runtime库里面的结构体等数据类型,方法转成了runtime库里面的C语言函数,平时调方法都是转成了objc_msgSend函数(所以说OC有个消息发送机制)
需要了解的:以下这些东西都可以通过runtime获取到
typedef struct objc_class *Class;
struct objc_class {
Class isa; // 指向metaclass
Class super_class ; // 指向其父类
const char *name ; // 类名
long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols; // 存储该类遵守的协议
}
细节
1.swizzle交换两个方法名
大家可以参考下这篇文章swizzle黑魔法
有不理解的留言我在细细讲解
2.巧妙运用AssociatedObject,解决代码冗余
参考我的另一篇runtime实用操作-AssociatedObject
3.KVC,KVO
参考KVO实现原理
就是文章有点零散。这个我务必会整理,并尽量写出源码。
认真的讲解下IMP和method的区别和联系:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
}
typedef id(*IMP) (id,SEL,...);
Method是runtime内部定义的方法,我们在调用runtime库内函数的时候才会放回Method类型,Class中定义了一个变量objc_method_list链表都是objc_method类型的。我个人理解是库内为了方便操作做得进一步对IMP的封装,出了这个范围就不能用了。
对于IMP:id是一个指向objc_object结构体的指针,该结构体只有一个成员isa,所以任何继承自NSObject的类对象都可以用id来指代,因为NSObject的第一个成员实例就是isa。
IMP是一个函数指针,这个被指向的函数包含一个接收消息的对象id调用方法的选标SEL,以及不定个数的方法参数,并返回一个id。也就是说IMP是消息最终调用的执行代码,是方法真正的实现代码。我们可以像在C语言里面一样使用这个函数指针。
NSObject类中的methodForSelector:方法就是这样一个获取指向方法实现IMP的指针,methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法参数类型和返回值类型。
写到这了就在写一下SEL吧
typedef struct objc_selector *SEL;
- (void)helloJianShu:(id)sender{
...
}
NSLog(@"SEL = %s",@selector(helloJianShu:));
打印结果: SEL = helloJianShu:
可以看出SEL的本质是一个以字符串为核心的结构体;
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);//SEL转字符串
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);//字符串转SEL
另外两个隐藏参数self和_cmd.这两个隐藏参数相对的意思是相对于在执行[... ...]方法的时候才有的叫法。实际上他就对应着转换成runtime中的objc_msgSend()中间必须的第一个参数self和第二个参数_cmd。