crash详解与防止,制止内部存款和储蓄器泄漏

Demo地址

前言:

[receiver message];

发送消息后:(eg.[instanceperformSelector:@selector(selname)])

一,使用NSProxy

unrecognized selector类型的crash是因为一个对象调用了一个不属于它的方法导致的。要解决这种类型的crash,我们先要了解清楚它产生的具体原因和流程。本文先讲了消息传递机制和消息转发机制的流程,然后对消息转发流程的一些函数的使用进行举例,最后指出了对“unrecognized selector类型的crash”的防护措施。

这一句的含义是:向receiver发送名为message的消息。

1.

先看官网介绍:

An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.

一、消息传递机制和消息转发机制

运行 clang-rewrite-objcMyClass.m之后将上面这句话重写成C代码

先一层一层寻找方法

这是一个超类,可以作为其他对象的替身

1.  消息传递机制(动态消息派发系统的工作过程)

((void(*)(id,SEL))(void*)objc_msgSend)((id)receiver,sel_registerName("message"));

Runtime在GitHub开源了一部分源码: objc4

objc-runtime-new.mm文件的 lookUpImpOrForward 方法中可以看到从子到父寻找的过程。 

二,官网实现

当编译器收到[someObject messageName:parameter]消息后,编译器会将此消息转换为调用标准的C语言函数objc_msgSend,如下所示:

去掉干扰因素,实际上上面这句话实际上是:

如果找不到,进入第二步

星彩网app下载 1

objc_msgSend(someObject,@selector(messageName:),parameter)

objc_msgSend(receiver,@selector(message));

2.

我们需要实现俩个方法

 该方法会去someObject所属的类中搜寻其“方法列表”,如果能找到与messageName:相符的方法,就跳转到实现代码;找不到就沿着继承体系继续向上找;如果最终还是找不到,就执行“消息转发”操作。

所以,像对象发送消息,最终大都会转换为objc_msgSend方法的调用

可以看到源码,这里调用了_class_resolveMethod方法

- forwardInvocation:(NSInvocation *)invocation;

- (nullable NSMethodSignature *)methodSignatureForSelector:sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");

2. 消息转发机制

为什么是大都呢?

if(resolver  &&  !triedResolver) {

runtimeLock.unlockRead();

_class_resolveMethod(cls, sel, inst);

// Don't cache the result; we don't hold the lock so it may have

// changed already. Re-do the search from scratch instead.

triedResolver =YES;

gotoretry;

}

二,实现自己的类

消息转发分两大阶段:

id objc_msgSend(id self,SEL _cmd,...)

就是NSObject的这个方法:(BOOL)resolveInstanceMethod:(SEL)sel

#import "MyProxy.h"

@interface MyProxy ()

@property (nullable, nonatomic, weak, readonly) id target;

@end@implementation MyProxy

proxyWithTarget:target{

MyProxy * proxy = [MyProxy alloc];

proxy->_target = target; return proxy;}

//重写以下俩个方法

- forwardInvocation:(NSInvocation *)invocation{

if ([_target respondsToSelector:invocation.selector]) {

// NSLog(@"Before calling "%@".", NSStringFromSelector(invocation.selector)); [invocation invokeWithTarget:_target];

// NSLog(@"After calling "%@".", NSStringFromSelector(invocation.selector));

}

}

- (NSMethodSignature *)methodSignatureForSelector:sel{

return [_target methodSignatureForSelector:sel];}

@end

(1)动态方法解析:即征询selector所属的类的下列方法,看其是否能动态添加这个未知的选择子:

将一个消息发送给一个对象,并且返回一个值。

在这个方法中可以新加方法,

三,使用使用myproxy代理你的对象就可以实现打破循环引用,防止内存泄漏

//  缺失的selector是实例方法调用 (BOOL)resolveInstanceMethod:(SEL)selector

其中,self是消息的接受者,_cmd是selector, ...是可变参数列表。

返回值:YESif the method was found and added to the receiver, otherwiseNO.

self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5

target:[MyProxy proxyWithTarget:self]

selector:@selector

userInfo:nil

repeats:YES];

//  缺失的selector是类方法调用 (BOOL)resolveClassMethod:(SEL)selector

当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

但是源码里并没有对返回值进行判断,就是说无论返回yes或者no,都会retry,并且只要addmethod成功就算返回NO也依然会调用。

四,除了重写上边俩个方法,也可以重写runtime消息转发方法。

该方法的参数就是那个未知的选择子,其返回值Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。(@dynamic属性没有实现setter方法和getter方法,可以在“消息转发”过程对其实现)

objc_msgSend函数将在self的方法字典中找到SEL _cmd对应的函数,所以这里你可以看出来,方法名一样的函数的SEL也是一样的。

还没找到就进入第三步:

// 也可以单独实现这一个方法;

- forwardingTargetForSelector:aSelector{return _target;}

(2)消息转发

由上面可以看出,由于我们传入的参数是SEL,然后再找对应的函数,所以我们可以在运行时添加SEL对应的函数,或者进行替换,

3.

(2.1)“备援接收者”方案----当前接收者第二次处理未知选择子的机会:运行期系统通过下列方法问当前接收者,能不能把这条消息转发给其它接收者来处理:

具体可以通过重载resolveInstanceMethod方法进行实现

// If neither self nor its superclasses implement

// it, forward the message because self might know

// someone who does.  This is a "chained" forward...

if (! size) return [self forward: sel: args];

-(id)forwardingTargetForSelector:(SEL)selector

(BOOL) resolveInstanceMethod:(SEL) sel

源码如上,这里应该是开始调用下面这个方法:

该方法的参数就是那个未知的选择子,其返回值id类型,表示找到的备援对象,找不到就返回nil。(缺点:我们无法操作经由这一步所转发的消息。)

这是NSObject根类提供的类方法,调用时机为当被调用的方法实现部分没有找到,而消息转发机制启动之前的这个中间时刻。

 (id)forwardingTargetForSelector:(SEL)aSelector

 (2.2) 完整的消息转发

这时就进行到消息转发的步骤了,具体步骤是:

如果一个对象实现了这个方法,并返回一个非nil的结果,则这个被返回的对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

调用下列方法转发消息:

当被调用的方法实现部分没有找到,调用resolveInstanceMethod

如果forward返回空,进入第四步:

-(void)forwardInvocation:(NSInvocation*)invocation

但是resolveInstanceMethod方法中也没有做重定向处理时,调用

4.

 NSInvocation把尚未处理的那条消息有关的全部细节都封于其中,包括:选择子、目标及参数。

- (id)forwardingTargetForSelector:(SEL)sel

 { return _otherObject; }

// Message self with the specified selector and arguments

return objc_msgSendv (self, sel, size, args);

(a)上面这个方法可以实现的很简单:只需改变调用目标,使消息在新目标上得以调用即可(与“备援接收者”方案所实现的方法等效,很少有人采用)。

假如再不行,进入完整的消息转发流程,调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

应该是在objc_msgSendv方法中开始了下面的过程:

(b)比较有用的实现方式为:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改换选择子等等。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

if (signature == nil) {

signature = [OtherObj instanceMethodSignatureForSelector:aSelector];

}

return signature;

}

先调用

上面的步骤都不能解决问题的话,就会调用NSObject的doesNotRecognizeSelector抛出异常。

如果signature为nil,则调用- (void)doesNotRecognizeSelector:(SEL)aSelector

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

总结:

- (void)doesNotRecognizeSelector:(SEL)aSelector

{

NSLog(@"%@ %@",NSStringFromClass(self.class),NSStringFromSelector(_cmd));

}

获取了NSMethodSignature, 再调用下面的forwardInvocation,如果没有实现上述方法,NSObject的默认实现是unrecognized selector。

  消息转发的全流程,如下图所示:

如果不为nil,即other实现了这个函数,则调用

- (void)forwardInvocation:(NSInvocation *)anInvocation

星彩网app下载 2

(void)forwardInvocation:(NSInvocation *)anInvocation,实现完整转发流程大概实现如下图

在这个方法中使用anInvocation-invoke 结束消息;

“消息转发”全流程图

-(void)forwardInvocation:(NSInvocation *)invocation

{

SEL invSEL = invocation.selector;

if([someOtherObject respondsToSelector:invSEL])

[anInvocation invokeWithTarget:someOtherObject];

}else{

[self doesNotRecognizeSelector:invSEL];

}

}

如果没有ivnoke 依然unrecognized selector

二、举例


1. 动态方法解析,即resolveInstanceMethod的使用:

下面我们详细讨论一下这四个步骤。

  (以动态方法解析来实现@dynamic属性)

星彩网app下载,动态方法解析

//EOCAutoDictionary.h

对象在接收到未知的消息时,首先会调用所属类的类方法 resolveInstanceMethod:(实例方法)或 者 resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经 实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。如下代码所示:

@interface EOCAutoDictionary : NSObject

void functionForMethod1(id self, SEL _cmd) {

NSLog(@"%@, %p", self, _cmd);

}

 (BOOL)resolveInstanceMethod:(SEL)sel {

NSString *selectorString = NSStringFromSelector(sel);

if([selectorString isEqualToString:@"method1"]) {

class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1,"@:");

}

return [super resolveInstanceMethod:sel];

}

@property(nonatomic, strong) NSDate *date;

备用接收者

@end

如果在上一步无法处理消息,则Runtime会继续调以下方法:

//EOCAutoDictionary.m

- (id)forwardingTargetForSelector:(SEL)aSelector

#import "EOCAutoDictionary.h"

如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

@interface EOCAutoDictionary()

使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。如下代码所示:

@property(nonatomic, strong) NSMutableDictionary *backingStore;

@interface SUTRuntimeMethodHelper : NSObject

- (void)method2;

@end@implementation SUTRuntimeMethodHelper

- (void)method2 {

NSLog(@"%@, %p", self, _cmd);

}

@end

#pragma mark -

@interface SUTRuntimeMethod () {SUTRuntimeMethodHelper *_helper;}

@end

@implementation SUTRuntimeMethod

 (instancetype)object {

return [[self alloc] init];

}

- (instancetype)init {

self = [super init];

if(self != nil) {

_helper = [[SUTRuntimeMethodHelper alloc] init];

}

return self;

}

- (void)test {

[self performSelector:@selector(method2)];

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

NSLog(@"forwardingTargetForSelector");

NSString *selectorString = NSStringFromSelector(aSelector);

// 将消息转发给_helper来处理

if([selectorString isEqualToString:@"method2"]) {

return_helper;

}

return[superforwardingTargetForSelector:aSelector];

}

@end

@end

这一步合适于我们只想将消息转发到另一个能处理该消息的对象上。但这一步无法对消息进行处理,如操作消息的参数和返回值。

@implementation EOCAutoDictionary

完整消息转发

@dynamic date;

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:

- (id)init {

- (void)forwardInvocation:(NSInvocation *)anInvocation

    if(self = [super init]) {_backingStore = [NSMutableDictionary new];}

运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息 有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation 方法中选择将消息转发给其它对象。

    return self;

forwardInvocation:方法的实现有两个任务:

}

1. 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。

(BOOL) resolveInstanceMethod:(SEL)selector {

2. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。

    //selector = "setDate:" 或 "date",_cmd = (SEL)"resolveInstanceMethod:"

不过,在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改,比如修改一个参数等,然后再去触发消息。另外,若发现某个消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理此调用请求。

    NSString *selectorString = NSStringFromSelector(selector);

还有一个很重要的问题,我们必须重写以下方法:

    if([selectorString hasPrefix:@"set"]) {

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

        // 向类中动态的添加方法,第三个参数为函数指针,指向待添加的方法。最后一个参数表示待添加方法的“类型编码”

消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。

        class_addMethod(self, selector,(IMP)autoDictionarySetter,"v@:@");

完整的示例如下所示:

    } else {

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

    NSMethodSignature *signature = [supermethodSignatureForSelector:aSelector]; 

    if(!signature) {

        if([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {

            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];

        }

  }

   returnsignature;

}

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

    if([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:_helper];

    }

}

        class_addMethod(self, selector,(IMP)autoDictionaryGetter,"v@:@");

NSObject的forwardInvocation:方法实现只是简单调用了doesNotRecognizeSelector:方法,它不会转发任何消息。这样,如果不在以上所述的三个步骤中处理未知消息,则会引发一个异常。

    }

从某种意义上来讲,forwardInvocation:就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象。这取决于具体的实现。

    return YES;

消息转发与多重继承

}

回过头来看第二和第三步,通过这两个方法我们可以允许一个对象与其它对象建立关系,以处理某些未知消息,而表面上看仍然是该对象在处理消息。通过这 种关系,我们可以模拟“多重继承”的某些特性,让对象可以“继承”其它对象的特性来处理一些事情。不过,这两者间有一个重要的区别:多重继承将不同的功能 集成到一个对象中,它会让对象变得过大,涉及的东西过多;而消息转发将功能分解到独立的小的对象中,并通过某种方式将这些对象连接起来,并做相应的消息转 发。

id autoDictionaryGetter(id self, SEL _cmd) {

不过消息转发虽然类似于继承,但NSObject的一些方法还是能区分两者。如respondsToSelector:和isKindOfClass:只能用于继承体系,而不能用于转发链。便如果我们想让这种消息转发看起来像是继承,则可以重写这些方法,如以下代码所示:

    // 此时_cmd = (SEL)"date"

- (BOOL)respondsToSelector:(SEL)aSelector   {

if( [superrespondsToSelector:aSelector] )

returnYES;

else{

/* 

Here, test whether the aSelector message can be forwarded to another object and whether that object can respond to it. Return YES if it can.

*/

}

returnNO;

}

    // Get the backing store from the object


    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *) self;

相关文章:

    NSMutableDictionary *backingStore = typeSelf.backingStore;

[Cocoa]深入浅出 Cocoa 之消息

    //the key is simply the selector name

Objective-C Runtime

    NSString *key = NSStringFromSelector(_cmd);

    //Return the value

    return [backingStore objectForKey:key];

}

void autoDictionarySetter(id self, SEL _cmd, id value) {

    // 此时_cmd = (SEL)"setDate:"

    // Get the backing store from the object

    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *) self;

    NSMutableDictionary *backingStore = typeSelf.backingStore;

    /** The selector will be for example, "setDate:".

    * We need to remove the "set",":" and lowercase the first letter of the remainder.*/

    NSString *selectorString = NSStringFromSelector(_cmd);

    NSMutableString *key = [selectorString mutableCopy];

    // Remove the ':' at the end

    [key deleteCharactersInRange:NSMakeRange(key.length-1, 1)];

    // Remove the 'set' prefix

    [key deleteCharactersInRange:NSMakeRange(0, 3)];

    // Lowercase the first character

    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];

    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];

    if(value) {

        [backingStore setObject:value forKey:key];

    } else {

        [backingStore removeObjectForKey:key];

    }

}

@end

使用date属性的setter和getter代码如下:

EOCAutoDictionary *dict = [EOCAutoDictionarynew];

dict.date = [NSDate dateWithTimeIntervalSince1970:475372800];

NSLog(@"dict.date = %@", dict.date);

 2. forwardingTargetForSelector的使用

注意:上面的resolveInstanceMethod返回YES的话,就无法调用forwardingTargetForSelector了。

下面的方法,对SLVForwardTarget的对象调用uppercaseString方法时,转发给另一个对象"hello WorLD!"来执行uppercaseString方法。

@implementation SLVForwardTarget

#pragma mark forwardingTargetForSelector

-(id) forwardingTargetForSelector:(SEL)aSelector {

    if(aSelector == @selector(uppercaseString)){

        return @"hello WorLD!";

    }

    return nil;

}

@end

3. forwardInvocation的使用

 改变调用目标,使消息在新目标上得以调用的例子:

// SLVForwardInvocation.h

@interface SLVForwardInvocation : NSObject

- (id)initWithTarget1:(id)t1 target2:(id)t2;

@end

// SLVForwardInvocation.m

@interface SLVForwardInvocation()

@property(nonatomic, strong)id realObject1;

@property(nonatomic, strong)id realObject2;

@end

@implementation SLVForwardInvocation

- (id)initWithTarget1:(id)t1 target2:(id)t2 {

    _realObject1 = t1;

    _realObject2 = t2;

    return self;

}

系统check实例是否能response消息呢?如果实例本身就有相应的response,那么就会响应之,如果没有系统就会发出methodSignatureForSelector消息,寻问它这个消息是否有效?有效就返回对应的方法签名,无效则返回nil。消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。// Here, we ask the two real objects, realObject1 first, for their method signatures, since we'll be forwarding the message to one or the other of them in -forwardInvocation:.  If realObject1 returns a non-nil method signature, we use that, so in effect it has priority.

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

    NSMethodSignature *sig;

    sig = [self.realObject1 methodSignatureForSelector:aSelector];

    if (sig){

        return sig;

    }

    sig = [self.realObject2 methodSignatureForSelector:aSelector];

    if (sig){

        return sig;

    }

    return nil;

}

// Invoke the invocation on whichever real object had a signature for it.

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

    id target = [self.realObject1 methodSignatureForSelector:[invocation selector]] ? self.realObject1 : self.realObject2;

    [invocation invokeWithTarget:target];

  //或者用下列方法

  /*  id target;

      if([self.realObject1 respondsToSelector:[invocation selector]]) {

          target = self.realObject1;

      } else if([self.realObject2 respondsToSelector:[invocation selector]]) {

         target = self.realObject2;

     }

      [invocation invokeWithTarget:target];    */

}

测试示例:

NSMutableString *string= [NSMutableString new];

NSMutableArray *array = [NSMutableArray new];

id  proxy = [[SLVForwardInvocation alloc] initWithTarget1:string target2:array];

// Note that we can't use appendFormat:, because vararg methods cannot be forwarded!

[proxy appendString:@"This "];

[proxy appendString:@"is "];

[proxy addObject:string];

[proxy appendString:@"a "];

[proxy appendString:@"test!"];

if([[proxy objectAtIndex:0] isEqualToString:@"This is a test!"]) {

    NSLog(@"Appending successful."); 

} else { 

    NSLog(@"Appending failed, got: '%@'", proxy); 

}

此处选择子"appendString:"改变目标为mutableString类型,

"addObject:"和"objectAtIndex:"改变目标为mutableArray类型。

三、unrecognized selector crash防护方案

  根据上面的讲解和举例,我们知道,当一个函数找不到时,runtime提供了三种方式去补救:

(1)调用resolveInstanceMethod给个机会让类添加实现这个函数;

(2)调用forwardingTargetForSelector让别的对象去执行这个函数;

(3)调用forwardInvocation(函数执行器)灵活的将目标函数以其它形式执行。

第一种方案:

  对于“unrecognized selector crash”,我们就可以利用消息转发机制来进行补救。对于使用上面三步中的哪一步来改造比较合适,我们选择第二步forwardingTargetForSelector。初步分析原因如下:上面的三步接收者均有机会处理消息。步骤越往后,处理消息的代价就越大。forwardInvocation要通过NSInvocation来执行函数,得创建和处理完整的NSInvocation,开销比较大。但resolveInstanceMethod给类添加不存在的方法,有可能这个方法并不需要,比较多余。用forwardingTargetForSelector将消息转发给一个对象,开销较小。

防护方案如下:

NSObject的类别NSObject Forwarding来重写forwardingTargetForSelector方法,让执行的目标转移到SLVUnrecognizedSelectorSolveObject里,然后SLVUnrecognizedSelectorSolveObject添加新的方法对未知选择子进行处理。在处理的这一块儿,可以加上日志.

缺点:

(1)类里的forwardingTargetForSelector如果提前返回nil了,就没办法执行SLVStubProxy里的autoAddMethod方法。另外,未知选择子对应的类里面如果有forwardInvocation方法的话,会优先执行SLVStubProxy里的autoAddMethod方法,而不会执行选择子对应的类里面的forwardInvocation方法。 整个处理流程,完全是按照以上三种方式的前后顺序执行,一旦一个方式解决了这个函数调用的问题,其它方法就不会执行。这里得注意工程代码里,可能就是需要自己的类里处理未知选择子的情况。

 (2)还有一些selector如:"getServerAnswerForQuestion:reply:"、

"startArbitrationWithExpectedState:hostingPIDs:withSuppression:onConnected:"、

"_setTextColor:"、"setPresentationContextPrefersCancelActionShown:"  也会拦截到。本来这些selector系统会自己处理的,相当于这块儿的拦截超前了,照这个比较大的缺陷来说,我们还是在第三步forwardInvocation来处理未知选择子比较好,所以有了下面这个方案。

第二种方案:

消息转发机制里的三个步骤处理未知选择子,步骤越往后,处理消息的代价就越大。但是步骤越往前,我们越有可能拦截到系统的本来能处理的方法,这种方案是以牺牲效率来改善拦截的准确性的。

防护方案如下:

NSObject的类别NSObject Forwarding来重写forwardInvocation方法,考虑到诸如"_navigationControllerContentInsetAdjustment"的选择子有可能系统会在自己的forwardInvocation方法里进行处理,所以此处先判断系统的方法能否处理,系统的方法不能处理未知选择子,再让执行的目标转移到未知选择子处理对象SLVUnrecognizedSelectorSolveObject 里。然后SLVUnrecognizedSelectorSolveObject添加新的方法对未知选择子进行处理。在处理的这一块儿,可以加上日志信息。

     以上两种方案的代码如下,其中用枚举SLVUnrecognizedSelectorSolveScheme分别表示上面的两种方案,可自行修改,这里推荐第二种方案:

// NSObject Forwarding.m

#import "NSObject Forwarding.h"

#import "SLVUnrecognizedSelectorSolveObject.h"

typedef NS_ENUM(NSInteger, SLVUnrecognizedSelectorSolveScheme) { 

 SLVUnrecognizedSelectorSolveScheme1, //第一种方案 

 SLVUnrecognizedSelectorSolveScheme2 //第二种方案     };

@implementation NSObject (Forwarding)

(void)load{ 

 static dispatch_once_t onceToken; 

 dispatch_once(&onceToken, ^{ 

     SLVUnrecognizedSelectorSolveScheme scheme = SLVUnrecognizedSelectorSolveScheme2; 

     if(scheme == SLVUnrecognizedSelectorSolveScheme1){ 

                [[self class] swizzedMethod:@selector(forwardingTargetForSelector:) withMethod:@selector(newForwardingTargetForSelector:)]; 

      }else if(scheme == SLVUnrecognizedSelectorSolveScheme2){ 

                [[self class] swizzedMethod:@selector(methodSignatureForSelector:) withMethod:@selector(newMethodSignatureForSelector:)]; 

                [[self class] swizzedMethod:@selector(forwardInvocation:) withMethod:@selector(newForwardInvocation:)]; } 

      });

}

(void)swizzedMethod:(SEL)originalSelector withMethod:(SEL )swizzledSelector { 

 Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, originalSelector); 

 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 

 BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); 

 if (didAddMethod) { 

           class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

 }else{ 

           method_exchangeImplementations(originalMethod, swizzledMethod); 

 }   }

#pragma mark forwardTarget

-(id) newForwardingTargetForSelector:(SEL)aSelector { 

 SLVUnrecognizedSelectorSolveObject *obj = [SLVUnrecognizedSelectorSolveObject sharedInstance]; 

 return obj;    }

- (NSMethodSignature *)newMethodSignatureForSelector:(SEL)sel{ 

 SLVUnrecognizedSelectorSolveObject *unrecognizedSelectorSolveObject = [SLVUnrecognizedSelectorSolveObject sharedInstance]; 

 return [self newMethodSignatureForSelector:sel]?:[unrecognizedSelectorSolveObject newMethodSignatureForSelector:sel];    }

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

     if([self newMethodSignatureForSelector:anInvocation.selector]){ 

           [self newForwardInvocation:anInvocation]; 

           return; 

      } 

      SLVUnrecognizedSelectorSolveObject *unrecognizedSelectorSolveObject = [SLVUnrecognizedSelectorSolveObject sharedInstance]; 

      if([self methodSignatureForSelector:anInvocation.selector]){ 

           [anInvocation invokeWithTarget:unrecognizedSelectorSolveObject]; 

      }

}

// SLVUnrecognizedSelectorSolveObject.m

#import "SLVUnrecognizedSelectorSolveObject.h"

@implementation SLVUnrecognizedSelectorSolveObject

(instancetype) sharedInstance{

    static SLVUnrecognizedSelectorSolveObject *unrecognizedSelectorSolveObject;

    static dispatch_once_t  once_token;

    dispatch_once(&once_token, ^{

   unrecognizedSelectorSolveObject = [[SLVUnrecognizedSelectorSolveObject alloc] init]; });

    return unrecognizedSelectorSolveObject;

}

(BOOL) resolveInstanceMethod:(SEL)selector {

    // 向类中动态的添加方法,第三个参数为函数指针,指向待添加的方法。最后一个参数表示待添加方法的“类型编码”

    class_addMethod([self class], selector,(IMP)autoAddMethod,"v@:@");

    return YES;

}

id autoAddMethod(id self, SEL _cmd) {

    //可以在此加入日志信息,栈信息的获取等,方便后面分析和改进原来的代码。

NSLog(@"unrecognized selector: %@",NSStringFromSelector(_cmd));  

 return 0;

}

本文由星彩网app下载发布于计算机编程,转载请注明出处:crash详解与防止,制止内部存款和储蓄器泄漏

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。