UIScrollView _delegateScrollViewAnimationEnded 崩潰處理

在項(xiàng)目開發(fā)過程中,多次遇到UIScrollView滑動時(shí)引起的崩潰,在這里分析一下,mark 下處理過程。

崩潰堆棧:

(lldb) bt all
* thread #1: tid = 0x4bb501, 0x00000001058451a8 CoreFoundation`___forwarding___ + 776, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS  (code=EXC_I386_GPFLT)
    frame #0: 0x00000001058451a8 CoreFoundation`___forwarding___ + 776
    frame #1: 0x0000000105844e18 CoreFoundation`__forwarding_prep_0___ + 120
  * frame #2: 0x0000000105d6884d UIKit`-[UIScrollView(UIScrollViewInternal) _delegateScrollViewAnimationEnded] + 46
    frame #3: 0x0000000105d6894f UIKit`-[UIScrollView(UIScrollViewInternal) _scrollViewAnimationEnded:finished:] + 181
    frame #4: 0x0000000105dddab7 UIKit`-[UIAnimator stopAnimation:] + 395
    frame #5: 0x0000000105dde0bf UIKit`-[UIAnimator(Static) _advanceAnimationsOfType:withTimestamp:] + 234
    frame #6: 0x00000001095a5747 QuartzCore`CA::Display::DisplayLinkItem::dispatch() + 37
    frame #7: 0x00000001095a560f QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 315
    frame #8: 0x000000010584df64 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #9: 0x000000010584db25 CoreFoundation`__CFRunLoopDoTimer + 1045
    frame #10: 0x0000000105810e5d CoreFoundation`__CFRunLoopRun + 1901
    frame #11: 0x0000000105810486 CoreFoundation`CFRunLoopRunSpecific + 470
    frame #12: 0x0000000108e799f0 GraphicsServices`GSEventRunModal + 161
    frame #13: 0x0000000105cd1420 UIKit`UIApplicationMain + 1282
    frame #14: 0x000000010504f32f UIScrollViewDelegate`main(argc=1, argv=0x00007fff5abb0360) + 111 at main.m:14
    frame #15: 0x0000000107e5e145 libdyld.dylib`start + 1
    

從崩潰信息里面,我們可以看到 ** stop reason = EXC_BAD_ACCESS **, 非法地址訪問(野指針)。
根據(jù)堆棧順序 *[UIAnimator stopAnimation:] -> [UIScrollView(UIScrollViewInternal) _scrollViewAnimationEnded:finished:] ** 可以看出是動畫完成后,調(diào)用scrollview的方法引起的非法訪問。經(jīng)過實(shí)驗(yàn)驗(yàn)證,是訪問了scrollview.delegate 引起的。 也就是說,scrollview在做動畫的過程中,scrollview.delegate 被釋放了。

簡單寫了一些測試代碼重現(xiàn)了這個(gè)崩潰

@implementation ScrollViewController

- (IBAction)onBackClick:(id)sender
{
    [self.scrollview setContentOffset:CGPointMake(0, self.scrollview.bounds.size.height * 3) animated:YES];
    [self.navigationController popViewControllerAnimated:NO];
}

  • 在iOS7 和 iOS8 的系統(tǒng)下,一點(diǎn)返回按鈕,pop 出當(dāng)前頁面,就會馬上崩潰。
  • 在iOS9下沒有問題(由于屬性修飾符改成weak)
//iOS9 以前
@property(nonatomic,unsafe_unretain) id<UIScrollViewDelegate> delegate; 

//iOS9
@property(nullable,nonatomic,weak) id<UIScrollViewDelegate> delegate; 

解決方法:

崩潰的原因已經(jīng)很明確,只要可以保證UIScrollView 的 delegate對象在釋放的時(shí)候,把scrollview.dlegate = nil; 就可以解決問題。

@implementation ScrollViewController
- (void)dealloc {
    self.scrollview.delegate = nil;
}

主動設(shè)置 scrollview.delegate = nil; 可以很好的解決崩潰。但是稍不注意,項(xiàng)目組的其他開發(fā)同事又很容易忘記,或者一不小心,又引起崩潰了。有沒有辦法可以一勞永逸呢?

更好的解決方法:

主要思路:通過Runtime,修改 dealloc 方法,讓代理對象在釋放時(shí)自動把scrollview.delegate置空。

  1. 首先,通過 method swizzling 給NSObject添加 deallocBlock 查看完成代碼
typedef void (^DeallocCallback)();

@interface NSObject(Deallocing)
+ (void)hookNSObjectDealloc;
- (void)setDeallocCallback:(DeallocCallback)callback;
- (DeallocCallback)deallocCallback;
@end
    
@implementation NSObject(Deallocing)
- (void)myselfDealloc {
    DeallocCallback callback = [self deallocCallback];
    if (callback) {
       callback(); //對象釋放前的主要操作
    }
    //調(diào)用原來的方法
    [self originalDealloc];
}
@end
  1. 通過method swizzling 修改UIScrollView 的 setDelegate 方法
- (void)myselfSetDelegate:(NSObject *)delegate
{
    if (delegate) {
        UIScrollView * __weak weak_self = (UIScrollView *)self;
        [delegate setDeallocCallback:^{
            weak_self.delegate = nil;
        }];
    }
    //調(diào)用原來方法
    [self originalSetDelegate:delegate];
}   
  1. 在App初始化的時(shí)候,調(diào)用一下 ScrollView 的 swizzling 方法,就可以解決這類型的崩潰(包括:UITableView, UIWebView, UICollectionView 動畫時(shí)delegate被釋放引起的崩潰)

總結(jié):

  1. 在做動畫過程中,系統(tǒng)會強(qiáng)引用需要做動畫的view
  2. 在完成動畫后,會更新view的狀態(tài)(注意外部對象被釋放)
  3. iOS9 SDK 更改了屬性的修飾符 (改成weak)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,161評論 4 61
  • 1.1 談一談GCD和NSOperation的區(qū)別? 首先二者都是多線程相關(guān)的概念,當(dāng)然在使用中也是根據(jù)不同情境進(jìn)...
    John_LS閱讀 1,396評論 0 12
  • 上周主要了解到了內(nèi)存的泄露問題還有一些收獲的小的知識點(diǎn),特此記錄下來。 1.左側(cè)的滑動以及橫屏豎屏的切換 1.1左...
    李周閱讀 703評論 0 0

友情鏈接更多精彩內(nèi)容