刚学OC的时候,不知道runtime这个东西,也不知道动态是什么概念。后来知道了runtime,但是看了一眼,什么class结构体、isa指针,不是说是“运行时”吗,这些跟运行什么关系?根本无法理解runtime是个什么东西,就是大脑里完全无法建立概念。
学OpenGL,一开始接触到“管线”这个词,看到说绘制管线,脑子里想到的是一根根的线、电路之类的,和图形绘制什么关系?后来理解,其实是“流水线”,绘制图形的过程像一个工厂流水线一样,流程基本是固定,但我们可以通过脚本对流程的一个个环节做处理。
同样,runtime的理解,我觉得不要受“运行时”这个翻译影响,也不要管“runtime”这个单词(至少一开始不要管)。首先定性,runtime是一个库、一个系统。runtime这个名字,只是这个系统要干的事,它的目的。
文档里介绍runtime的第一句话:
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.
OC想要把决定从编译期推迟到运行期,就是尽可能的动态。这是OC的特性,而runtime就是用来让OC具有这样的性质,OC是建立在runtime之上的,而runtime之下是C。
我的理解是,C本身不是动态的,也不适合面向对象的编程,那我想要有一个动态的语言,怎么办?我来弄一个库,它里面有一些数据结构,有一些方法,可以在C的基础之上构建一个新的语言,让我可以享受想要的这些性质。所以runtime是一个中间层,连接着C和OC。
所以看了runtime的代码后,会了解类、对象等等本质上是啥,了解OC里做的处理实际是干了什么,至少是一定程度的本质。
类,实际是一个结构体,它里面有变量保存父类,有保存方法列表、变量列表等等;对象也是一个结构体,只不过有个变量指向它的类结构体。而调用方法,实际是调用消息发送。
通过runtime里的方法,可以获取类的属性、方法,可以添加方法,甚至可以更改对象的类。一个对象的类是什么,就是它的isa变量存的class类型的变量的值,那把值改掉就好了。
runtime的存在,是用来支撑OC的运行和它的特性的,而不是用来帮助我们写iOS程序的,至少这不是它该有的意义。比如修改一个对象的类,可以做到,这种事有必要吗?还有,可以修改一个method的函数实现(IMP),让你代码里写的是调用A方法,但实际执行的是B函数。这样不就违背了原本面向对象的编程了?类A的对象A1,最后运行起来类B的方法,这样不是瞎搞?
但又觉得如果编程用不到,那么OC这么搞有什么意义?或许我该想想动态的目的,为什么要尽力在运行时做决定?这一切都是为了这个目的而设计的吧!
//9.13更,runtime库的函数们
以类为核心,类可以构建生成对象;而类本身具有方法、成员变量和属性;然后,我们可以通过类别和协议给类在cocoa层面添加属性和变量。由这些行为,把主要的类型关联起来,即类、方法、成员变量、属性、类别、协议。
1.类和对象的关联
类的结构原型:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //指向元类metaClass
#if !__OBJC2__
Class super_class //父类
const char *name //名字
long version
long info
long instance_size
struct objc_ivar_list *ivars //变量列表
struct objc_method_list **methodLists //方法列表
struct objc_cache *cache //方法调用缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
对象原型:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
对象的isa 指向它自身的类,也就是由这个isa把类和对象关联起来。
而类本身也有isa变量,这个是指向metaClass的,对于一个类,实例方法存放自身的方法列表里,类方法存放在metadClass的方法列表里;成员变量也是一样。
类和它的父类是通过superclass关联起来,这样类-父类-对象整个的都关联起来了。
2.方法调用机制:
对于[A methodB:xxx],是怎么一个过程?
方法调用实际是给对象发送消息,
[A methodB:xxx]
就是,会把对象作为第一个参数,方法名构建SEL作为第二个参数,如有更多参数放在后面。
objc_msgSend(A, @selector(methodB:))
A是对象,可以由A取到它的类,然后取到方法列表,匹配方法,找到了调用,找不到到父类中继续找;用代码过程类似这样:
NSObject *A = [[NSObject alloc]init]; //调用方法的对象
SEL selector ; //调用的方法名构建的SEL
Class classA = object_getClass(A);//获取对象的类
IMP findFunc = NULL;
while (classA) {
IMP func = class_getMethodImplementation(classA, selector);
if (func) {
findFunc = func;
break;
}
/*
//或者,获取method再获取IMP;method有实例方法和类方法之分,分成两个函数分别获取
Method m = class_getInstanceMethod(classA, selector);
if (m) {
findFunc = method_getImplementation(m);
break;
}
m = class_getClassMethod(classA, selector);
if (m) {
findFunc = method_getImplementation(m);
break;
}
*/
classA = class_getSuperclass(classA); //获取父类
}
然后,为什么要获取IMP?
首先IMP是method的一部分,
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
然后IMP本身:
// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
这个宏没研究,运行试了下,是下面一种定义。
也就是IMP本质是一个函数指针,而且,第一个参数是调用这个方法的对象,第二个参数是这个方法的SEL(objc_method中的method_name,也是消息发送里的selector).
使用IMP类似这样:
typedef id(*myFunc)(id object,SEL selector,...);
myFunc func3 = (id (*)(id,SEL,...))findFunc;
func3(A,selector);
所以对于一个方法objc_method,本身包含3部分,一个方法名,一个参数类型列表,和一个实际的函数实现。
所以如果把一个方法的IMP更改了,表面上,你还是在调用[A methodB:xxx],但实际执行的代码变成了另一个。在某些特殊情况下会用到吧,比如看不了别人源码时,把执行过程替换成自己的......
3.获取、修改属性和变量
对于有多个的成员,像属性、变量、协议,都会提供两个方法,一个是根据名字获取特定的,一个是返回所有的列表,如:
objc_property_t pro = class_getProperty(classA, "xxx");
unsigned int count; //count用来返回个数
objc_property_t* proList = class_copyPropertyList(classA, &count);
修改添加:
objc_property_attribute_t attri;
unsigned int count;
class_addProperty(classA, "name", &attri, count);
class_replaceProperty(classA, "name",&attri, count);
objc_property_attribute_t是个结构体,包含name,value两个变量;
找个类测试了一下:
//类Book的一个属性:
@property (nonatomic,copy) NSString* name;
objc_property_t p = class_getProperty([Book class], "name");
unsigned int count;
objc_property_attribute_t *attri = property_copyAttributeList(p, &count);
for (int i = 0; i<count; i++) {
printf("%s = %s\n",attri[i].name,attri[i].value);
}
输出结果为:
T = @"NSString"
C =
N =
V = _name
开头必须为T,值是属性的名字,"@"在Type Encodings里代表对象,如果是int那类型就是“i”; 结尾必须是V,值为属性的变量名(属性名为name,实际生成变量名是_name);其他的在文档的Declared Properties有详细说明。