├── README.md ├── UIScrollView+iOS8Safe.h └── UIScrollView+iOS8Safe.m /README.md: -------------------------------------------------------------------------------- 1 | # UIScrollView-iOS8Safe 2 | 3 | 4 | Avoiding UIScrollView EXC_BAD_ACCESS on iOS8 5 | 6 | [Blog](http://ziecho.com/post/ios/avoiding-uiscrollview-exc_bad_access-on-ios8) 7 | -------------------------------------------------------------------------------- /UIScrollView+iOS8Safe.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+iOS8Safe.h 3 | // EasiPass 4 | // 5 | // Created by zie on 26/10/2018. 6 | // Copyright © 2018 ziecho. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface UIScrollView (iOS8Safe) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /UIScrollView+iOS8Safe.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+iOS8Safe.m 3 | // EasiPass 4 | // 5 | // Created by zie on 26/10/2018. 6 | // Copyright © 2018 ziecho. All rights reserved. 7 | // 8 | 9 | #import "UIScrollView+iOS8Safe.h" 10 | #import 11 | 12 | #define object_getIvarValue(object, name) object_getIvar(object, class_getInstanceVariable([object class], name)) 13 | 14 | #define object_setIvarValue(object, name, value) object_setIvar(object, class_getInstanceVariable([object class], name), value) 15 | 16 | #define IOS_VERSION ([[[UIDevice currentDevice] systemVersion] floatValue]) 17 | 18 | CG_INLINE void 19 | SwizzleMethod(Class _class, SEL _originSelector, SEL _newSelector) { 20 | Method oriMethod = class_getInstanceMethod(_class, _originSelector); 21 | Method newMethod = class_getInstanceMethod(_class, _newSelector); 22 | BOOL isAddedMethod = class_addMethod(_class, _originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)); 23 | if (isAddedMethod) { 24 | class_replaceMethod(_class, _newSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); 25 | } else { 26 | method_exchangeImplementations(oriMethod, newMethod); 27 | } 28 | } 29 | 30 | 31 | @interface ReleaseDelegateCleaner : NSObject 32 | @property (nonatomic, strong) NSPointerArray *scrollViews; 33 | @end 34 | 35 | @implementation ReleaseDelegateCleaner 36 | 37 | - (void)dealloc { 38 | [self cleanScrollViewsDelegate]; 39 | } 40 | 41 | - (void)recordDelegatedScrollView:(UIScrollView *)scrollView { 42 | NSUInteger index = [self.scrollViews.allObjects indexOfObject:scrollView]; 43 | if (index == NSNotFound) { 44 | [self.scrollViews addPointer:(__bridge void *)(scrollView)]; 45 | } 46 | } 47 | 48 | - (void)removeDelegatedScrollView:(UIScrollView *)scrollView { 49 | NSUInteger index = [self.scrollViews.allObjects indexOfObject:scrollView]; 50 | if (index != NSNotFound) { 51 | [self.scrollViews removePointerAtIndex:index]; 52 | } 53 | } 54 | 55 | - (void)cleanScrollViewsDelegate { 56 | [self.scrollViews.allObjects enumerateObjectsUsingBlock:^(UIScrollView *scrollView, NSUInteger idx, BOOL * _Nonnull stop) { 57 | object_setIvarValue(scrollView, "_delegate", nil); 58 | if ([scrollView isKindOfClass:[UITableView class]] || [scrollView isKindOfClass:[UICollectionView class]]) { 59 | object_setIvarValue(scrollView, "_dataSource", nil); 60 | } 61 | }]; 62 | } 63 | 64 | - (void)setScrollViews:(NSPointerArray *)scrollViews { 65 | objc_setAssociatedObject(self, @selector(scrollViews), scrollViews, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 66 | } 67 | 68 | - (NSPointerArray *)scrollViews { 69 | NSPointerArray *scrollViews = objc_getAssociatedObject(self, _cmd); 70 | if (!scrollViews) { 71 | scrollViews = [NSPointerArray weakObjectsPointerArray]; 72 | [self setScrollViews:scrollViews]; 73 | } 74 | return scrollViews; 75 | } 76 | 77 | @end 78 | 79 | @interface NSObject (iOS8Safe) 80 | @property (nonatomic, readonly) ReleaseDelegateCleaner *iOS8DelegateCleaner; 81 | @end 82 | 83 | @implementation NSObject (EPiOS8ScrollViewSafe) 84 | 85 | - (ReleaseDelegateCleaner *)iOS8DelegateCleaner { 86 | ReleaseDelegateCleaner *cleaner = objc_getAssociatedObject(self, _cmd); 87 | if (!cleaner) { 88 | cleaner = [ReleaseDelegateCleaner new]; 89 | objc_setAssociatedObject(self, _cmd, cleaner, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 90 | } 91 | return cleaner; 92 | } 93 | 94 | @end 95 | 96 | 97 | @implementation UITableView (iOS8Safe) 98 | 99 | - (void)safe_setDataSource:(id)dataSource { 100 | if (dataSource) { 101 | [[(NSObject *)dataSource iOS8DelegateCleaner] recordDelegatedScrollView:self]; 102 | 103 | } else { 104 | id _dataSource = object_getIvarValue(self, "_dataSource"); 105 | [[(NSObject *)_dataSource iOS8DelegateCleaner] removeDelegatedScrollView:self]; 106 | } 107 | 108 | [self safe_setDataSource:dataSource]; 109 | } 110 | 111 | @end 112 | 113 | 114 | @implementation UICollectionView (iOS8Safe) 115 | 116 | - (void)safe_setDataSource:(id)dataSource { 117 | if (dataSource) { 118 | [[(NSObject *)dataSource iOS8DelegateCleaner] recordDelegatedScrollView:self]; 119 | 120 | } else { 121 | id _dataSource = object_getIvarValue(self, "_dataSource"); 122 | [[(NSObject *)_dataSource iOS8DelegateCleaner] removeDelegatedScrollView:self]; 123 | } 124 | 125 | [self safe_setDataSource:dataSource]; 126 | } 127 | 128 | @end 129 | 130 | 131 | @implementation UIScrollView (iOS8Safe) 132 | 133 | - (void)safe_will_dealloc { 134 | objc_setAssociatedObject(self, @selector(safe_will_dealloc), @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 135 | 136 | [self safe_will_dealloc]; 137 | } 138 | 139 | + (void)load { 140 | if (IOS_VERSION < 9.0) { 141 | SwizzleMethod([UIScrollView class], sel_registerName("dealloc"), @selector(safe_will_dealloc)); 142 | SwizzleMethod([UIScrollView class], @selector(setDelegate:), @selector(safe_setDelegate:)); 143 | 144 | SwizzleMethod([UITableView class], @selector(setDataSource:), @selector(safe_setDataSource:)); 145 | SwizzleMethod([UICollectionView class], @selector(setDataSource:), @selector(safe_setDataSource:)); 146 | } 147 | } 148 | 149 | - (void)safe_setDelegate:(id)delegate { 150 | BOOL willDealloc = [objc_getAssociatedObject(self, @selector(safe_will_dealloc)) boolValue]; 151 | 152 | if (!willDealloc) { 153 | id _delegate = object_getIvarValue(self, "_delegate"); 154 | 155 | if (_delegate != delegate) { 156 | [[(NSObject *)_delegate iOS8DelegateCleaner] removeDelegatedScrollView:self]; 157 | [[(NSObject *)delegate iOS8DelegateCleaner] recordDelegatedScrollView:self]; 158 | } 159 | 160 | } 161 | 162 | [self safe_setDelegate:delegate]; 163 | } 164 | 165 | @end 166 | 167 | --------------------------------------------------------------------------------