小白看runtime(一)基本概念
最近网上看了不少runtime的文章,自己总结一些也摘抄一些,避免过快的忘记
runtime
引入
当我们在编写OC代码时,或者是在看博客的时候,或者在最初学习OC语法的时候,当去调用一个方法时不止一次的看到或者被告知这种行为不是调用方式而是在“发消息” 这个概念,但是很少或者没有去了解究竟是为什么。那究竟为什么把“方法调用”称作发消息呢?
这就要从OC语言讲起,众所周知 OC是一门动态语言,什么是动态语言呢?
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的JavaScript便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。
既然有动态语言那肯定有静态语言:
静态类型语言的类型判断是在运行前判断(如编译阶段),比如C#、java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,等特殊语法。
所以不同于静态类型语言 OC总是想办法把一些决定工作从编译连接推迟到运行时,需要编译器和运行时系统 (runtime system) 一起来执行编译后的代码。
C语言是一门静态语言,C语言是OC的基石,运行时机制(runtime system)
不仅使得OC保持的C语言编译时的特性(如类型检查) 还借鉴了smalltalk机制为其添加了运行时机制也就是我们要介绍的runtime机制,runtime机制使得C语言具有了面对对象的特性,所以就有了OC。
runtime是由C和汇编编写的,目前有两个版本 modern 和 legacy 现在使用的Object-C 2.0是modern版本的runtime系统,只能运行在iOS和OS X10.5之后的64位程序。而OS X较老的32位程序仍然采用Object-c 1 中legacy版本的runtime系统。
总结来说 OC是动态类型语言许多事情在编译时无法确定,需要等在运行时。而造成这种现象的因为runtime机制的存在。
学习runtime先从老生常谈的objc_msgSend函数谈起
objc_msgSend函数
新建一个Person类有实例方法和类方法
-(void)instanceMethod;
+(void)classMethod;
@interface Person : NSObject
-(void)instanceMethod;
+(void)classMethod;
@end
-(void)instanceMethod{
NSLog(@"instanceMethod is print");
}
+(void)classMethod{
NSLog(@"classMethod is print");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *per = [[Person alloc] init];
//很简单发送'调用'两个方法 分别是类方法和实例方法
[Person classMethod];
[per instanceMethod];
}
return 0;
}
输出:
2017-04-29 11:13:21.493311+0800 runtime[5163:1579843] classMethod is print
2017-04-29 11:13:21.493433+0800 runtime[5163:1579843] instanceMethod is print
在main.m目录下 使用clang -rewrite-objc main.m
编译mian.m文件生成main.cpp
摘抄一些重要的代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("classMethod"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)per, sel_registerName("instanceMethod"));
}
return 0;
}
我们可以看到一共出现四次objc_msgSend
函数alloc 和init时候以及'调用'我们自己设置的方法
除去外衣我们只看函数的参数
objc_msgSend(objc_getClass("Person"), sel_registerName("classMethod"));
objc_msgSend(per, sel_registerName("instanceMethod"));
在源码里面查看
id objc_msgSend(id self, SEL op, ...)
函数声明长这样子
第一个参数类型为id,大家对它都不陌生,它是一个指向类实例的指针
长这样:
typedef struct objc_object *id;
学过C语言很快就能看出id是一个指向objc_object结构体的指针。
那我们就查看下结构体里有些什么呢?
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
只有一个Class类型的指针
那Class又是个什么鬼?
typedef struct objc_class *Class;
原来是个指向objc_class结构体的指针啊
既然是个结构体那我们看下结构体有些什么
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#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_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
感觉东西好多而且加上这么多的概念有些乱啊
我们就用简单的图来描述思路
是不是思路清晰了许多?
我们着重来看下
methodLists
老规矩看看是个什么东西
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
结构体methodLists
是指向objc_method_list
指针的指针。
其中的struct objc_method method_list[1]
看看官方的描述为:长度可变的结构体数组
也就是说可以动态修改*methodLists的值来添加成员方法.
简单的理解就是methodLists
是方法的数组
里面存着类或者实例的方法
来看看objc_method
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
是不是非常眼熟?SEL objc_msgSend
的第二个参数
来看看他的真面目
SEL
typedef struct objc_selector *SEL;
一个指向objc_selector
结构体的指针
你可以理解为:SEL是一个仅仅包含方法名字的字符串
IMP
typedef id (*IMP)(id, SEL, ...);
恩他是个函数指针,它会指向一个函数。
当发送消息时,会执行一段代码,哪里去找那段代码呢?由IMP来指定。
如果你思路清晰的话很快可以看出这货的参数和objc_msgSend
的参数相同
确定一个IMP需要id和SEL。换句话说给我一个id,和一个SEL(方法名)就能确定一个方法的IMP(实现)。
到现在一堆的概念头都快炸了我们来完善一下思路结构图
顺着思路是不是觉得思路清晰了一些,对结构有了一些直观认识。
但是有好多的东西都没有介绍 ,慢慢来,一口吃不成胖子。
method_types
表示的是方法的返回值和参数编码
在runtime.h
中我们可以找到一些有关于的函数
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
IMP class_replaceMethod(Class cls, SEL name, IMP imp,
const char *types)
添加方法和替换方法
下面写一个简单例子来体会它存在的意义,手写要添加一个方法需要几个条件:方法名(SEL)、方法的实现地址(IMP)、方法所属的类(Class)。
幸运的是runtime API为我们提供了便捷的实现IMP的Block
IMP imp_implementationWithBlock(id block)
那我们就现在手动创建一个类并未其添加一个方法test,并尝重写description方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建继承自NSObject类的People类
Class People = objc_allocateClassPair([NSObject class], "People", 0);
//将People类注册到runtime中
objc_registerClassPair(People);
//注册test: 方法选择器
SEL sel = sel_registerName("test:");
//函数实现
IMP imp = imp_implementationWithBlock(^(id this,id args){
NSLog(@"方法的调用者为 %@",this);
NSLog(@"参数为 %@",args);
return @"返回值测试";
});
//向People类中添加 test:方法;函数签名为@@:@,
// 第一个@表示返回值类型为id,
// 第二个@表示的是函数的调用者类型,
// 第三个:表示 SEL
// 第四个@表示需要一个id类型的参数
class_addMethod(People, sel, imp, "@@:@");
//替换People从NSObject类中继承而来的description方法
class_replaceMethod(People,@selector(description), imp_implementationWithBlock(^NSString*(id this,...){
return @"我是Person类的对象";}),
"@@:");
//完成 [[People alloc]init];
id p1 = objc_msgSend(objc_msgSend(People, @selector(alloc)),@selector(init));
//调用p1的sel选择器的方法,并传递@"???"作为参数
id result = objc_msgSend(p1, sel,@"???");
//输出sel方法的返回值
NSLog(@"sel 方法的返回值为 : %@",result);
//获取People类中实现的方法列表
NSLog(@"输出People类中实现的方法列表");
unsigned int methodCount;
Method * methods = class_copyMethodList(People, &methodCount);
for (int i = 0; i<methodCount; i++) {
NSLog(@"方法名称:%s",sel_getName(method_getName(methods[i])));
NSLog(@"方法Types:%s",method_getDescription(methods[i])->types);
}
free(methods);
}
return 0;
}
参考:
http://www.jianshu.com/p/c0157110caa5
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/