Objective-C的runtime
原文链接 https://gaoxiaosong.github.io/2014/04/27/objective-c-runtime.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
Objective-C是一门简单的语言,95%是C语言。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。
- runtime是开源的。可以去下载:http://opensource.apple.com/tarballs/objc4/objc4-437.1.tar.gz。
- runtime是由C语言实现的。
- runtime的两个版本。
归纳了下,Objective-C的核心思想是消息机制,先在这里讲下Objective-C里类的原理。首先我们看他对类的定义:
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
大多数面向对象的语言里有classes和objects的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects,这点跟python很像,也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。
object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型:metaclasses。一个metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass这个的定义在runtime.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.
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到底里面是些什么东西,父类,名字,还有一堆我们关心的方法,协议,缓存数据等。可以看到方法协议,什么的都用的还是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;
讲讲方法吧,这里也就是上面要涉及但憋回去的消息机制。说起方法就不得不说他的机制,Objective-C里的方法就是消息:
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"dd", nil];
[arr addObject:@"k"];
objc_msgSend(arr, @selector(addObject:),@"pp");
NSLog(@"arr:%@",arr);
他们可以等价,调用array的insertObject的方法,参数分别是foo, 5。上面就是典型的Objective-C模式,下面就是等价的消息模式,前提是你得引入消息模式的文件。
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
继续说这个objc_method,逐个说下SEL类成员方法的指针。可以理解@selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Objective-C的类不能直接应用函数指针,这样只能做一个@selector语法来取。可以用这个NSSelectorFromString直接取,NSStringFromSelector反转。
简单说就是函数的地址,只不过函数的地址和函数实现的地址不在同一个地方。
method_types描述方法的参数列表. 在运行时注册选择器时使用。那时候方法名就会包含方法的参数列表。
method_imp之IMP是"implementation"的缩写,它是Objetive-C方法实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时寻找匹配此消息的IMP,然后调用它。但有些时候我们希望获取到IMP进行直接调用。
简单来说method_imp就是函数实现的代码块地址。
方法大概就这些,接下来就说下,对象是怎么找到方法的,这里涉及到一些原理,我们可以假装不知道的。
当一个对象去执行某一个方法的时候,首先是去cache里找method,因为这个最快(他们都是存到hash里的),当找不到的时候才去那个objc_method_list里找,如果还没有就去super_class里找,这样循环到根上也就是NSObject,还是没有就不好意思crash了。
接下来说下平常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 *methods = class_copyMethodList([self class], &selfCount);
for (int i = 0; i <= attrCount; ++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 <= selfCount; ++i) {
Method *method = methods[i];
SEL sel = method_getName(method);
IMP imp = method_getImplementation(method);
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];
对,我们上面说到了劫持,所以我就继承了这个方法,并重写了下。第一个参数是消息的发起方,就对象,self就是方法的指针。必须的,后面的参数就跟着依次。
// 这个就是alert函数实现的地方。
void defineIMPForAlert(id self,SEL _cmd) {
NSLog(@"xxxx");
}
这里可以通过重写这个,当然也可以在外面直接用class_addMethod。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 当在调用alert的时候,会自动添加这个方法进去。
if (sel == @selector(alert)) {
// 这里就劫持了并添加并实现了我想要的方法。最后那个参数是个奇葩见说明。
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]); //实现方法。
// 于是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); //实现方法。
// 于是有了结果的变化
[c say];
[c sing];
// b也跟着变了
[b performSelector:@selector(say)];
说明:
1、方法缩略写法的字符含义:
- c char
- i int
- s short
- l long
- l is treated as a 32-bit quantity on 64-bit programs.
- q long long
- C unsigned char
- I unsigned int
- S unsigned short
- L unsigned long
- Q unsigned long long
- f float
- d double
- B C++ bool or a C99 _Bool
- v void
- * character string (char *)
- @ object (whether statically typed or typed id)
- # class object (Class)
- : method selector (SEL)
- [array type] array
- {name=type...} structure
- (name=type...) A union
- bnum bit field of num bits
- ^ type pointer to type
- ? An unknown type (among other things, this code is used for function pointers)
貌似@:组成了返回类型和形参的分割线。
2、设置子类继承关系:
Class object_setClass(id obj, Class cls)
{
if (obj) {
Class old;
do {
old = obj->isa;
} while (! OSAtomicCompareAndSwapPtrBarrier(old, cls, (void*)&obj->isa));
return old;
}
else return Nil;
}
3、交换两个方法名称对应的实现:
void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
IMP m1_imp;
struct old_method *m1 = _method_asOld(m1_gen);
struct old_method *m2 = _method_asOld(m2_gen);
if (!m1 || !m2) return;
if (m1->method_name == (SEL)kIgnore || m2->method_name == (SEL)kIgnore) {
// Ignored methods stay ignored. Now they're both ignored.
m1->method_imp = (IMP)&_objc_ignored_method;
m2->method_imp = (IMP)&_objc_ignored_method;
return;
}
OSSpinLockLock(&impLock);
m1_imp = m1->method_imp;
m1->method_imp = m2->method_imp;
m2->method_imp = m1_imp;
OSSpinLockUnlock(&impLock);
}