Reputation: 421
I am running iOS 14.0.1, Xcode 12.0.1 targeting iOS14.
I have data in CoreData that I pull out and display in a SwiftUI List/ForEach. It was working fine on iOS13, but when I went to iOS14 is started getting intermittent crashes. After I add a new object to core data and then go back to the List/ForEach view it repaints the list but sometimes it over counts the number of elements and crashes with a Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section. Since the array of entities from Core Data is observable and mapped directly to the List/ForEach I have no control over the count at all. It never crashes on the iOS 14 simulator, only on a real iPhone. It does not crash ever time I add a new entry, only sometimes seemly at random. It never crashed on iOS13 either on the simulator or a real iPhone.
This is the code to display the list;
List {
ForEach(self.locations, id: \.self) { location in
LocationView(currentLocation: location)
}
.onDelete(perform: self.deleteSelectedRow)
}
Is anyone else having this issue? It appears to be some internal bug to iOS14 ForEach processing with a CoreData created observable object, but if that were true I would think many people would be having the same issue, but I do not see any comments about this some sort of problem. Anyone have any ideas?
Thanks in advance for any an all ideas and help anyone can give!
Upvotes: 0
Views: 813
Reputation: 11
Using runtime swizzle can completely solve this problem. I also encountered this problem and tried many ways. This can make the problem disappear.
#import "PPAvoidCrash.h"
#import <objc/runtime.h>
#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil
@implementation NSObject (SwitchMethod)
+ (BOOL)swizzleInstanceMethod:(SEL)originalSel withMethod:(SEL)newSel {
Method originalMethod = class_getInstanceMethod(self, originalSel);
Method newMethod = class_getInstanceMethod(self, newSel);
if (!originalMethod || !newMethod) return NO;
class_addMethod(self,
originalSel,
class_getMethodImplementation(self, originalSel),
method_getTypeEncoding(originalMethod));
class_addMethod(self,
newSel,
class_getMethodImplementation(self, newSel),
method_getTypeEncoding(newMethod));
method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
class_getInstanceMethod(self, newSel));
return YES;
}
+ (void)swizzlingMethod:(NSString *)systemMethodString systemClassString:(NSString *)systemClassString toSafeMethod:(NSString *)safeMethodString targetClassString:(NSString *)targetClassString {
Method sysMethod = class_getInstanceMethod(NSClassFromString(systemClassString), NSSelectorFromString(systemMethodString));
Method safeMethod = class_getInstanceMethod(NSClassFromString(targetClassString), NSSelectorFromString(safeMethodString));
method_exchangeImplementations(safeMethod,sysMethod);
}
+ (void)handleExceptionBlock:(void (^)(void))block {
@try {
if (block) block();
} @catch (NSException *exception) {
NSLog(@"[PPAvoidCrash] %@", exception.reason);
} @finally {
}
}
@end
@implementation UITableView (AvoidCrash)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(performBatchUpdates:completion:)
withMethod:@selector(ac_performBatchUpdates:completion:)];
[self swizzleInstanceMethod: NSSelectorFromString(@"_Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section:")
withMethod: @selector(ac_Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section:)];
/// Section
[self swizzleInstanceMethod:@selector(insertSections:withRowAnimation:)
withMethod:@selector(ac_insertSections:withRowAnimation:)];
[self swizzleInstanceMethod:@selector(deleteSections:withRowAnimation:)
withMethod:@selector(ac_deleteSections:withRowAnimation:)];
[self swizzleInstanceMethod:@selector(moveSection:toSection:)
withMethod:@selector(ac_moveSection:toSection:)];
[self swizzleInstanceMethod:@selector(reloadSections:withRowAnimation:)
withMethod:@selector(ac_reloadSections:withRowAnimation:)];
/// Rows
[self swizzleInstanceMethod:@selector(reloadRowsAtIndexPaths:withRowAnimation:)
withMethod:@selector(ac_reloadRowsAtIndexPaths:withRowAnimation:)];
[self swizzleInstanceMethod:@selector(insertRowsAtIndexPaths:withRowAnimation:)
withMethod:@selector(ac_insertRowsAtIndexPaths:withRowAnimation:)];
[self swizzleInstanceMethod:@selector(deleteRowsAtIndexPaths:withRowAnimation:)
withMethod:@selector(ac_deleteRowsAtIndexPaths:withRowAnimation:)];
[self swizzleInstanceMethod:@selector(moveRowAtIndexPath:toIndexPath:)
withMethod:@selector(ac_moveRowAtIndexPath:toIndexPath:)];
[self swizzleInstanceMethod:@selector(scrollToRowAtIndexPath:atScrollPosition:animated:)
withMethod:@selector(ac_scrollToRowAtIndexPath:atScrollPosition:animated:)];
});
}
/// MARK: - #1046002 NSInternalInconsistencyException
/// 尝试解决SwiftUI iOS13版本 因为系统调用performBatchUpdates导致的crash
- (void)ac_performBatchUpdates:(void (NS_NOESCAPE ^ _Nullable)(void))updates completion:(void (^ _Nullable)(BOOL finished))completion {
[NSObject handleExceptionBlock:^{
[UIView performWithoutAnimation:^{
[self beginUpdates];
BLOCK_SAFE_RUN(updates); /// 如果updates为NULL 则会crash 所以进行处理
[self endUpdates];
}];
completion(true);
}];
}
- (void)ac_Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section:(NSInteger)section {
[NSObject handleExceptionBlock:^{
NSLog(@"Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section");
}];
}
/// MARK: - Sections Crash Handler
- (void)ac_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
[NSObject handleExceptionBlock:^{
[self ac_insertSections:sections withRowAnimation:animation];
}];
}
- (void)ac_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
[NSObject handleExceptionBlock:^{
[self ac_deleteSections:sections withRowAnimation:animation];
}];
}
- (void)ac_moveSection:(NSInteger)section toSection:(NSInteger)newSection {
[NSObject handleExceptionBlock:^{
[self ac_moveSection:section toSection:newSection];
}];
}
- (void)ac_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
[NSObject handleExceptionBlock:^{
[self ac_reloadSections:sections withRowAnimation:animation];
}];
}
/// MARK: - Rows Crash Handler
- (void)ac_reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
[NSObject handleExceptionBlock:^{
[self ac_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
}];
}
- (void)ac_insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
[NSObject handleExceptionBlock:^{
[self ac_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];
}];
}
- (void)ac_deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
[NSObject handleExceptionBlock:^{
[self ac_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];
}];
}
- (void)ac_moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath {
[NSObject handleExceptionBlock:^{
[self ac_moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
}];
}
- (void)ac_scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated {
[NSObject handleExceptionBlock:^{
[self ac_scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
}];
}
@end
Upvotes: 0
Reputation: 340
the crash is due to the underlying TableView code inside SwiftUI framework. Refer to this link for more info: https://developer.apple.com/forums/thread/129956?page=1#650267022
Upvotes: 0