本博文总结于南峰子Runtime(博客地址:http://southpeak.github.io/2014/10/25/objective-c-runtime-1/ )
运行时之一:类与对象
一、类与对象基础数据结构
1、 Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。
它的定义如下:typedef struct objc_class *Class;
2、isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)
3、cache:用于缓存最近使用的方法。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
4、元类(Meta Class):meta-class是一个类对象的类。
meta-class也是一个类,也可以向它发送一个消息,meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 变量名
char *ivar_type OBJC2_UNAVAILABLE; // 变量类型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字节
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
objc_removeAssociatedObjects
函数来移除一个关联对象,或者使用objc_setAssociatedObject
函数 将key指定的关联对象设置为nil。
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
+resolveInstanceMethod:
(实例方法)或者
+resolveClassMethod:
(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法””。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过
class_addMethod
函数动态添加到类里面就可以了。如下代码所示:
voidfunctionForMethod1(idself, SEL _cmd) {
<span style="white-space:pre"> </span>NSLog(@"%@, %p",self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
<span style="white-space:pre"> </span>NSString*selectorString =NSStringFromSelector(sel);
<span style="white-space:pre"> </span>if([selectorString isEqualToString:@"method1"]) {
<span style="white-space:pre"> </span>class_addMethod(self.class,@selector(method1), (IMP)functionForMethod1,"@:");
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>return[superresolveInstanceMethod:sel];
}
@dynamic
属性。
self
自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理
aSelector
,则应该调用父类的实现来返回结果。
@interfaceSUTRuntimeMethodHelper :NSObject
- (void)method2;
@end
@implementationSUTRuntimeMethodHelper
- (void)method2 {
<span style="white-space:pre"> </span>NSLog(@"%@, %p",self, _cmd);
}
@end
#pragma mark -
@interfaceSUTRuntimeMethod () {
SUTRuntimeMethodHelper *_helper;
}
@end
@implementationSUTRuntimeMethod
+ (instancetype)object {
<span style="white-space:pre"> </span>return [[self alloc] init];
}
- (instancetype)init {
<span style="white-space:pre"> </span>self = [super init];
<span style="white-space:pre"> </span>if (self !=nil) {
<span style="white-space:pre"> </span>_helper = [[SUTRuntimeMethodHelper alloc] init];
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>returnself;
}
- (void)test {
[self performSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
<span style="white-space:pre"> </span>NSLog(@"forwardingTargetForSelector");
<span style="white-space:pre"> </span>NSString *selectorString =NSStringFromSelector(aSelector);
<span style="white-space:pre"> </span>// 将消息转发给_helper来处理
<span style="white-space:pre"> </span>if ([selectorString isEqualToString:@"method2"]) {
<span style="white-space:pre"> </span>return _helper;
<span style="white-space:pre"> </span> }
<span style="white-space:pre"> </span>return [super forwardingTargetForSelector:aSelector];
}
@end
NSInvocation
对象,把与尚未处理的消息有关的全部细节都封装在
anInvocation
中,包括
selector
,目标(
target
)和参数。我们可以在
forwardInvocation
方法中选择将消息转发给其它对象。
forwardInvocation:
方法的实现有两个任务:
- 定位可以响应封装在
anInvocation
中的消息的对象。这个对象不需要能处理所有未知消息。 - 使用
anInvocation
作为参数,将消息发送到选中的对象。anInvocation
将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
NSInvocation
对象。因此我们必须重写这个方法,为给定的
selector
提供一个合适的方法签名。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
<span style="white-space:pre"> </span>NSMethodSignature*signature = [supermethodSignatureForSelector:aSelector];
<span style="white-space:pre"> </span>if(!signature) {
<span style="white-space:pre"> </span>if([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
<span style="white-space:pre"> </span>returnsignature;
}
- (void)forwardInvocation:(NSInvocation*)anInvocation {
<span style="white-space:pre"> </span>if([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
forwardInvocation:
方法实现只是简单调用了
doesNotRecognizeSelector:
方法,它不会转发任何消息。这样,如果不在以上所述的三个步骤中处理未知消息,则会引发一个异常。
forwardInvocation:
就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象。这取决于具体的实现。
MethodfromMethod =class_getInstanceMethod([selfclass],@selector(action_1));
MethodtoMethod =class_getInstanceMethod([selfclass],@selector(action_2));
//通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了
if(!class_addMethod([selfclass],@selector(action_1),method_getImplementation(toMethod),method_getTypeEncoding(toMethod))) {
method_exchangeImplementations(fromMethod, toMethod);
}
selector
)、方法(
method
)和实现(
implementation
)是运行时中一个特殊点,虽然在一般情况下,这些术语更多的是用在消息发送的过程描述中。
以下是Objective-C Runtime Reference
中的对这几个术语一些描述:
Selector(typedef struct objc_selector *SEL)
:用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。Method(typedef struct objc_method *Method)
:在类定义中表示方法的类型Implementation(typedef id (*IMP)(id, SEL, ...))
:这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。
- 总是调用方法的原始实现(除非有更好的理由不这么做):API提供了一个输入与输出约定,但其内部实现是一个黑盒。Swizzle一个方法而不调用原始实现可能会打破私有状态底层操作,从而影响到程序的其它部分。
- 避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。
- 明白是怎么回事:简单地拷贝粘贴swizzle代码而不理解它是如何工作的,不仅危险,而且会浪费学习Objective-C运行时的机会。阅读
Objective-C Runtime Reference
和查看<objc/runtime.h>
头文件以了解事件是如何发生的。 - 小心操作:无论我们对Foundation, UIKit或其它内建框架执行Swizzle操作抱有多大信心,需要知道在下一版本中许多事可能会不一样。
super
,如下所示:
@interfaceMyViewController:UIViewController
@end
@implementationMyViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do something
...
}
@end
如何使用
super
我们都知道。现在的问题是,它是如何工作的呢?
super
与
self
不同。
self
是类的一个隐藏参数,每个方法的实现的第一个参数即为
self
。而
super
并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用viewDidLoad方法时,去调用父类的方法,而不是本类中的方法。而它实际上与
self
指向的是相同的消息接收者。为了理解这一点,我们先来看看
super
的定义:
|
struct objc_super { id receiver; Class superClass; };
|
- receiver:即消息的实际接收者
- superClass:指针当前类的父类
super
来接收消息时,编译器会生成一个objc_super
结构体。就上面的例子而言,这个结构体的receiver
就是MyViewController对象,与self
相同;superClass
指向MyViewController的父类UIViewController。
objc_msgSend
函数,而是调用
objc_msgSendSuper
函数,其声明如下:
id objc_msgSendSuper (
struct objc_super *
super, SEL op, ... );
|
objc_super
结构体,第二个参数是方法的selector
。该函数实际的操作是:从objc_super
结构体指向的superClass
的方法列表开始查找viewDidLoad的selector
,找到后以objc->receiver
去调用这个selector
,而此时的操作流程就是如下方式了
objc_super->receiver
就是
self
本身,所以该方法实际与下面这个调用是相同的:
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );
imp_implementationWithBlock
函数:参数block的签名必须是method_return_type ^(id self, method_args …)
形式的。该方法能让我们使用block作为IMP
。如下代码所示:
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj,NSString *str) {
<span style="white-space:pre"> </span>NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp,"v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
3、弱引用操作
// 加载弱引用指针引用的对象并返回
id objc_loadWeak ( id *location );
// 存储__weak变量的新值
id objc_storeWeak ( id *location, id obj );</span>
objc_loadWeak
函数:该函数加载一个弱指针引用的对象,并在对其做retain
和autoreleasing
操作后返回它。这样,对象就可以在调用者使用它时保持足够长的生命周期。该函数典型的用法是在任何有使用__weak
变量的表达式中使用。
objc_storeWeak
函数:该函数的典型用法是用于__weak
变量做为赋值对象时。