UIScrollView _delegateScrollViewAnimationEnd 引起的崩溃处理

来源:互联网 发布:淘宝正品阿迪达斯店 编辑:程序博客网 时间:2024/05/08 08:20

我们先看一下崩溃堆栈:

0x01c9709f libobjc.A.dylib`objc_msgSend + 19 0x00c3656b UIKit`-[UIScrollView(UIScrollViewInternal) _delegateScrollViewAnimationEnded] + 62 0x00c3665a UIKit`-[UIScrollView(UIScrollViewInternal) _scrollViewAnimationEnded:finished:] + 149 0x00c366da UIKit`-[UIScrollView(UIScrollViewInternal) animator:stopAnimation:fraction:] + 62 0x00ca1a50 UIKit`-[UIAnimator stopAnimation:] + 519 0x00ca2120 UIKit`-[UIAnimator(Static) _advanceAnimationsOfType:withTimestamp:] + 385 0x00ca1c58 UIKit`-[UIAnimator(Static) _LCDHeartbeatCallback:] + 67 0x003562d2 QuartzCore`CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long) + 110 0x0035675f QuartzCore`CA::Display::TimerDisplayLink::callback(__CFRunLoopTimer*, void*) + 161 0x01e7c376 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 22 0x01e7be06 CoreFoundation`__CFRunLoopDoTimer + 534 0x01e63a82 CoreFoundation`__CFRunLoopRun + 1810 0x01e62f44 CoreFoundation`CFRunLoopRunSpecific + 276 0x01e62e1b CoreFoundation`CFRunLoopRunInMode + 123 0x02a157e3 GraphicsServices`GSEventRunModal + 88 0x02a15668 GraphicsServices`GSEventRun + 104 0x00bc9ffc UIKit`UIApplicationMain + 1211 0x00002c6d ZHCRM`main(argc=1, argv=0xbffff3cc) + 141 at main.m:16 0x00002b95 ZHCRM`start + 53 

崩溃的主要原因是因为 scrollview.delegate 在没做完滚动动画的时候释放了, 野指针调用导致。

stackoverflow 上有人讨论过这个问题点击查看。
网上一般的解决方法都是确保处理scrollview delegate 消息的对象在释放前把scrollview.delegate设置为nil。

@implement MyTableViewController- (void)dealloc {    self.tableview.delegate = nil;}

这种方法可以解决崩溃, 但是不够收敛,需要改动的地方很多, 而且也不能确保新的开发人员不会再引起。

接下来给大家介绍一种统一的处理方式。

主要思路:
添加一个DelegateWrapper包装对象,确保UIScrollView在释放前,uiscrollview.delegate 不为nil

对象关系就会变成这样
UITableView -> DelegateWrapper -> DelegateHandler(ViewController)

生命周期管理
然后我们通过 weak 指针可以解决 DelegateHandler的释放问题。

@interface DelegateWrapper : NSObject@property (weak, nonatomic) id delegate;@end 

有了这个对象之后, 设置给 UIScrollerView.delegate 就变成我们的包装对象了。

- (void)setDelegate:(id)delegate{    DelegateWrapper *wrapper = [DelegateWrapper new];    wrapper.delegate = delegate;    scrollerview.delegate = wrapper;    ...} 

通过动态绑定技术,可以很方便的把DelegateWrapper对象嵌入到scrollview 里面,来控制 wrapper 的生命周期。

消息转发
接下来Delegate 还要处理scrollview 发过来的delegate 消息,把它转到真正的delegate对象上。

- (BOOL)respondsToSelector:(SEL)aSelector{    if (!_delegate) {        NSLog(@"call selector:%s", sel_getName(aSelector));        return NO;    }    return [_delegate respondsToSelector:aSelector];}- (id)forwardingTargetForSelector:(SEL)aSelector{    if (_delegate) {        return _delegate;    }    return [super forwardingTargetForSelector:aSelector];}

好了, 现在有了这些消息转发机制后, 真正的delegate handler可以收到消息了。

调用收敛
现在基本的流程和原理都介绍完了, 接下来的事情就是怎样在一个地方处理了, 以后就都不会有这个问题。

入口点 [UIScrollView setDelegate:]
根据Object C 语言的动态性,使用MethodSwizzing 技术可以很好的把 [UIScrollView setDelegate:] 的方法替换成我们的。这样只要在程序初始化后调用一下,以后就不用担心了。

+ (void)hookMethedClass:(Class)class hookSEL:(SEL)hookSEL originalSEL:(SEL)originalSEL myselfSEL:(SEL)mySelfSEL{    Method hookMethod = class_getInstanceMethod(class, hookSEL);    Method mySelfMethod = class_getInstanceMethod([MethodsHooker class], mySelfSEL);    IMP hookMethodIMP = method_getImplementation(hookMethod);    class_addMethod(class, originalSEL, hookMethodIMP, method_getTypeEncoding(hookMethod));    IMP hookMethodMySelfIMP = method_getImplementation(mySelfMethod);    class_replaceMethod(class, hookSEL, hookMethodMySelfIMP, method_getTypeEncoding(hookMethod));}+ (void)hookUIScrollViewSetDelegate{    [MethodsHooker hookMethedClass:NSClassFromString(@"UIScrollView")                           hookSEL:@selector(setDelegate:)                       originalSEL:@selector(originalScrollViewSetDelegate:)                         myselfSEL:@selector(myselfScrollViewSetDelegate:)];}- (void)myselfScrollViewSetDelegate:(id)delegate{    WeakDelegate *weakDelegateObject = [[WeakDelegate alloc] init];    weakDelegateObject.delegate = delegate;    objc_setAssociatedObject(self, "weak_delegate_handler", weakDelegateObject, OBJC_ASSOCIATION_RETAIN);    [self originalScrollViewSetDelegate:weakDelegateObject];}- (void)originalScrollViewSetDelegate:(id)delegate{}

现在只要在didFinishLunch 后调用一下 hookUIScrollViewSetDelegate 这个方法就可以了。

0 0
原创粉丝点击