最近看了些关于object-c(以下简称oc)的runtime文章,了解了些oc的原理,下面阐述下oc的一些机制
oc是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让oc如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。
runtime是开源的。可以去down.
runtime是(基本上,还有部分是汇编,反正很多都是各种宏,in my opinion)由C语言实现的。
runtime的两个版本。(这个不知道要表达个什么,大概是些历史什么的)
归纳了下,oc的核心思想是消息机制,先说在这。
大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects(译者注:这点跟python很像。证据就在类的定义里,有个isa,指向了另外一个类,也叫元类metaclasses),也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。
这里引入一段精辟的看不大清楚的话
(Objective-C is a class-based object system. Each object is an instance of some class; the object's isa pointer points to its class. That class describes the object's data: allocation size and ivar types and layout. The class also describes the object's behavior: the selectors it responds to and instance methods it implements.)
Object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass
这个的定义在runtime.h里:
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;
从中可以清楚的看到一个Class到底里面是些什么东西,isa就是指向元类的类指针,父类,名字,还有一堆我们关心的方法,协议,缓存数据等(还有几个,我不好意思说)。
可以看到方法协议,什么的都用的还是struct。
下面我捡一些好听的来说。
当然我们肯定关心的是方法,首先看下方法列表的定义:
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;
} OBJC2_UNAVAILABLE;
大概是什么捏,我也看不大明白,没事继续看下方法的具体定义:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
这下差不多了,这是具体方法的定义。这个内部的剖析,等下在说,先绕开往下说下objc_cache。
看看objc_cache的定义:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
} /* GrP fixme should be OBJC2_UNAVAILABLE, but isn't because of spurious warnings in [super ...] calls */;
等下,这有个Method有点蕾丝objc_method哈,于是找到这个定义:
typedef struct objc_method *Method;
联系上鸟。
好吧,不绕了,讲讲方法吧,这里也就是上面要涉及但憋回去的消息机制。
说起方法就不得不说他的机制,oc里的方法就是消息,Look:
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"dd", nil];
[arr addObject:@"k"];
objc_msgSend(arr, @selector(addObject:),@"pp");
NSLog(@"arr:%@",arr);
他们可以等价,调用array的insertObject的方法,参数分别是foo,5。上面就是典型的oc试,下面就是等价的消息模式,亲侧可行.
跑了有点裤子穿偏了,回过神来继续说这个objc_method,逐个说下
SEL 类成员方法的指针:
可以理解 @selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Object-C的类不能直接应用函数指针,这样只能做一个@selector语法来取.
可以用这个NSSelectorFromString直接取,NSStringFromSelector反转。
简单说就是函数的地址,只不过函数的地址和函数实现的地址不在同一个地方。
method_types 描述方法的参数列表. 在运行时注册选择器时使用.那时候方法名就会包含方法的参数列表
method_imp 之 IMP 是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时(Objective-C runtime)寻找匹配此消息的IMP,然后调用它;但有些时候我们希望获取到IMP进行直接调用。
简单来说method_imp就是函数实现的代码块地址。
方法大概就这些,接下来就说下,对象是怎么找到方法的,这里涉及到一些原理,我们可以假装不知道的。
当一个对象去执行某一个方法的时候,首先去是cache里找method,因为这个最快(他们都是存到内存中的hash表里的),当找不到的时候才去那个objc_method_list里找,如果还没有就去super_class里找,这样循环到根上也就是NSObject,还是没有就不好意思crash了。
类方法也一样,先去元类里找,找不到就去元类的父亲哪找,直到找到NSObject的元类,还是找不到就再去找NSObject的父亲哪,但NSObject是root类了,他的父亲就是他自己,就回到NSObject里方法列表里找,找不到就crash了。
我们在这里扩展下Category:
这里我们要引申下,oc里类的成员变量,方法都是存在类固定的一个位置。
我们重温下类的定义那,方法是放在objc_method_list这个地址指向的struct里。
struct objc_method_list **methodLists
而成员变量则是存在objc_ivar_list这个struct里。
struct objc_ivar_list *ivars
这里有个伏笔,就是这个类的大小,是由其他部分+成员变量struct的大小(注意他是成员变量的所有大小之和)和方法列表指针的地址组成(他仅仅是一个地址,才不管地址指向的struct里的大小)。
btw:由此可以看出成员变量,都可以通过对象的地址+成员变量的偏移量来找到。
类在runtime的时候分配内存就是根据这些元素分配一个对应大小的空间,一旦编译完成,就固定了。
这就是为什么Category不能动态添加成员变量,但可以添加方法的原因,因为成员变量实际上参与了类大小的容积,而方法却没有,只有一个指向他们的指针参与了。
联合存储(Associated)这个东西,虽然表面上可以添加属性,但实际上他和成员变量不是一个概念,他仅仅是存在类的一个hashmap的单例里,不想成员变量直接存在objc_ivar_list里,效率也肯定不及成员变量的存取速度,他们是直接通过类地址+偏移量获取,而联合存储的东西,则要在hashmap里去遍历,所以联合存储不宜滥用。
接下来说下平常coding有用的东西了
class开头的方法是用来修改和自省classes。方法如class_addIvar, class_addMethod, class_addProperty和class_addProtocol允许重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList能拿到一个class的所有内容。而class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty返回单个内容。
假设我要取回去某个对象的属性或者方法:
uint attrCount = 0,selfCount = 0;
objc_property_t *attrs = class_copyPropertyList([self class], &attrCount);
Method *method = class_copyMethodList([self class], &selfCount);
for (int i=0;i<=count;++i) {
objc_property_t property = attrs[i];
NSLog(@"key:%s,value:%@",property_getName(property),[self valueForKey:[NSString stringWithFormat:@"%s",property_getName(property)]]);
}
for (int i=0;i<=count1;++i) {
SEL sel = method_getName(method[i]);
IMP imp = method_getImplementation(method[i]);
NSLog(@"sel:%s",sel_getName(sel));
}
这些都不算啥,现在说点动态的东西,比如动态添加属性,动态添加方法。
这里其实我们需要用class_addMethod就可以了。
这里有必要说下
+(BOOL)resolveInstanceMethod:(SEL)sel;
和
+(BOOL)resolveClassMethod:(SEL)sel;
字面上来说,一个是对象方法,一个是类方法,都是消息转发机制前调用的。
意思是如果我们劫持重写这个,就可以添加一切我们想要的方法。
下面拿一个世界上最干净的类之一来说事:
@interface DB : NSObject
@end
@implementation DB
@end
如果我现在
DB *d = [DB new];
[d alert];
有人要说我傻x了,好吧,我牺牲自己引入下文:
对,我们上面说到了劫持,所以我就继承了这个方法,并重写了下
ex:
//这个就是alert函数实现的地方。
ps:这里得多说几句,第一个参数是消息的发起方,就对象,self就是方法的指针。必须的,后面的参数就跟着依次。
void define IMPForAlert(id self,SEL _cmd){
NSLog(@"xxxx");
}
ps:这里可以通过重写这个,当然也可以在外面直接用class_addMethod.
+(BOOL)resolveInstanceMethod:(SEL)sel{
//当在调用alert的时候,会自动添加这个方法进去。
if(sel == @selector(alert)){
//这里就劫持了并添加并实现了我想要的方法。最后那个参数是个奇葩见(1)说明。
class_addMethod([self class],sel,(IMP)defineIMPForAlert,[@"v@:" UTF8String]);
return YES;
}
return [super resolveClassMethod:sel];
}
哈。我的对象就具备了alert的方法。也就是说我的对象就可以来发送alert的消息。好吧,就是说我[d performSelector:@selector(alert)];就合法了(注意这里不能直接写[d alert],因为我那个是动态添加的应该是说要在运行的时候才有alert方法,所以IDE并不知道,编译也不会让你过)。尽管我什么alert都没定义。
记得上面说过属性也可以,是的,属性也可以。但网上的栗子都没成熟,所以,我就先放一个我测试过了的(看起来有点不舒服的栗子)。
NSString* name(id self,SEL _cmd){
return objc_getAssociatedObject(self, @"name");
}
void setName(id self,SEL _cmd,NSString *name){
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
class_addMethod([DB class],@selector(name),(IMP)name,[@"@@:" UTF8String]);
class_addMethod([DB class],@selector(setName:),(IMP)setName,[@"v@:@" UTF8String]);
这样我就可以
[d performSelector:@selector(setName:) withObject:@"ddd"];
NSLog(@"self.name:%@",[d performSelector:@selector(name)]);
下面是些扩展用法。
@interface CC : NSObject
-(void)say;
-(void)sing;
@end
@implementation CC
-(void)say{
NSLog(@"os");
}
-(void)sing{
NSLog(@"sn");
}
@end
BB *b = [BB new];
//我让b去继承cc的
object_setClass(b, [CC class]); //实现方法。见(2)
//于是b就有了say的方法
[b performSelector:@selector(say)];
CC *c = [CC new];
//获取say的方法
Method methodSay = class_getInstanceMethod([c class], @selector(say));
Method methodSing = class_getInstanceMethod([c class], @selector(sing));
//交换2个方法
method_exchangeImplementations(methodSay, methodSing); //实现方法。见(3)
//于是有了结果的变化
[c say];
[c sing];
//b也跟着变了
[b performSelector:@selector(say)];
以上。