Runtime
1.对象和类
Objective-C类是由Class
类型来表示的,它实际上是一个指向objc_class
结构体的指针。它的定义如下:
typedef struct objc_class *Class;
在objc/runtime.h中,struct objc_class 的定义如下:
struct objc_class {
Class isa;//元类
#if !__OBJC2__
Class super_class//父类 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;
虽然这个结构体已经过时了,但是还是有参考的意义,比起最新的版本更好理解。
由于 objc_class 也有 isa 指针,所以类本身也是一个对象。
下面就根据runtime提供的一些办法,使用简单的例子来探窥其究竟:
//首先创建一个测试类
#import <Foundation/Foundation.h>
@interface MyRuntimeTestClass : NSObject
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end
#import "MyRuntimeTestClass.h"
@interface MyRuntimeTestClass ()
{
NSInteger _instance1;
NSString *_instance2;
}
@property (nonatomic, assign) NSInteger *integer;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation MyRuntimeTestClass
+(void)classMethod1
{
}
-(void)method1
{
NSLog(@"call the method1");
}
-(void)method2
{
NSLog(@"call the method2");
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
//测试类和对象
-(void)testMyRuntimeTestClass
{
MyRuntimeTestClass *obj = [[MyRuntimeTestClass alloc]init];
self.myRuntimeTestClass = obj;
unsigned int outCount = 0;
Class cls = obj.class;
NSLog(@"class name :%s",class_getName(cls));
NSLog(@"==========================================================");
NSLog(@"super class name :%s",class_getName(class_getSuperclass(cls)));
NSLog(@"==========================================================");
Class metaClass = objc_getMetaClass(class_getName(cls));
NSLog(@"%s metaClass name:%s",class_getName(cls),class_getName(metaClass));
NSLog(@"==========================================================");
NSLog(@"%s is %@ a metaClass",class_getName(cls),class_isMetaClass(cls) ? @"" : @"not");
NSLog(@"==========================================================");
NSLog(@"class size:%zu",class_getInstanceSize(cls));
NSLog(@"==========================================================");
// 成员变量
Ivar *ivars = class_copyIvarList(cls, &outCount);//获取整个成员变量列表
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"instance variable's name:%s at index:%d",ivar_getName(ivar),i);
}
free(ivars);
Ivar array = class_getInstanceVariable(cls, "_array"); //获取类中指定名称实例成员变量的信息
if (array != NULL) {
NSLog(@"instance variable's %s",ivar_getName(array));
}
NSLog(@"==========================================================");
// 属性操作
objc_property_t *properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSLog(@"property's name: %s",property_getName(property));
}
free(properties);
objc_property_t string = class_getProperty(cls, "_string");
if (string != NULL) {
NSLog(@"property %s",property_getName(string));
}
NSLog(@"==========================================================");
// 方法操作
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
NSLog(@"method's signature:%s",sel_getName(method_getName(method)));
}
free(methods);
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
NSLog(@"instand method %s",sel_getName(method_getName(method1)));
}
Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
if (classMethod != NULL) {
NSLog(@"class method %s",sel_getName(method_getName(method1)));
}
IMP imp = class_getMethodImplementation(cls, @selector(method1));
IMP imp2 = method_getImplementation(class_getInstanceMethod(cls, @selector(method1)));
imp();
imp2();
NSLog(@"==========================================================");
/**
* 1.成员变量和属性: (1) 成员变量内部使用,属性外部使用,属性是为了让类外能够访问到成员变量,即是属性是外部访问成员变量的接口。
(2)类的变量包含成员变量和属性,成员变量就好似一个人的自己固有的属性(如大脑,眼睛等等),属性是一个人的外部特征(如他的名字,职业等等)。
(3)只要@property 声明了成员变量,SDK自动生成成员变量,不要需要手动对应,是专用于从类外部对其进行调用或赋值的.
(4)成员变量命名方式:_变量名
2.@property:自动创建setter and getter
3.类对象和类的实例(即对象):
*/
}
根据上面的一些例子,我们来看看元类:
/**
* 测试NSObject
*/
-(void)testNSObject
{
NSObject *obj = [[NSObject alloc]init];
Class cls = obj.class;//typedef struct objc_class *Class;
NSLog(@"NSObject name is %s",class_getName(cls));
NSLog(@"NSObject super class name is %s",class_getName(class_getSuperclass(cls)));// object super isa
NSLog(@"NSObject metaClass name is %s",class_getName(objc_getMetaClass(class_getName(cls))));// object isa
}
2.方法和消息
selector:
常说的方法或是选择子,其实质就是函数的指针,根据 selector 可以找到对应的办法。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(
Int
类型的地址),这个标识就是SEL
。
IMP
IMP
实际上是一个函数指针,指向方法实现的首地址,是一个函数实现的入口。
办法的调用:
Objective-C 语言中,消息直到运行时才被绑定起来。编译器会将消息表达式
[receiver message]
转化为一个消息函数的调用,即objc_msgSend
这个函数完成了动态绑定的所有事情:
- 首先它找到
selector
对应的方法实现。因为同一个方法可能在不同的类中有不同的实现,所以我们需要依赖于接收者的类来找到的确切的实现。- 它调用方法实现,并将接收者对象及方法的所有参数传给它。
- 最后,它将实现返回的值作为它自己的返回值。
获取办法的地址
该方式就类似 C 语言的函数调用,直接找到函数的地址。一般情况下不会使用该方式,只有在比较频繁调用某个函数的时候才会使用该方式。即使 OC 中使用了办法缓存机制,如果一个办法频繁调用,根据缓存列表,快速找到相应的办法和办法实现,也是有一定的时间的。
/**
* 直接获取办法的地址:有cocoa框架提供,并非 Objective-C 语言的特性 (一个办法比较频繁调用才会使用该办法,相当于直接调用函数)
*/
-(void)testGetMethodAdress
{
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[MyRuntimeTestClass methodForSelector:@selector(method1)];
for (int i = 0 ; i < 1000 ; i++)
setter(self, @selector(method1), YES);
}
消息转发
当对象接收到无法解读的消息后,就会启动”消息转发“。消息转发分三个阶段:动态解析,备用接收者,完整的消息转发。
动态解析:
该方式是当对象接收到无法解读的消息后,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel,
+(BOOL)resolveClassMethod:(SEL)sel
这两个办法,这时候我们可以在该办法处理对象无法解读的消息。
/**
* 动态解析:当对象接收到无法识别的消息,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)
*/
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod");
NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"testMySendMsgOfDymanicMehod"]) {
class_addMethod(self.class, @selector(testMySendMsgOfDymanicMehod), (IMP)functionForTestMySendMsgOfDymanicMehod, "@:");
}
return [super resolveInstanceMethod:sel];;
}
+(BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod");
NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
void functionForTestMySendMsgOfDymanicMehod(id self, SEL _cmd)
{
NSLog(@"the IMP of functionForTestMySendMsgOfDymanicMehod");
}
备用接收者:
当第一步的动态解析也无法解读未知的消息的时候,就会转向备用接收者:-(id)forwardingTargetForSelector:(SEL)aSelector
其实质就是寻找有没有其他对象能解读该对象,即是所谓的备用接收者。
/**
* 备用接收者
*/
-(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"forwardingTargetForSelector");
NSLog(@"can not performSelector called: %@",NSStringFromSelector(aSelector));
NSString *selectorString = NSStringFromSelector(aSelector);
if ([selectorString isEqualToString:@"method1"]) {
return self.myRuntimeTestClass;
}
return [super forwardingTargetForSelector:aSelector];
}
完整消息转发:
当前面两种方式都不能解读未知的消息,那就启用最后一中方式,即是将尚未处理的那条消息的有关的全部细节都封装在 NSInvocation 中,此对象包含选择子,目标以及参数。在触发 NSInvocation 对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。
/**
* 由于完整的消息转发需要将尚未处理的消息有关的全部细节封装在NSInvocation中,故应重写该办法。
*/
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector:");
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([MyRuntimeTestClass instancesRespondToSelector:aSelector]) {
signature = [MyRuntimeTestClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
/**
* 完整的消息转发
*/
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation");
if ([MyRuntimeTestClass resolveClassMethod:anInvocation.selector]) {
anInvocation.target = self.myRuntimeTestClass;
}
}
3.办法交换
有时候我们需要替换系统的办法时,比如我想在每次调用
-(void)viewWillAppear:(BOOL)animated
都打印一段话。这样每个 VC 都码上打印语句,未免效率有些不高,这时候我们就可以使用办法交换,将我们自己的办法和系统的办法交换。
/**
* 第一次调用类的类方法或实例方法之前被调用
*/
+(void)initialize
{
NSLog(@"initialize");
}
/**
* 在类初始加载时调用
*/
+(void)load
{
NSLog(@"load");
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzlingSelector = @selector(YYviewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
if (didAddMethod) {
//实现两个办法的实现部分交换
class_replaceMethod(class, swizzlingSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
});
/*
1.Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。
2.Method(typedef struct objc_method *Method):在类定义中表示方法的类型
3. Implementation(typedef id (*IMP)(id, SEL, ...)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。
*/
}
#pragma mark -method swizzling
-(void)YYviewWillAppear:(BOOL)animated
{
[self YYviewWillAppear:animated];
NSLog(@"method had changed!");
}
注:办法的交换永远都应该在 load 办法中实现,因为 load 办法在类初始化的时候就会调用,而 initialize 要在类被发送消息的时候才会调用。