专栏首页正则消息转发
原创

消息转发

运行时的语言会把绝大部分的调用实现延后至运行时进行确定,这就为更广泛程度上的方法干预提供了可能,比如交换方法实现,动态添加方法,动态生成中间类等。同时还有一套完整的异常消息转发机制,在消息异常时,提供完整的转发链来供用户进行异常补救.在几乎所有的方法调用中,我们都会确保相关方法得到了实现了,但总是会有漏网之鱼:

尝试使用字符串映射对应的方法,来进行动态调用时出现异常:比如不小心写错了一个字符之类;

由于处理上的不一致,导致出现非预期的调用:比如你预期会得到了一个字符串,接口中却意外返回了一个NSNull对象;

这时候在调用对应的方法时,系统通过遍历自己的继承链上所有方法,发现并未找到对应的实现,在应用终止之前,就会进行消息的转发流程.在OC中的消息转发机制分为以下时机:

在这部分分析中,需要具备以下知识:

动态方法解析

这是消息转发的第一步.在这一步骤中,运行时希望能够获得一个方法实现来正确处理这个异常的消息.这里只需要提供一个方法实现就行了,方法的实现主体还在当前对象,处理还在当前类中进行解决.针对类方法和实例方法,系统提供了两个不同的时机:

+ (BOOL)resolveClassMethod:(SEL)sel:用于处理异常的类方法

+ (BOOL)resolveInstanceMethod:(SEL)sel:用于处理异常的实例方法

这两个方法里,只能获取到一个参数就是sel,所以方法的名称是有了的,剩下的就是给方法添加一个候补的方法实现,并在成功添加之后返回True,这样就系统就会重新尝试执行方法.

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

- (NSString *)getInstanceInfo;

+ (NSString *)getClassInfo;

@end

@implementation Person

static id dealException(id self, SEL cmd) {

NSLog(@"self == %@", self);

NSLog(@"cmd == %@", NSStringFromSelector(cmd));

return nil;

}

+ (BOOL)resolveClassMethod:(SEL)sel {

class_addMethod(object_getClass(self), sel, (IMP)dealException, "@@:");

return true;

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

class_addMethod(self, sel, (IMP)dealException, "@@:");

return true;

}

@end

这样我们就可以避免在Person类中由于调用未实现的方法而产生的闪退,当然你也可以针对特定的方法动态添加对应的实现,

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

- (NSString *)getInstanceInfo;

+ (NSString *)getClassInfo;

@end

@implementation Person

static id dealException(id self, SEL cmd) {

NSLog(@"self == %@", self);

NSLog(@"cmd == %@", NSStringFromSelector(cmd));

return nil;

}

static id getClassInfo(id self, SEL cmd) {

NSString *className = NSStringFromClass(self);

NSString *selectorName = NSStringFromSelector(cmd);

return [NSString stringWithFormat:@"className:%@, selector:", className, selectorName];

}

+ (BOOL)resolveClassMethod:(SEL)sel {

NSString *selName = NSStringFromSelector(sel);

if ([selName isEqualToString:@"getClassInfo"]) {

class_addMethod(object_getClass(self), sel, (IMP) getClassInfo, "@@:");

return true;

}

return [super resolveClassMethod:sel];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

class_addMethod(self, sel, (IMP)dealException, "@@:");

return true;

}

@end

不过之所以是异常,那就出现了我们未能及时考虑到的情况,所以这种针对特定方法方法选择器动态添加方法实现的情况在实际开发中,并不常用到.如果在这个时机,我们未能对异常的方法进行处理,或者处理之后依旧返回了false,那么消息转发就会进入到下一个转发流程.

1.2 快速转发(Fast Rorwarding)

这是消息转发的第二步,在这一步骤中,消息已经不能在当前对象中进行处理,需要在备用的处理类中进行处理.系统提供的方法时机:

- (id)forwardingTargetForSelector:(SEL)aSelecto

在这个时机中,我们需要提供一个可以处理该异常方法的对象,这样消息的处理就会转移到我们心提供的类中来进行处理,就跳出来原来的类,使用新的类来处理当前你的方法.而且,有一个很重要的事情,就是类方法不会的消息不能到这一步,只有实例对象的方法才可以转发到这个时机.

@interface Exception : NSObject

@end

@implementation Exception

- (id) getInstanceInfo {

NSLog(@"self == %@", self);

return nil;

}

@end

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

- (NSString *)getInstanceInfo;

+ (NSString *)getClassInfo;

@end

@implementation Person

+(BOOL)resolveInstanceMethod:(SEL)sel {

NSLog(@"消息转发到了这里,但是我没有处理! %@", NSStringFromSelector(sel));

return false;

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

NSString *selName = NSStringFromSelector(aSelector);

if ([selName isEqualToString:@"getInstanceInfo"]) {

return [[Exception alloc] init];

}

return [super forwardingTargetForSelector:aSelector];

}

@end

在这个时机中,我们只需要提供一个可以处理异常方法的对象就就可以完成消息的转发,而且只能转发给一个对象.

当然如果不想单独设置一个类来捕获异常方法的话,也可以通过动态注册新类的方法来处理异常的方法:

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

- (NSString *)getInstanceInfo;

@end

@implementation Person

static id dealException(id self, SEL cmd) {

return nil;

}

+(BOOL)resolveInstanceMethod:(SEL)sel {

NSLog(@"消息转发到了这里,但是我没有处理! %@", NSStringFromSelector(sel));

return [super resolveInstanceMethod:sel];

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

NSString *className = @"Exception";

if (!NSClassFromString(className)) {

Class exceptionClass = objc_allocateClassPair([NSObject class], className.UTF8String, 0);

objc_registerClassPair(exceptionClass);

class_addMethod(exceptionClass, aSelector, (IMP)dealException, "@@:");

return [[exceptionClass alloc] init];

}

return [super forwardingTargetForSelector:aSelector];

}

这样我们就不需要专门为了处理异常实现了一个类,只需要在异常出现时动态增加一个新的类来处理异常就可以了.或许有人会有疑问,为什么明明只新增了一个类,而方法的名字objc_allocateClassPair却是新增了一对呢?因为在OC中,每个类除了自身之外,还会生成一个同名的元类,该元类中存储了类对象的一些信息(例如类方法都是存储在类的元类中),同时也是类对象isa指针的指向.

1.3 完整转发(Normal Forwarding)

这是消息转发的第三步,执行这个方法说明之前的时机都未能正常处理异常.而在这个时机中,系统会把现有的异常消息的所有细节(例如方法选择器,方法编码以及参数等)封装成NSInvocation对象传递到对应的方法中,等待执行所需要的全部细节都得到满足之后,由消息派发系统进行触发.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

- (void)forwardInvocation:(NSInvocation *)anInvocation;

在第一个方法中,我们需要返回一个可用的方法签名,用来对NSInvocation进行初始化,然后在第二个方法中设置处理对象进行调用.与之前的转发的处理不一样的是,在这个时机里,你可以同时将消息转发给多个对象进行处理.

@interface Exception : NSObject

@end

@implementation Exception

- (NSString *)getInstanceInfo {

NSLog(@"Exception");

return @"";

}

@end

@interface Exception2 : NSObject

@end

@implementation Exception2

- (NSString *)getInstanceInfo {

NSLog(@"Exception2");

return nil;

}

@end

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

- (NSString *)getInstanceInfo;

@end

@implementation Person

+(BOOL)resolveInstanceMethod:(SEL)sel {

NSLog(@"消息转发到了这里,但是我没有处理! %@", NSStringFromSelector(sel));

return [super resolveInstanceMethod:sel];

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

NSLog(@"消息转发到了这里,但是我没有处理! %@", NSStringFromSelector(aSelector));

return [super forwardingTargetForSelector:aSelector];

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"@@:"];

return sig;

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

[anInvocation invokeWithTarget:[[Exception alloc] init]];

[anInvocation invokeWithTarget:[[Exception2 alloc] init]];

}

@end

如果你错过了上述所有的时机,或者虽然处理了但是没有成功,最终都会被系统捕获为异常进行抛出,从而导致应用闪退.

- (void)doesNotRecognizeSelector:(SEL)aSelecto

 

2. 消息转发机制的应用

了解了消息转发的实现之后,我们来探索一下消息转发机制的应用.

2.1 避免程序异常闪退,定位异常位置

无论你多么牛逼,实现中都会有一些莫名其妙的异常,而针对不能识别的这类异常,就可以利用消息转发机制在抛出异常之前对异常的方法进行转发处理,可以有效地避免程序闪退.那么我们选择哪个时机来处理异常呢?

选择1:我们尝试使用resolveClass/InstanceMethod:这组方法.我们首先来动态交换一下这组方法,看一下会系统是否有用到这个方法来处理事件:

@interface NSObject (Exception)

@end

@implementation NSObject (Exception)

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Method resolveClassMethod = class_getClassMethod(self, @selector(resolveClassMethod:));

Method ed_resolveClassMethod = class_getClassMethod(self, @selector(ed_resolveClassMethod:));

method_exchangeImplementations(resolveClassMethod, ed_resolveClassMethod);

Method resolveInstanceMethod = class_getClassMethod(self, @selector(resolveInstanceMethod:));

Method ed_resolveInstanceMethod = class_getClassMethod(self, @selector(ed_resolveInstanceMethod:));

method_exchangeImplementations(resolveInstanceMethod, ed_resolveInstanceMethod);

});

}

+ (BOOL)ed_resolveClassMethod:(SEL)sel {

BOOL resolve = [self ed_resolveClassMethod:sel];

NSString *selectorName = NSStringFromSelector(sel);

NSLog(@"Class_class == %@:%@, %@", NSStringFromClass(self), selectorName, @(resolve));

return [self ed_resolveClassMethod:sel];

}

+ (BOOL)ed_resolveInstanceMethod:(SEL)sel {

BOOL resolve = [self ed_resolveInstanceMethod:sel];

NSString *selectorName = NSStringFromSelector(sel);

NSLog(@"Instancel_class == %@:%@, %@", NSStringFromClass(self), selectorName, @(resolve));

return resolve;

}

@end

输出结果:

Class_class == CADisplay selector:keyPathsForValuesAffectingCloned, result:0

Class_class == UIStatusBar selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarWindow selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarWindow selector:bundleForClass, result:0

Class_class == UIApplication selector:bundleForClass, result:0

Class_class == UIStatusBarWindow selector:bundleForClass, result:0

Class_class == UIStatusBarBackgroundView selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarSignalStrengthItemView selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarDataNetworkItemView selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarBatteryItemView selector:traitCollectionDidChange:, result:0

Class_class == UIImageView selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarTimeItemView selector:traitCollectionDidChange:, result:0

Class_class == UIStatusBarBatteryItemView selector:traitCollectionDidChange:, result:0

Class_class == UIImageView selector:traitCollectionDidChange:, result:0

Class_class == UINavigationBar selector:traitCollectionDidChange:, result:0

...

...

...

Intance_class == __NSCFString selector:encodeWithOSLogCoder:options:maxLength:, result:0

Intance_class == __NSCFString selector:encodeWithOSLogCoder:options:maxLength:, result:0

Intance_class == NSTaggedPointerString selector:_copyFormattingDescription:, result:0

Intance_class == __NSCFString selector:_dynamicContextEvaluation:patternString:, result:0

Intance_class == __NSCFString selector:encodeWithOSLogCoder:options:maxLength:, result:0

Intance_class == NSTaggedPointerString selector:_dynamicContextEvaluation:patternString:, result:0

Intance_class == NSTaggedPointerString selector:descriptionWithLocale:, result:0

Intance_class == __NSCFConstantString selector:_dynamicContextEvaluation:patternString:, result:0

Intance_class == BSMachPortTaskNameRight selector:fallbackXPCEncodableClass, result:0

Intance_class == FBSWorkspaceConnectEvent selector:fallbackXPCEncodableClass, result:0

Intance_class == CADisplay selector:keyPathsForValuesAffectingCloned, result:0

Intance_class == CADisplay selector:getCloned, result:0

Intance_class == CADisplay selector:cloned, result:0

AppDelegate selector:application:handleOpenURL:, result:0

AppDelegate selector:application:openURL:sourceApplication:annotation:, result:0

...

...

...

所以,系统利用这两个函数做了许多事,我们如果想要用这两个函数来处理异常就需要把这些处理全部隔离掉,然后处理捕获我们自定义的异常.但是看得出来,想要完全隔离这些类和方法并不容易,而且很难确保以后在新的操作系统版本中不会增加别的方法,所在在这里处理自定义异常,会变得非常复杂,而且具有不确定性,很可能会带来新的异常.

选择2:forwardingTargetForSelector:同样动态交换这个方法的实现,看看系统是否有使用到这个方法来处理事件:

@interface NSObject (Exception)

@end

@implementation NSObject (Exception)

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Method forwardingTargetForSelector = class_getInstanceMethod(self, @selector(forwardingTargetForSelector:));

Method ed_forwardingTargetForSelector = class_getInstanceMethod(self, @selector(ed_forwardingTargetForSelector:));

method_exchangeImplementations(forwardingTargetForSelector, ed_forwardingTargetForSelector);

});

}

- (id)ed_forwardingTargetForSelector:(SEL)aSelector {

NSLog(@"self == %@, selector == %@", self.class, NSStringFromSelector(aSelector));

return [self ed_forwardingTargetForSelector:aSelector];

}

@end

输出结果:

self == __NSXPCInterfaceProxy__UIKeyboardArbitration, selector == startArbitrationWithExpectedState:hostingPIDs:usingFence:withSuppression:onConnected:

self == _UIAppearance, selector == setPresentationContextPrefersCancelActionShown:

self == _UIAppearance, selector == setInPopover:

self == _UIAppearance, selector == setHasDimmingView:

self == _UIAppearance, selector == setShouldHaveBackdropView:

self == _UIAppearance, selector == setAlignsToKeyboard:

....

....

....

明显可以看到系统也使用了这个方法做了很多内部的处理,所以我们只能暂时抛弃这个方法,看看有没有其他的更好选择.

选择3:methodSignatureForSelector: and forwardInvocation:,同样我们先看一下系统有没有用这个时机进行事件处理,

@interface NSObject (Exception)

@end

@implementation NSObject (Exception)

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Method methodSignatureForSelector = class_getInstanceMethod(self, @selector(methodSignatureForSelector:));

Method ed_methodSignatureForSelector = class_getInstanceMethod(self, @selector(ed_methodSignatureForSelector:));

method_exchangeImplementations(methodSignatureForSelector, ed_methodSignatureForSelector);

Method forwardInvocation = class_getInstanceMethod(self, @selector(forwardInvocation:));

Method ed_forwardInvocation = class_getInstanceMethod(self, @selector(ed_forwardInvocation:));

method_exchangeImplementations(forwardInvocation, ed_forwardInvocation);

});

}

- (NSMethodSignature *)ed_methodSignatureForSelector:(SEL)aSelector {

NSMethodSignature *signature = [self ed_methodSignatureForSelector:aSelector];

NSLog(@"class == %@, aSelector == %@, signature == %@", self, NSStringFromSelector(aSelector), signature);

return signature;

}

- (void)ed_forwardInvocation:(NSInvocation *)anInvocation {

NSLog(@"class B== %@, aSelector == %@", self, NSStringFromSelector(anInvocation.selector));

[self ed_forwardInvocation:anInvocation];

}

@end

输出结果:

class == <_UIAlertControllerView: 0x10200e800; frame = (0 0; 736 414); layer = <CALayer: 0x2818724e0>>, aSelector == setHasDimmingView:, signature == <NSMethodSignature: 0x280d1c7c0>

class == <_UIAlertControllerView: 0x10200e800; frame = (0 0; 736 414); layer = <CALayer: 0x2818724e0>>, aSelector == setShouldHaveBackdropView:, signature == <NSMethodSignature: 0x280d1c7c0>

class == <_UIAlertControllerView: 0x10200e800; frame = (0 0; 736 414); layer = <CALayer: 0x2818724e0>>, aSelector == setAlignsToKeyboard:, signature == <NSMethodSignature: 0x280d1c7c0>

class == <Person: 0x283683c20>, aSelector == getInstanceInfo, signature == (null)

所以可以看到其实系统也使用了这个时机做了内部处理,但是我们发现:

系统的处理methodSignatureForSelector:都是有不为空的signature对象返回,而非系统内部处理的异常,signature为空;

系统处理并没有将异常抛给forwardInvocation:处理.

所以我们可以尝试根据methodSignatureForSelector:是否为空来判断是是否进入自定义的异常处理流程

@interface NSObject (Exception)

@end

@implementation NSObject (Exception)

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Method methodSignatureForSelector = class_getInstanceMethod(self, @selector(methodSignatureForSelector:));

Method ed_methodSignatureForSelector = class_getInstanceMethod(self, @selector(ed_methodSignatureForSelector:));

method_exchangeImplementations(methodSignatureForSelector, ed_methodSignatureForSelector);

Method forwardInvocation = class_getInstanceMethod(self, @selector(forwardInvocation:));

Method ed_forwardInvocation = class_getInstanceMethod(self, @selector(ed_forwardInvocation:));

method_exchangeImplementations(forwardInvocation, ed_forwardInvocation);

});

}

- (NSMethodSignature *)ed_methodSignatureForSelector:(SEL)aSelector {

NSMethodSignature *signature = [self ed_methodSignatureForSelector:aSelector];

NSLog(@"class == %@, aSelector == %@, signature == %@", self, NSStringFromSelector(aSelector), signature);

if (!signature) {

signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];

}

return signature;

}

- (void)ed_forwardInvocation:(NSInvocation *)anInvocation {

NSLog(@"class B== %@, aSelector == %@", self, NSStringFromSelector(anInvocation.selector));

[self ed_forwardInvocation:anInvocation];

}

@end

输出结果:

class == _UIAlertControllerView, aSelector == setHasDimmingView:, signature == <NSMethodSignature: 0x281da35c0>

class == _UIAlertControllerView, aSelector == setShouldHaveBackdropView:, signature == <NSMethodSignature: 0x281da35c0>

class == _UIAlertControllerView, aSelector == setAlignsToKeyboard:, signature == <NSMethodSignature: 0x281da35c0>

class == Person, aSelector == getInstanceInfo, signature == (null)

class B== <Person: 0x280af8610>, aSelector == getInstanceInfo

-[Person getInstanceInfo]: unrecognized selector sent to instance 0x280af8610

可以看出,只有系统未经系统处理的异常,在调用系统methodSignatureForSelector:实现时,返回的签名为空;但是经过我们人为添加自定义的方法签名时,会将该签名信息封装到forwardInvocation:的参数中供下一步的调用.所以我们就可以在forwardInvocation:中添加自定义的异常处理.

@interface Exception : NSObject

@end

@implementation Exception

@end

@interface NSObject (Exception)

@end

@implementation NSObject (Exception)

static id dealException(id self, SEL cmd, NSString *orignalClassName) {

/*

在这里就可以获取到出现异常的原始类名,方法名等信息,用于排查信息

*/

NSLog(@"self == %@, cmd == %@, orignal == %@", self, NSStringFromSelector(cmd), orignalClassName);

return nil;

}

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Method methodSignatureForSelector = class_getInstanceMethod(self, @selector(methodSignatureForSelector:));

Method ed_methodSignatureForSelector = class_getInstanceMethod(self, @selector(ed_methodSignatureForSelector:));

method_exchangeImplementations(methodSignatureForSelector, ed_methodSignatureForSelector);

Method forwardInvocation = class_getInstanceMethod(self, @selector(forwardInvocation:));

Method ed_forwardInvocation = class_getInstanceMethod(self, @selector(ed_forwardInvocation:));

method_exchangeImplementations(forwardInvocation, ed_forwardInvocation);

});

}

- (NSMethodSignature *)ed_methodSignatureForSelector:(SEL)aSelector {

NSMethodSignature *signature = [self ed_methodSignatureForSelector:aSelector];

NSLog(@"class == %@, aSelector == %@, signature == %@", self.class, NSStringFromSelector(aSelector), signature);

if (!signature) {

signature = [NSMethodSignature signatureWithObjCTypes:"@@:@"];

}

return signature;

}

- (void)ed_forwardInvocation:(NSInvocation *)anInvocation {

NSLog(@"class B== %@, aSelector == %@, target == %@", self, NSStringFromSelector(anInvocation.selector), anInvocation.target);

BOOL success = class_addMethod([Exception class], anInvocation.selector, (IMP)dealException, "@@:");

if (success) {

NSString *className = NSStringFromClass([anInvocation.target class]);

[anInvocation setArgument:&className atIndex:2];

[anInvocation invokeWithTarget:[[Exception alloc] init]];

}

}

@end

这样我们就可以使用统一的dealException来拦截调用了未实现方法[unrecognized selector sent to instance]这类异常 ,达到了:

通过添加自定义的处理,阻断了应用由于该类异常导致的闪退,改善了用户体验;

可以在dealException方法中获取到出现异常的原始类名,方法名等信息,以方便进一步定位异常所在可以在下次及时进行修正.

但是,但是,但是,其实我们只是拦截了实例方法未实现导致的异常,如果是类方法未实现导致的异常,怎么处理呢?下面给出一种替换类方法的思路:

@interface NSObject (Exception)

@end

@implementation NSObject (Exception)

static id dealException(id self, SEL cmd, NSString *orignalClassName) {

/*

在这里就可以获取到出现异常的原始类名,方法名等信息,用于排查信息

*/

NSLog(@"self == %@, cmd == %@, orignal == %@", self, NSStringFromSelector(cmd), orignalClassName);

return nil;

}

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

//类方法存储在类对应的元类中

Class metaClass = object_getClass(self);

//每个方法对应的函数实现都会有两个必须的参数,当前对象和SEL,其他的参数依次在后边

__block id(*orignalIMP)(__unsafe_unretained id, SEL, SEL) = NULL;

NSMethodSignature *(^methodSignatureForSelectorBlock)(id, SEL) = ^NSMethodSignature *(id _self, SEL _cmd) {

NSMethodSignature *signature = nil;

if (orignalIMP) {

SEL sel = @selector(methodSignatureForSelector:);

signature = orignalIMP(_self, sel, _cmd);

if (signature) {

return signature;

}

}

return [NSMethodSignature signatureWithObjCTypes:"@@:@"];

};

IMP ed_methodSignatureForSelector = imp_implementationWithBlock(methodSignatureForSelectorBlock);

Method methodSignatureForSelector = class_getInstanceMethod(object_getClass(self), @selector(methodSignatureForSelector:));

if (!class_addMethod(metaClass, @selector(methodSignatureForSelector:), ed_methodSignatureForSelector, method_getTypeEncoding(methodSignatureForSelector))) {

// The class already contains a method implementation.

orignalIMP = (typeof(orignalIMP))method_getImplementation(methodSignatureForSelector);

method_setImplementation(methodSignatureForSelector, ed_methodSignatureForSelector);

}

//替换forwardingInvocation:方法

void(^forwardInvocationBlock)(id, NSInvocation *) = ^(id _self, NSInvocation *anInvocation){

BOOL success = class_addMethod([Exception class], anInvocation.selector, (IMP)dealException, "@@:@");

if (success) {

NSString *className = NSStringFromClass([anInvocation.target class]);

NSLog(@"className ==== %@", className);

[anInvocation setArgument:&className atIndex:2];

[anInvocation invokeWithTarget:[[Exception alloc] init]];

}

};

IMP ed_forwardInvocation = imp_implementationWithBlock(forwardInvocationBlock);

Method forwardInvocation = class_getInstanceMethod(metaClass, @selector(forwardInvocation:));

method_setImplementation(forwardInvocation, ed_forwardInvocation);

});

}

感觉读得懂就读一下,感觉不理解就去看一下以下知识:

类与元类之间的关系

函数实现与Block之间的关系

动态交换方法实现

如果还不懂,可以尝试在结尾留言.

 

2.2 面向切面编程(AOP)

在消息转发过程中有一个非常重要的函数方法_objc_msgForward,如果你直接调用了该方法,无论原始的类是否实现了该方法,方法调用都会跳过正常的方法遍历直接进入消息流程.

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

- (NSString *)getInstanceInfo;

@end

@implementation Person

- (NSString *)getInstanceInfo {

NSLog(@"__%s__", __func__);

return nil;

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

NSLog(@"__%s__", __func__);

return [super resolveInstanceMethod:sel];

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

NSLog(@"__%s__", __func__);

return [super methodSignatureForSelector:aSelector];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

NSLog(@"__%s__", __func__);

[super forwardInvocation:anInvocation];

}

@end

//然后直接使用_objc_msgForward进行转发

Person *person = [[Person alloc] init];

((void(*)(id, SEL))_objc_msgForward)(person, @selector(getInstanceInfo));

输出结果:

__-[Person methodSignatureForSelector:]__

__+[Person resolveInstanceMethod:]__

__-[Person forwardInvocation:]__

-[Person getInstanceInfo]: unrecognized selector sent to instance 0x282d2a040

我们可以清楚地发现,虽然我们已经实现了方法,但是使用_objc_msgForward调用方法时,方法直接进入了消息转发流程,而没有调用真实的实现方法.利用这个特性,我们可以将指定方法的调用直接使用_objc_msgForward进行转发,从而拦截到原始方法的实现,在指定的时机(原始实现执行前,执行后,或者不执行原始实现)加入自定义的实现,实现达到干预原始实现的目的.

例如,我们想要拦截UIViewController的viewViewAppear:方法并在该方法实现执行之前,执行一段自定义的代码,主要的实现如下:

@interface EDIdentifier : NSObject

@property (assign, nonatomic) void(*operation)(UIViewController *, BOOL);

- (instancetype)initWithOperation:(void(*)(UIViewController *, BOOL))operation;

@end

@implementation EDIdentifie

- (instancetype)initWithOperation:(void(*)(UIViewController *, BOOL))operation {

if (self = [super init]) {

self.operation = operation;

}

return self;

}

@end

//绑定在指定的对象上(类对象)

@interface EDContainer : NSObject

- (void)addFunction:(EDIdentifier *)identifier option:(UIViewControllerOption)option;

@end

@interface EDContainer ()

@property (copy, nonatomic) NSArray *befores, *replaces, *afters;

@end

@implementation EDContaine

- (void)addFunction:(EDIdentifier *)identifier option:(UIViewControllerOption)option {

if (option == UIViewControllerOptionBefore) {

self.befores = self.befores ? [self.befores arrayByAddingObject:identifier] : @[identifier];

} else if (option == UIViewControllerOptionAfter) {

self.afters = self.afters ? [self.afters arrayByAddingObject:identifier] : @[identifier];

} else {

self.replaces = self.replaces ? [self.replaces arrayByAddingObject:identifier] : @[identifier];

}

}

@end

@interface EDTracker : NSObject

- (id)initWithTrackedClass:(Class)trackedClass parent:(EDTracker *)parent;

@property (nonatomic, strong) Class trackedClass;

@property (nonatomic, strong) NSMutableSet *selectorNames;

@property (nonatomic, weak) EDTracker *parentEntry;

@end

@implementation EDTracke

- (id)initWithTrackedClass:(Class)trackedClass parent:(EDTracker *)parent {

self = [super init];

if (self) {

self.trackedClass = trackedClass;

self.parentEntry = parent;

}

return self;

}

- (NSMutableSet *)selectorNames {

if (!_selectorNames) {

_selectorNames = [NSMutableSet set];

}

return _selectorNames;

}

@end

@implementation UIViewController (ForwardingInvocation)

static EDContainer *getContainerForObject(id self) {

NSString *_key = [@"ed_" stringByAppendingString:@"viewWillAppear"];

SEL key = NSSelectorFromString(_key);

EDContainer *container = nil;

Class kClass = (Class)self;

do {

container = objc_getAssociatedObject(kClass, key);

if (container) {

break;

}

} while((kClass = class_getSuperclass(kClass)));

if (!container) {

container = [[EDContainer alloc] init];

objc_setAssociatedObject(self, key, container, OBJC_ASSOCIATION_RETAIN);

}

return container;

}

static NSString *const EDForwardInvocationSelectorName = @"__ed_forwardInvocation:";

static void swizzleForwardInvocation(Class kClass) {

IMP originalImplementation = class_replaceMethod(kClass, @selector(forwardInvocation:), (IMP)ed_forwardInvocation, "v@:@");

if (originalImplementation) {

class_addMethod(kClass, NSSelectorFromString(EDForwardInvocationSelectorName), originalImplementation, "v@:@");

}

}

static BOOL isSelectorAllowedAndTrack(Class kClass, SEL sel) {

static NSMutableDictionary *swizzledClassesDict;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

swizzledClassesDict = @{}.mutableCopy;

});

NSString *selectorName = NSStringFromSelector(sel);

Class currentClass = kClass;

do {

EDTracker *tracker = swizzledClassesDict[currentClass];

if ([tracker.selectorNames containsObject:selectorName]) {

EDTracker *parentTracker = tracker;

while (parentTracker.parentEntry) {

parentTracker = parentTracker.parentEntry;

}

NSLog(@"Error: %@ already hooked in %@", selectorName, parentTracker.trackedClass);

return false;

}

} while ((currentClass = class_getSuperclass(currentClass)));

currentClass = kClass;

EDTracker *parentTracker = nil;

do {

if ([currentClass instancesRespondToSelector:sel]) {

EDTracker *tracker = swizzledClassesDict[currentClass];

if (!tracker) {

tracker = [[EDTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];

swizzledClassesDict[(id<NSCopying>)kClass] = tracker;

}

[tracker.selectorNames addObject:selectorName];

parentTracker = tracker;

}

} while ((currentClass = class_getSuperclass(currentClass)));

return true;

}

static void swizzleClassInPlace(Class kClass) {

NSString *className = NSStringFromClass(kClass);

static dispatch_once_t onceToken;

static NSMutableArray<NSString *> *swizzledClasses;

dispatch_once(&onceToken, ^{

swizzledClasses = @[].mutableCopy;

});

@synchronized (swizzledClasses) {

if (![swizzledClasses containsObject:className]) {

swizzleForwardInvocation(kClass);

[swizzledClasses addObject:className];

}

}

}

static void ed_forwardInvocation(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {

NSInteger countOfArguments = invocation.methodSignature.numberOfArguments;

if (countOfArguments == 3) {

UIViewController *_self;

[invocation getArgument:&_self atIndex:0];

SEL sel;

[invocation getArgument:&sel atIndex:1];

BOOL animated;

[invocation getArgument:&animated atIndex:2];

EDContainer *container = getContainerForObject([_self class]);

if (container.befores) {

for (EDIdentifier *identifier in container.befores) {

identifier.operation(_self, animated);

}

container.befores = nil;

}

if (container.replaces) {

for (EDIdentifier *identifier in container.replaces) {

identifier.operation(_self, animated);

}

container.replaces = nil;

} else {

Class currentClass = object_getClass(invocation.target);

SEL aliasSelector = aliasForSelector(invocation.selector);

//确保对象在继承链上

do {

if ([currentClass instancesRespondToSelector: aliasSelector]) {

invocation.selector = aliasForSelector(invocation.selector);

[invocation invoke];

break;

}

}

}

if (container.afters) {

for (EDIdentifier *identifier in container.afters) {

identifier.operation(_self, animated);

}

container.afters = nil;

}

} else {

invocation.selector = aliasForSelector(invocation.selector);

[invocation invoke];

}

}

static SEL aliasForSelector(SEL sel) {

NSString *selectorName = NSStringFromSelector(sel);

return NSSelectorFromString([@"__ed_" stringByAppendingString:selectorName]);

}

+ (void)addCustomerCode:(void(*)(UIViewController *, BOOL))customer option:(UIViewControllerOption)option {

EDIdentifier *identifier = [[EDIdentifier alloc] initWithOperation:customer];

EDContainer *container = getContainerForObject(self);

[container addFunction:identifier option:option];

if (isSelectorAllowedAndTrack(self, @selector(viewWillAppear:))) {

swizzleClassInPlace(self);

SEL sel_viewWillAppear = @selector(viewWillAppear:);

Method method_viewWillAppear = class_getInstanceMethod([self class], sel_viewWillAppear);

const char *types = method_getTypeEncoding(method_viewWillAppear);

SEL aliasSelector = aliasForSelector(sel_viewWillAppear);

if (![self instancesRespondToSelector:aliasSelector]) {

class_addMethod([self class], aliasSelector, method_getImplementation(method_viewWillAppear), types);

}

class_replaceMethod([self class], sel_viewWillAppear, (IMP)_objc_msgForward, types);

}

}

在这个演示代码,我们使用了C函数作为自定义操作的实现函数,并做了基本实现可以作为参考.如果想要获取更多的实现细节,可以参考我们之前分析过的Aspects类库的实现,不同的是在这个类库中只用了Block作为自定义操作的实现函数.

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 消息转发及super

    **消息转发的时候。由于oc的底层原理是消息机制,所以可以添加c语言函数等 **

    老沙
  • Runtime消息转发机制

    Class_Nonnull isa OBJC_ISA_AVAILABILITY;

    ZY_FlyWay
  • NSProxy 设计消息转发

    继承于NSProxy的类 找实现方法的时候 只会找当前类是否实现 而不找super,如果没找到直接进入消息动态解析,以及消息转发机制。比继承NSObject的类...

    老沙
  • (4)OC中消息和消息转发-02

    czjwarrior
  • (3)OC中消息和消息转发-01

    czjwarrior
  • 理解消息转发机制

    王大锤
  • iOS RunTime之四:消息转发

    在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,即通过重载 - (id)forwardingTargetForSelector:(SEL...

    s_在路上
  • 理解消息转发机制

    王大锤
  • ios OC 消息转发机制

    在编译期向类发送了其无法解读的的消息并不会报错,因为在运行期可以继续让类中添加方法,所有编译器在编译时还无法确知类中到底会不会有某个方法实现,当对象接收到无法解...

    conanma
  • ios OC 消息转发机制

    在编译期向类发送了其无法解读的的消息并不会报错,因为在运行期可以继续让类中添加方法,所有编译器在编译时还无法确知类中到底会不会有某个方法实现,当对象接收到无法解...

    conanma
  • RunTime 之消息处理与消息转发

    有关Runtime的知识总结,我本来想集中写成一篇文章的,但是最后发现实在是太长,而且不利于阅读,最后分成了如下几篇:

    進无尽
  • iOS进阶之消息转发机制

    Dwyane
  • 深入理解iOS消息转发机制

    程序员不务正业
  • iOS runtime方法调用与消息转发

    导语: iOS runtime为开发者提供了很多灵活便捷的方法,使得在运行时也可以改变类的结构。这篇文章主要是从方法调用作为切入点,来学习&记录runtime的...

    MelonTeam
  • 消息转发流程的源码探究

    在上篇文章方法的查找流程——慢速查找中,在lookUpImpOrForward函数里面会进行方法的查找,如果最终没有找到,那么就会进入消息的转发流程,如下:

    拉维
  • 更改 TUIKit 实现消息转发的功能

    实现原理一句话介绍: 拿到当前消息的信息, 转发的时候重新构建一条新的消息发送出去

    腾讯云 - zjiezhu
  • OC-从方法的汇编层看消息转发流程

    CacheLookup Normal,objc_msgSend(sel,imp)

    Wilbur-L
  • Python构建企业微信自动消息转发服务端

    目前有在项目分组,就小组成员中,微信群消息回复较多的情况下,想根据组来转发特定消息,包含文字、图片、语言等。在此只是自己实现仅供参考,可以根据自身需求修改更多功...

    KaliArch
  • iOS的动态创建实例方法和实现消息转发

    做了几年的iOS开发一直没有写博客,一直怕写的不好误导大家,今儿第一次在腾讯云写点干货

    conanma

扫码关注云+社区

领取腾讯云代金券