原创Blog,转载请注明出处
http://blog.csdn.net/hello_hwc?viewmode=list
我的stackoverflow
前言:之前一篇文章里,我详细的讲解了一些基本关键词以及基本概念,这里再简要列出来,以防有些同学看不懂。
_cmd 每个OC方法都具有的参数
但是, 我仍然推荐先看一下前一篇文章
Objective C语言把能在运行期做的事情就推迟到运行期再决定。这就意味着,Objective C不仅需要一个编译器,而且需要一个运行期环境。这个运行期环境就是Runtime。
Runtime是用C和汇编写的,并且它是开源的。所有的Apple开源代码都可以在http://www.opensource.apple.com上找到。
在OC中,方法调用称为向对象发送消息,为什么这么叫呢?我们先看个例子
[receiver message]
那么,[receiver message]
编译后是什么呢?
编译后是这个样子的
objc_msgSend(receiver, selector)
我们看看这个方法的文档
id objc_msgSend(id self, SEL op, ...)
参数
上文提到了,一个OC方法被编译成objc_msgSend,那么,Runtime如何找到方法的执行体呢?
因为OC中存在一种对象叫做类对象(Class Object),类对象中保存了方法的列表,superClass等信息。
类对象的定义
typedef struct objc_class *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;
可以看到这一行
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
那么,OC的实例对象又是如何找到类对象呢?从上文的第一部分,我们可以看到,objc_msgSend这个C函数输入参数包括,id类型的self,SEL类型的_cmd,以及参数列表。很直观,id类型中一定存在一个可以找到类对象的指针。我们来看看id定义
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
}
现在很清楚了,实例对象通过isa找到类对象。
我们知道了
那么,实际的方法执行体(Method)到底是什么东西?
看看Method的定义
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
Method保存了
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
我们来看一个例子,
定义一个
@interface CustomObject : NSObject
-(NSString *)returnMeHelloWorld;
@end
@implementation CustomObject
-(NSString *)returnMeHelloWorld{
return @"hello world";
}
@end
我们先只看调用这一行
NSString * helloWorld = [obj returnMeHelloWorld];
上文提到了,实例方法,那么类方法又是如何处理的呢?
由上文我们知道,实例对象由类对象创建,类对象保存了实例对象的方法列表。
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 OBJC_ISA_AVAILABILITY;
这个isa指向的一个Class类型,就是保存了类方法的地方。这个Class类型的东西就是类元对象
类对象由类元对象(meta class)创建,类元对象保存了类对象的类方法,类名字等信息。
类元对象和类对象都是Class类型,只不过服务的对象不同罢了。
到这里,有同学可能要问了,类元对象由什么创建呢?
答案: NSObject类元对象
那么NSObject类元对象由什么创建呢?(就喜欢刨根问底)
答案: NSObject类元对象自身
我们来看一个例子,验证下这一些列的讲解,也加深一下印象
Class class = [CustomObject class];//类对象
Class metaClass = object_getClass(class);//类元对象
Class metaOfMetaClass = object_getClass(metaClass);//NSObject类元对象
Class rootMataClass = object_getClass(metaOfMetaClass);//NSObject类元对象的类元对象
NSLog(@"CustomObject类对象是:%p",class);
NSLog(@"CustomObject类元对象是:%p",metaClass);
NSLog(@"metaClass类元对象:%p",metaOfMetaClass);
NSLog(@"metaOfMetaClass的类元对象的是:%p",rootMataClass);
NSLog(@"NSObject类元对象%p",object_getClass([NSObject class]));
可以看到Log
CustomObject类对象是:0x10248aed0
CustomObject类元对象是:0x10248aea8
metaClass类元对象:0x102ce5198
metaOfMetaClass的类元对象的是:0x102ce5198
NSObject类元对象0x102ce5198
在面相对象中,我们知道,子类调用一个方法,如果子类没有实现,会查找基类。OC作为一种面相对象的语言,当然支持这些。
我们依然写一段示例代码
Class class = [CustomObject class];//类对象
Class superClass = class_getSuperclass(class);//基类对象NSObject
Class superOfNSObject = class_getSuperclass(superClass);//NSObject类元对象
NSLog(@"CustomObject类对象是:%p",class);
NSLog(@"CustomObject类superClass是:%p",superClass);
NSLog(@"NSObject的superClass是:%p",superOfNSObject);
可以看到log
CustomObject类对象是:0x101792e88
CustomObject类superClass是:0x101fec170
NSObject的superClass是:0x0
由此,我们可以绘制继承关系图(图中nil的地址不对,不重新绘制了)
综合上述两个例子,我们绘制出完整的isa和superClass图
通过上文我们知道,在OC中,方法是通过isa指针,查找Class中的Method list来查询的。而一个类往往会实现很多方法,每次调用都查询一次Method list的分发表(dispatch table)的代价是很高的(因为,这种查询可能每个RunLoop就执行上亿次)。这也就引入了Class Cache.
Class Cache认为,当一个方法被调用,那么它之后被调用的可能性比较大。
举个例子,我们常见的alloc,init,调用顺序如下
CustomObject * obj = [[CustomObject alloc] init];
这里,再提一下alloc,init两个方法
alloc方法的文档
初始化isa,其他所有属性被设为0
init
NSObject的init返回self,其余的子类要调用[super init]进行必要的初始化工作。
为什么要放在一起写?
因为alloc和init有可能返回不同的对象
id a = [NSMutableArray alloc];
id b = [a init];
NSLog(@"%p",a);
NSLog(@"%p",b);
输出
0x7fc550400fb0
0x7fc5505523a0
NSObject根类除了定义了一些基本的方法,例如,description,alloc。也定义了一些Runtime的基础方法,这里把一些不常用的列出来。并且,从上文的isa和superclass图来看,NSObject是整个消息机制的核心。
//两个Runtime的接口-这个之后在method Swizzling中详细讲解
+initialize //在一个类接收第一条消息之前的
+load //在一个类对象加载到Runtime的时候调用
//检查是否可以向实力对象发送某消息
+(BOOL)instancesRespondToSelector:(SEL)aSelector
-respondsToSelector:
//向对象发送消息
- (id)performSelector:(SEL)aSelector
- performSelector:withObject:
- performSelector:withObject:withObject:
...
//动态消息转发处理机制
+resolveInstanceMethod:
- forwardingTargetForSelector:
- forwardInvocation:
到现在觉得这些函数熟悉吗?不熟悉,我还是建议回去好好研读下NSObject的文档。
这里,我们再一次抢到,Objective C的方法就是前两个参数是self和_cmd的C方法
void ocMethod(id self, SEL _cmd) {
// implementation ....
}
在Objective中,对一个对象发送一个它没有实现的Selector是完全合法的,这样做可以隐藏某一个消息背后实现,也可以模拟多继承(OC不支持多继承)。这个机制就是动态转发机制。
主要包括三个函数
+resolveInstanceMethod:
- forwardingTargetForSelector:
- forwardInvocation:
动态方法的机制第一步,类自己处理
使用resolveInstanceMethod
这个方法的文档
动态为实例方法提供一个实现
这个方法在Objective C消息转发机制之前被调用。如果respondsToSelector
或者instancesRespondToSelector:
被调用,可以为改Selector提供动态的实现者。
举个例子
#import "ViewController.h"
#import
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[self performSelector:@selector(dynamicSelector) withObject:nil];
#pragma clang diagnostic pop
}
void myMehtod(id self,SEL _cmd){
NSLog(@"This is added dynamic");
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (sel == @selector(dynamicSelector)) {
#pragma clang diagnostic pop
class_addMethod([self class],sel, (IMP)myMehtod,"v@:");
return YES;
}else{
return [super resolveInstanceMethod:sel];
}
}
@end
简单介绍下,class_addMethod这个函数
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
作用,为一个类添加实例方法
参数
返回值
第二部(第一步不能处理的情况下),调用forwardingTargetForSelector来简单的把执行任务转发给另一个对象,到这里,还是廉价调用
代码
#import "ViewController.h"
#import
@interface CustomObject : NSObject
-(void)dynamicSelector;
@end
@implementation CustomObject
-(void)dynamicSelector{
NSLog(@"hello world");
}
@end
@interface ViewController ()
@property (strong,nonatomic)CustomObject * myObj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myObj = [[CustomObject alloc] init];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[self performSelector:@selector(dynamicSelector) withObject:nil];
#pragma clang diagnostic pop
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(dynamicSelector) && [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
return self.myObj;
}else{
return [super forwardingTargetForSelector:aSelector];
}
#pragma clang diagnostic pop
}
@end
第三步,当前两步都不能处理的时候,调用forwardInvocation转发给别人,返回值仍然返回给最初的Selector
代码
#import "ViewController.h"
#import
@interface CustomObject : NSObject
-(void)dynamicSelector;
@end
@implementation CustomObject
-(void)dynamicSelector{
NSLog(@"hello world");
}
@end
@interface ViewController ()
@property (strong,nonatomic)CustomObject * myObj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myObj = [[CustomObject alloc] init];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[self performSelector:@selector(dynamicSelector) withObject:nil];
#pragma clang diagnostic pop
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
if ([self.myObj respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:self.myObj];
}else{
[super forwardInvocation:anInvocation];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [CustomObject instanceMethodSignatureForSelector:aSelector];
}
@end
总结
消息转发机制使得OC可以进行”多继承”,比如有一个消息中心负责处理消息,这个消息中心很多个类都要用,继承或者聚合都不是很好的解决方案,使用单例看似可以,但单例的缺点也是很明显的。这时候,把消息转发给消息中心,无疑是一个较好的解决方案。
Runtime的文档
Runtime Programming Guide
Objective C Runtime Reference
Foundation 中的Runtime方法
NSObject文档(建议没有研读过的同学好好研究下每一个方法)
总的来说,Runtime能够提供增加,修改,删除,查询类(属性方法),协议,block。
举个例子-用SEL属性来动态执行方法
经常会根据后台的Json来执行不同的方法,一两个还好,如果是10个20个,用if else或者switch都是很坑爹的设计。
#import "ViewController.h"
#import
@interface ViewController ()
@property (nonatomic)SEL methodToInvoke;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
int a = arc4random() % 2;
_methodToInvoke = NSSelectorFromString([[self numToSelector] objectForKey:@(a)]);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:_methodToInvoke withObject:nil];
#pragma clang diagnostic pop
}
-(NSDictionary *)numToSelector{
return @{@(0):@"method1",
@(1):@"method2"};
}
-(void)method1{
NSLog(@"method1");
}
-(void)method2{
NSLog(@"method2");
}
@end
后续会继续讲解Runtime的应用,欢迎关注我的博客
http://blog.csdn.net/hello_hwc