Runtime
Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着OC需要Runtime系统来动态创建类和对象,进行消息发送和转发。
1、 Runtime机制
在介绍OC的Runtime机制前,我们先了解一些Runtime机制所依赖的一些数据结构。
1.1、 NSObject
@interface NSObject <NSObject>
{
Class isa;
}
//…
@end
在OC中,基类为NSObject中,只有一个属性为isa,那么isa到底是什么数据类型呢?在objc.h文件中,定义如下:
typedef struct objc_class *Class;
显而易见,isa是一个结构体指针,该指针指向了实例对象的Class对象(类对象)。为了防止混淆,我们在此约定,实例对象是指某个类的一个实例,如NSString的一个实例;Class对象指指向objc_class结构体的指针。
1.2、 Class
通过1.1节我们知道Class其实就是一个结构体指针,其定义如下:
typedef struct objc_class *Class;
struct objc_class
{
Class isa; //指向mateclass
Class super_class;//指向父类
const char *name; //类名
long version; //版本信息,默认为0
long info; //标识信息,区别一般类,还是metaclass
long instance_size; //实例变量的大小
struct objc_ivar_list *ivars; //存储所有成员变量的地址
struct objc_method_list **methodLists; //存储实例方法或类方法
struct objc_cache *cache; //缓存,保存最近使用方法的地址
struct objc_protocol_list *protocols; //该类遵守的协议
};
1.2.1、 isa
isa,在NSObject中,也有isa成员,那么这两者有什么不同呢?
在NSObject中,isa指向的是实例对象的类对象,而Class的isa指向的该类的元类对象。元类对象也是Class对象,那么元类对象与类对象有什么不同呢?
在定义类的时候,既可以有类方法,也可以有实例方法。在类对象中保存着实例方法,而在元类对象中保存着类方法和其他一些元数据。
一个类的实例对象可以有多个,但只有一个类对象和一个元类对象。实例对象、类对象和元类对象之间的关系,如下图所示:
上图表明:所有的实例对象isa都指向该类的类对象,类对象的isa指向该类的元类对象。由于元类对象也是Class对象,也有isa指针,所有子类的元类对象的isa指向RootMetaClass对象,而RootMetaClass的isa指向其自身。
正是由于isa的存在,OC中的所有对象都可以在运行时查找自己的真实类型,并确定自己所能执行的方法。
在OC中,调用方法,其实质上是给对象发送消息,运行时机制会对该消息进行一系列复杂的处理。[targetdoSomething],会被编译器转换为如下代码:
objc_msgSend(target,SEL(doSomething)),其中target称为消息接收者,SEL(doSomething)称为消息选择器;
在OC中,调用实例实例方法或类方法(发送消息)的过程如下:
1) 调用实例方法,首先在自身的isa指向的类对象的cache缓存中查找该方法:如果找不到,在类对象的methodLists中查找;如果找不到,则沿着super_class指针到其父类中查找,如果仍然找不到,则继续通过super_class向上一级父结构体中查找,直至根class;若在根class仍找不到,则进行消息转发;
2) 调用类方法,首先通过isa指针找到metaclass,并从中查找类方法,如果找不到会通过metaclass的super_class指针找到父类的metaclass结构体,从中查找,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根metaclass;若在根class仍找不到,则进行消息转发。
1.2.2、 ivars
structobjc_ivar_list
{
int ivar_count;
#ifdef __LP64__
int space;
#endif
/* variable lengthstructure */
structobjc_ivar ivar_list[1];
}
objc_var_list其实就是一个链表,存储多个objc_var,而objc_var结构体存储类的单个成员变量信息。
1.2.3、 methodList
methodLists表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改*methodLists的值来添加成员方法,这也是Category实现的原理,同样也解释了Category不能天机实例变量的原因。在runtime.h可以看到它的定义:
structobjc_method_list
{
struct objc_method_list *obsolete ; intmethod_count
#ifdef __LP64__ int space ; #endif
/* variablelength structure */
structobjc_method method_list[1];
}
同理,objc_method_list也是一个链表,存储多个objc_method,而objc_metho结构体存储了某个方法的信息,如下:
structobjc_method
{
SEL method_name ;
char *method_types;
IMP method_imp ;
}
objc_method存储了方法名,方法类型和实现方法等。所以同一个类不能有两个重名的方法,即使参数类型不同,函数也不能重名。IMP其实一个函数指针,定义如下:
#if!OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
当向某个对象发送消息,可以由这个函数指针来指定实现方法,这样就可以绕开消息传递阶段而去执行另一个方法实现。
1.3、 id
/// A pointerto an instance of a class.
typedef struct objc_object *id;
1.4、 SEL
/// An opaquetype that represents a method selector.
typedef struct objc_selector *SEL;
2、 消息转发
在1.2.1节中,我们介绍了方法调用流程。但是,当实例对象不存在实例发那个发或类中不存在类方法时,将进行消息转发。消息转发的流程比较复杂,主要分为三个步骤,如下图所示
1) 首先,会调用消息接收者所在类的
+ (BOOL)resolveInstanceMethod:(SEL)sel方法(NSObject中定义),该方法返回一个一个BOOL值,表示是否动态添加一个方法来响应当前消息选择器;如果发送消息的是一个类方法,则会调用resoveClassMethod方法;方法若放回YES,意味着想要动态添加一个方法来响应当前的消息,可以在重写的方法内使用class_addMethod函数来为当前类添加方法。
2) 如果上一步中,并没有新方法能响应消息选择器,则会进入转发流程的第二步。此时,系统会调用消息接收者所在类的
-(id)forwardingTargetForSelector:(SEL)aSelector方法,用以询问能否将消息转发给其他接受者来处理,方法的返回值就代表这个新的接受者,如果不允许将消息转发给其他接受者则返回nil;(利用该方法,可以使用组合的方式模拟出多重继承的特性)
3) 如果步骤2中,不进行消息转发,那么消息转发机制还要继续进行最后一步。在这一步中,系统会将尚未处理的消息包装成一个NSInvocation对象,其内部包含与该消息相关的所有信息,比如消息的选择器,目标接收者,参数等。之后,系统会调用消息接收者所在类的
- (void)forwardInvocation:(NSInvocation *)anInvocation;
并将生成的NSInvocation对象作为参数传入。我们可以重写该方法,将NSInvocation对象的target属性设置为其他接收者。NSInvocation类还提供了许多属性和方法用于修改对应发那个发的信息,如可以修改方法的参数、返回这,或者直接更改消息选择器转而调用其他方法;
4) 经过上述步骤后,仍无法响应消息选择器,那么系统将会抛出异常。
3、 消息转发实例
// .h
@interface Person : NSObject
-(void) sayHello;
@end
// .m
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSString * selString=NSStringFromSelector(sel);
if ([selString isEqualToString:@"sayHello"])
{
class_addMethod(self, sel, (IMP)hello, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void hello()
{
printf("hello world");
}
@end
// .测试
Person * person=[[Person alloc] init];
[person sayHello];
4、 MethodSwizzing
通过上面章节,我们已经了解了OC中对象的类型和消息处理机制,这些有助于我们进一步了解OC运行时的其他功能和特性。接下来就介绍一种MethodSwizzing(方法调配)的技术,该技术常常被称为iOS开发中的黑魔法。
OC中方法和消息选择器之间的关系,如下
structobjc_method
{
SEL method_name ;
char *method_types;
IMP method_imp ;
}
可以看出,每一个方法内都包含三个成员,第一个是选择器的名字,第二个是方法的类型,最后一个是C语言的函数指针,用于指向方法具体执行的函数。我们可以把方法的内部结构理解为每一个SEL选择器对应一个具体的IMP函数,这也是SEL被称为选择器的原因,这样,我们就可以更加清楚地理解消息派发时,系统是如何根据选择器来查找对应的方法并跳转到方法的具体实现。
首先,当对象接收到某个消息时,编译器首先将代码转换为objc_msgSend函数,并将消息的接收者和选择器当作函数的参数传入,接下来系统会根据接收者的isa指针找到它所对应的类,在类的元数据信息中找到该类所拥有的方法列表,然后遍历方法列表,将每一个方法内部的SEL选择器同传入的消息选择器进行匹配,当找到相同的选择器后,就根据方法内部的IMP函数指针跳转到方法的具体实现。
了解清楚选择器和方法实现之间的一对一关系后,方法调配技术就是利用运行时提供的函数来动态修改选择器和方法实现之间的对应关系的一种技术,如下
// .h
#import <Foundation/Foundation.h>
@interface NSString(Logging)
-(NSString *)lowercaseStringWithLogging;
@end
// .m
#import "NSString+Logging.h"
@implementation NSString(Logging)
-(NSString *)lowercaseStringWithLogging
{
NSLog(@"logging....");
return @"lowercase...";
}
@end
// 测试
Method lowercase=class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method lowercaselogging=class_getInstanceMethod([NSString class], @selector(lowercaseStringWithLogging));
method_exchangeImplementations(lowercase,lowercaselogging);
NSLog(@"%@",[@"ABCD" lowercaseString]);
5、 AssociatedObjects
当想要使用Category对已存在的类进行扩展时,一般只能添加实例方法或类方法,而不适合添加额外的属性。虽然可以在Category头文件中声明属性,但是在实现文件中是无法synthesize任何实例变量和属性访问方法。这时需要自定义属性访问方法并且使用AssociatedObjects提供的三个API来向对象添加、获取和删除关联值。
l voidobjc_setAssociatedObject (id object, const void *key, id value,objc_AssociationPolicy policy )
l idobjc_getAssociatedObject (id object, const void *key )
l voidobjc_removeAssociatedObjects (id object )
其中objc_AssociationPolicy是个枚举类型,它可以指定Objc内存管理的引用计数机制。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy)
{
OBJC_ASSOCIATION_ASSIGN = 0,
/**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
/**< Specifies a strong reference to the associated object. The association is not madeatomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC= 3,
/**< Specifies that the associated object is copied. The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401,
/**< Specifies a strongreference to the associated object *The association ismade atomically. */
OBJC_ASSOCIATION_COPY= 01403
/**< Specifies that theassociated object is copied.
* The association is madeatomically. */
};
下面有个关于NSObject+AssociatedObject Category添加属性associatedObjec的代码:
NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (strong, nonatomic) idassociatedObject;
@end
NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
-(void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self,@selector(associatedObject), associatedObject,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject
{
returnobjc_getAssociatedObject(self, _cmd);
}
@end
Associated Objects的key要求是唯一并且是常量,而SEL是满足这个要求的,所以上面的采用隐藏参数_cmd作为key。
原文链接:http://www.jianshu.com/p/25a319aee33d