问题描述:因为objc是动态语言,对象的类型在运行时才会被确认,所以很容易出现一个定义为NSString类型的变量,在运行时的类型变成了NSNull,从而导致如下错误出现:-[NSNull stringByAppendingFormat:]: unrecognized selector sent to instance
下面介绍一下解决这个问题的思路
首先我们知道objc提供了消息转发机制,可以挽救当一个类调用了不存在的方法时,给你挽救的机会。我们就可以利用这个机制来拯救这个错误。
我们通过创建一个NSNull的分类,然后重写- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector和- (void)forwardInvocation:(NSInvocation *)anInvocation两个方法
1、在methodSignatureForSelector方法体中我们需要遍历系统中所有注册的iOS类,然后找到可以执行aSelector的类,并返回这个类的该aSelector的方法签名对象。
这里涉及以下几点:
a、遍历所有的已注册的类,利用runtime的objc_getClassList或objc_copyClassList
b、排除那些非继承自NSObject的类
c、对过滤后的类缓存,以及类对应的签名也要进行缓存,这里是为了提高查找效率
2、在forwardInvocation里如下:
1 - (void)forwardInvocation:(NSInvocation *)anInvocation{ 2 anInvocation.target = nil; 3 [anInvocation invoke]; 4 }
这里利用了objc的对象值为null的对象调用任何方法都会不执行并不报错的特点,将调用者target的值设置为null即可。
下面是获取方法签名的具体代码:
1 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ 2 @synchronized([self class]) 3 { 4 //look up method signature 5 NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; 6 if (!signature) 7 { 8 //not supported by NSNull, search other classes 9 static NSMutableSet *classList = nil; 10 static NSMutableDictionary *signatureCache = nil; 11 if (signatureCache == nil) 12 { 13 classList = [[NSMutableSet alloc] init]; 14 signatureCache = [[NSMutableDictionary alloc] init]; 15 16 //get class list 17 int numClasses = objc_getClassList(NULL, 0); 18 Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses); 19 numClasses = objc_getClassList(classes, numClasses); 20 21 //add to list for checking 22 c 23 for (int i = 0; i < numClasses; i++) 24 { 25 //determine if class has a superclass 26 Class someClass = classes[i]; 27 Class superclass = class_getSuperclass(someClass); 28 while (superclass) 29 { 30 if (superclass == [NSObject class]) 31 { 32 [classList addObject:someClass]; 33 break; 34 } 35 [excluded addObject:NSStringFromClass(superclass)]; 36 superclass = class_getSuperclass(superclass); 37 } 38 } 39 40 //remove all classes that have subclasses 41 for (Class someClass in excluded) 42 { 43 [classList removeObject:someClass]; 44 } 45 46 //free class list 47 free(classes); 48 } 49 50 //check implementation cache first 51 NSString *selectorString = NSStringFromSelector(aSelector); 52 signature = signatureCache[selectorString]; 53 if (!signature) 54 { 55 //find implementation 56 for (Class someClass in classList) 57 { 58 if ([someClass instancesRespondToSelector:aSelector]) 59 { 60 signature = [someClass instanceMethodSignatureForSelector:aSelector]; 61 break; 62 } 63 } 64 65 //cache for next time 66 signatureCache[selectorString] = signature ?: [NSNull null]; 67 } 68 else if ([signature isKindOfClass:[NSNull class]]) 69 { 70 signature = nil; 71 } 72 } 73 return signature; 74 } 75 }
原文地址:https://www.cnblogs.com/zbblog/p/12120413.html