JustMe
JustMe

Reputation: 421

Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section Crash in SwiftUI

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

Answers (2)

Okayokay
Okayokay

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

Anthonius
Anthonius

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

Related Questions