問題描述:因為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 }