1. 理解objc_msgSend
iOS上调用方法就是传递消息,消息包括名字、方法。还有可能包括参数和返回值。
OC和c的区别:
c是静态绑定,代码上写的调用哪个方法,这个方法就存在,在编译期就能确定运行时会调用哪个方法。
OC是动态绑定,虽然所有的底层代码都是由c语言编写,但是再c语言之上加了很多的判断,所以只有在具体运行的时候才能知道到底是调用了哪个方法,甚至可以在运行时动态去更改,正是这些成就了OC是一门动态语言。
OC调用方法的代码,编译器会将它转换成c语言函数调用,调用的函数是消息传递中的核心函数:
void objc_msgSend(id self, SEL cmd, ...)
这个方法至少两个参数,第一个是参数接受者(方法调用者),第二个参数是选择器,后面就是选择器的所有参数,依次排列。
该方法会在接受者所属的类的方法列表中去寻找和选择器名字相符的方法,找不到就沿着继承体系向上寻找,找得到就跳转,找不到就执行消息转发操作。
当调用过一次方法之后,objc_msgSend会将匹配结果缓存在快速映射表上,每个类都有这样一块缓存,当再次调用同样的方法时,执行速度会很快。
objc_msgSend_stret: 返回值是结构体的方法调用
objc_msgSend_fpret: 返回值是浮点数的方法调用
objc_msgSendSuper: 调用父类方法, 类似于[super message:parameter];
2.消息转发
分三步:
首先征询接受者所属的类,能不能动态的添加方法去处理这个未知的方法。如果不能请求接受者查找有没有其他的对象可以处理这个方法,如果可以消息转发结束,如果不行,执行最后一步,完整的消息转发流程。
动态消息解析:
+(BOOL)resolveInstanceMethod:(SEL)selector;
当对象接受到无法解读的消息后,会去调用这个方法
+(BOOL)resolveClassMethod:(SEL)selector;
如果消息对象是类方法的话,调用这个方法
使用这两个方法的前提是,事先已经写好了代码,重写了这个方法,并在内部对selector做了操作。
具体代码:
#import "TestDynamic.h"
#import
@interface TestDynamic()
@property (nonatomic,strong) NSMutableDictionary *testDict;
@end
@implementation TestDynamic
@dynamic str, testNumber, idObject; //使用dynamic 不让属性自行创建get和set方法
- (instancetype)init
{
self = [super init];
if (self) {
self.testDict = [NSMutableDictionary dictionary];
}
return self;
}
//当有方法不能识别的时候,首先调用这个方法。
+(BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selStr = NSStringFromSelector(sel);
//我们会在外部调用属性,所以这里只处理set 和 get两个方法
if ([selStr hasPrefix:@"sex"]) { //set方法
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
} else { //get方法
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "v@:@");
}
return YES;
}
//自己实现的get和set方法
id autoDictionaryGetter(id self, SEL _cmd) {
TestDynamic *typedSelf = (TestDynamic*)self;
NSMutableDictionary *backingStore = typedSelf.testDict;
NSString *key = NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value) {
TestDynamic *typedSelf = (TestDynamic*)self;
NSMutableDictionary *backingStore = typedSelf.testDict;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
//将方法名字大小写和set去掉
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
} else {
[backingStore removeObjectForKey:key];
}
}
备援接受者:将当前方法转给其他的接受者来操作
- (id)forwardingTargetForSelector:(SEL)selector;
通过这个可以模拟多重继承的某些特性,在一个对象内部,还有一些其他的对象,这样可以让该对象把能够处理方法的其他对象同个这个方法返回,在外部看来就像是这个对象自己处理的一样。
完整消息转发
运行期系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。
动态调试 黑盒方法
每个类的方法列表会把选择子的名称映射到相关的实现方法上。 这些方法都以函数指针的形式存在,这些指针叫做IMP;
OC运行期提供了 几个方法可以进行新增选择子、改变某个选择子的实现或者交换两个选择子的实现功能。
- (void)testMessage {
//交换NSString的两个方法实现
Method a = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method b = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(a, b);
}
//看似这个方法会进入死循环, 但是可以将这个方法的实现和别的方法替换
//此方法适用于不知道另一个方法的内部实现,然后通过交换方法的实现去调试那个方法
//此方法仅适用于方法调试(不常用)
- (NSString *)getLow_lowcaseString {
NSString *lowStr = [self getLow_lowcaseString];
return lowStr;
}
类方法
iOS 中对象的定义,如果给定了特定的类型,编译器会在调用方法的时候根据类型去判断是否可以去调用方法并提出警告。 而通用的对象类型 id object,编译器会默认所有的方法都可以调用。
对象的元数据结构:
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info; long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
isa指针是首个变量,称为元类, 类型为Class,所以Class也属于对象。
super_class定义了本类的超类, 是继承体系的链接。
可以通过两个方法查询类自身属于哪个类或者是继承自己哪个类
我们使用isMemberOfClass:能够判断出对象是否为某个特定类的实例;
而isKindOfClass:方法能够判断出对象是否为某类或其派生类的实例。
这两种方法都是利用了isa指针获取对象所属的类,然后通过super_class类在继承体系中查询。
从集合中获取对象的时候,当无法知道对象的具体类型时,需要对它的类型进行判断:
- (NSString*)commaSeparatedStringFromObjects:(NSArray*)array {
NSMutableString *string = [NSMutableString new];
for (id object in array) {
if ([object isKindOfClass:[NSString class]]) {
[string appendFormat:@"%@,", object];
} else if ([object isKindOfClass:[NSNumber class]]) {
[string appendFormat:@"%d,", [object intValue]];
} else if ([object isKindOfClass:[NSData class]]) {
NSString *base64Encoded = /* base64 encoded data */;
[string appendFormat:@"%@,", base64Encoded];
} else {
// Type not supported
}
}
return string;
}
也可以使用比较类对象是否相同的方法,但是因为直接操作的isa指针,不能使用isEqual:,应该使用 ==
[object class] == [anotherObject class];
但是如果对象是代理类型(NSProxy类或者它的子类),此时直接调用class,获取的是对象本身,而不是使用代理的那个对象。所以应该全部使用isKindOfClass:类型信息查询方法。