UIScrollView _delegateScrollViewAnimationEnd 引起的崩溃处理

我们先看一下崩溃堆栈:

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值