- 復(fù)現(xiàn)步驟:
用UIWebView打開這個測試網(wǎng)頁,將其中<select></select>
節(jié)點中所有的option節(jié)點刪除褐奴,會出現(xiàn)一個空白的UIPickerView按脚,如下圖:
IMG_0630.PNG
在這個空白的picker中隨便劃幾下再點完成按鈕便會crash。
- 崩潰堆棧:
Date/Time: 2017-06-16 18:15:02.000 +0800
OS Version: iOS 10.2 (14C92)
Report Version: 104
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x00000000 at 0x0000000000000000
Crashed Thread: 0
Application Specific Information:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 CoreFoundation __exceptionPreprocess + 124
1 libobjc.A.dylib objc_exception_throw + 56
2 CoreFoundation -[__NSArrayM removeObjectAtIndex:] + 0
3 UIKit -[UIWebSelectSinglePicker pickerView:didSelectRow:inComponent:] + 72
4 UIKit -[UIPickerView _sendSelectionChangedForComponent:notify:] + 116
5 UIKit -[UIPickerView _sendSelectionChangedFromTable:notify:] + 344
6 UIKit -[UIPickerTableView _scrollingFinished] + 188
7 UIKit -[UIPickerTableView scrollViewDidEndDecelerating:] + 28
8 UIKit -[UIScrollView(UIScrollViewInternal) _scrollViewDidEndDeceleratingForDelegate] + 132
9 UIKit -[UIScrollView(UIScrollViewInternal) _stopScrollDecelerationNotify:] + 332
10 UIKit -[UIScrollView _smoothScrollWithUpdateTime:] + 2356
11 QuartzCore CA::Display::DisplayLinkItem::dispatch(unsigned long long) + 44
12 QuartzCore CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 444
13 IOKit IODispatchCalloutFromCFMessage + 372
14 CoreFoundation __CFMachPortPerform + 180
15 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 56
16 CoreFoundation __CFRunLoopDoSource1 + 436
17 CoreFoundation __CFRunLoopRun + 1840
18 CoreFoundation CFRunLoopRunSpecific + 444
19 GraphicsServices GSEventRunModal + 180
20 UIKit -[UIApplication _run] + 684
21 UIKit UIApplicationMain + 208
22 moma main (main.m:15)
23 libdyld.dylib start + 4
crash分析:
-
首先看看
-[UIWebSelectSinglePicker pickerView:didSelectRow:inComponent:]
方法中具體發(fā)生崩潰的邏輯:1527236825624-image.png
圖中在對optionItems
數(shù)組進(jìn)行objectAtIndex
時直接將方法第二個參數(shù)r15傳了進(jìn)去敦冬,而此時optionItems
是個空數(shù)組辅搬,所以發(fā)生了崩潰。
- 解決方法:
按照上面的分析如果我們?nèi)ook-[UIWebSelectSinglePicker pickerView:didSelectRow:inComponent:]
方法似乎不太靠譜脖旱,這個方法太長堪遂,我們繼續(xù)往上找在哪里調(diào)用了該方法:
void -[UIPickerView _sendSelectionChangedForComponent:notify:](void * self, void * _cmd, long long arg2, bool arg3) {
rcx = arg3;
r14 = arg2;
rbx = self;
if ((rbx->_pickerViewFlags & 0x8) != 0x0) {
rcx = rcx ^ 0x1;
if (rcx == 0x0) {
rax = [rbx selectedRowInComponent:r14];
[rbx->_delegate pickerView:rbx didSelectRow:rax inComponent:r14];
}
}
rdi = rbx;
rdx = r14;
[rdi _noteScrollingFinishedForComponent:rdx];
return;
}
看到這里思路就清晰了,原來崩潰的UIWebSelectSinglePicker
是UIPickerView
的delegate
萌庆,那我們就行調(diào)用delegate
的地方入手進(jìn)行防護(hù)溶褪。
- Code
代碼很簡單,直接貼在這里踊兜。
#import <objc/runtime.h>
@implementation UIPickerView (DEFWebSinglePickCrash)
+ (void)load
{
SEL originalSelector = @selector(_sendSelectionChangedForComponent:notify:);
SEL overrideSelector = @selector(swizzle_sendSelectionChangedForComponent:notify:);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method overrideMethod = class_getInstanceMethod(self, overrideSelector);
BOOL success = class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod));
if (success) {
class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
- (void)swizzle_sendSelectionChangedForComponent:(int)arg1 notify:(BOOL)arg2
{
Class class = NSClassFromString(@"UIWebSelectSinglePicker");
if ([self isKindOfClass:class]) {
NSArray *optionItems = [self valueForKey:@"_optionItems"];
if (optionItems.count > 0) {
[self swizzle_sendSelectionChangedForComponent:arg1 notify:arg2];
}
} else {
[self swizzle_sendSelectionChangedForComponent:arg1 notify:arg2];
}
}
@end