xmartlabs / Eureka

Elegant iOS form builder in Swift
https://eurekacommunity.github.io
MIT License
11.78k stars 1.33k forks source link

Remove All is randomly crashing the app, out of bounds #2155

Open navyseai opened 3 years ago

navyseai commented 3 years ago

I'm unable to reproduce it but it keeps happening.

xcode 12.4 (12D4e) Eureka 5.3.3 iOS 14.4.2

Fatal Exception: NSRangeException *** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array Section.subscript.getter

0 CoreFoundation 0x1a2eb99d8 exceptionPreprocess 1 libobjc.A.dylib 0x1b723cb54 objc_exception_throw 2 CoreFoundation 0x1a2f23d98 -[NSCFString characterAtIndex:].cold.1 3 CoreFoundation 0x1a2da2b64 -[NSArrayM objectAtIndex:] 4 Eureka 0x104cdde30 Section.subscript.getter + 249 (Section.swift:249) 5 Eureka 0x104c7c434 FormViewController.tableView(:heightForRowAt:) 6 Eureka 0x104c7c5c8 @objc FormViewController.tableView(:heightForRowAt:) 7 UIKitCore 0x1a593f6e0 -[UITableView _classicHeightForRowAtIndexPath:] 8 UIKitCore 0x1a5943d40 -[UITableView _heightForRowAtIndexPath:] 9 UIKitCore 0x1a59564dc -[UISectionRowData heightForRow:inSection:canGuess:] 10 UIKitCore 0x1a595b394 -[UITableViewRowData heightForRow:inSection:canGuess:adjustForReorderedRow:] 11 UIKitCore 0x1a595c9bc -[UITableViewRowData rectForRow:inSection:heightCanBeGuessed:] 12 UIKitCore 0x1a595cb40 -[UITableViewRowData rectForGlobalRow:heightCanBeGuessed:] 13 UIKitCore 0x1a595d334 -[UITableViewRowData globalRowsInRect:canGuess:] 14 UIKitCore 0x1a5906ed0 -[UITableView _visibleGlobalRowsInRect:canGuess:] 15 UIKitCore 0x1a5905a58 -[UITableView _updateVisibleCellsNow:] 16 UIKitCore 0x1a5900b9c -[UITableView setupCellAnimations] 17 UIKitCore 0x1a591a744 -[UITableView beginUpdates] 18 Eureka 0x104c7b8b8 FormViewController.rowsHaveBeenRemoved(:at:) () 19 Eureka 0x104ce8aec specialized Section.KVOWrapper.observeValue(forKeyPath:of:change:context:) () 20 Eureka 0x104cdfaa8 @objc Section.KVOWrapper.observeValue(forKeyPath:of:change:context:) () 21 Foundation 0x1a41476c4 NSKeyValueNotifyObserver 22 Foundation 0x1a4149af8 NSKeyValueDidChange 23 Foundation 0x1a4146ef0 NSKeyValueDidChangeWithPerThreadPendingNotifications 24 Foundation 0x1a413d3f8 -[NSKeyValueNotifyingMutableArray removeObjectAtIndex:] 25 CoreFoundation 0x1a2da37ac -[NSMutableArray removeObjectsInRange:inArray:range:] 26 Eureka 0x104ce3140 Section.removeAll(where:) () 27 MyApp 0x1044b27a8 closure #1 in SettingsVC.reloadDevicesSection() + 412 (SettingsVC.swift:412) 28 MyApp 0x1044b6234 partial apply for thunk for @callee_guaranteed () -> () () 29 MyApp 0x1044af580 thunk for @escaping @calleeguaranteed () -> () () 30 UIKitCore 0x1a5c21704 +[UIView(Animation) performWithoutAnimation:] 31 MyApp 0x1044b2708 SettingsVC.reloadDevicesSection() () 32 MyApp 0x1044b0cec @objc SettingsVC.viewWillAppear(:) () 33 UIKitCore 0x1a5068a4c -[UIViewController _setViewAppearState:isAnimating:] 34 UIKitCore 0x1a50691d0 -[UIViewController viewWillAppear:] 35 UIKitCore 0x1a4f76bd8 -[UITabBarController viewWillAppear:] 36 MyApp 0x1043e22b8 HomeVC.viewWillAppear(:) + 34 (HomeVC.swift:34) 37 MyApp 0x1043e26c4 @objc HomeVC.viewWillAppear(:) () 38 UIKitCore 0x1a5068a4c -[UIViewController _setViewAppearState:isAnimating:] 39 UIKitCore 0x1a50691d0 -[UIViewController __viewWillAppear:] 40 UIKitCore 0x1a4fa0164 -[UINavigationController viewWillAppear:] 41 UIKitCore 0x1a5068a4c -[UIViewController _setViewAppearState:isAnimating:] 42 UIKitCore 0x1a50691d0 -[UIViewController viewWillAppear:] 43 UIKitCore 0x1a4f55fa4 56-[UIPresentationController runTransitionForCurrentState]_block_invoke.466 44 UIKitCore 0x1a5bcdf08 -[_UIAfterCACommitBlock run] 45 UIKitCore 0x1a5733984 _runAfterCACommitDeferredBlocks 46 UIKitCore 0x1a5722eb4 _cleanUpAfterCAFlushAndRunDeferredBlocks 47 UIKitCore 0x1a5754484 _afterCACommitHandler 48 CoreFoundation 0x1a2e3887c CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION 49 CoreFoundation 0x1a2e32f50 CFRunLoopDoObservers 50 CoreFoundation 0x1a2e33498 CFRunLoopRun 51 CoreFoundation 0x1a2e32ba0 CFRunLoopRunSpecific 52 GraphicsServices 0x1b9b98598 GSEventRunModal 53 UIKitCore 0x1a57242f4 -[UIApplication run] 54 UIKitCore 0x1a5729874 UIApplicationMain 55 libswiftUIKit.dylib 0x1b65b0b54 UIApplicationMain(:::_:) 56 MyApp 0x1043149b0 main (BleServices.swift) 57 libdyld.dylib 0x1a2b11568 start

basically we create the section once, then we have refresh our specific section by deleting all rows and recreating them. It might be trigger by viewDidAppear or Observers watching Realm


    @objc private func reloadDevicesSection() {

        if let devicesSection = self.vc.form.sectionBy(tag: SettingsSection.Devices.rawValue) {
            UIView.performWithoutAnimation {
                devicesSection.reload()
                devicesSection.removeAll(where: { $0.indexPath?.section == 0})
                populateDevicesSection()
                self.vc.tableView.reloadData()                     // this is causing a crash
            }
        }
    }
   private func populateDevicesSection() {

        if let realm = try? Realm(), let devicesSection = self.vc.form.sectionBy(tag: SettingsSection.Devices.rawValue) {

        let devices = realm.objects(Devices.self) 
        for device in devices {
                devicesSection <<< deviceRow(device: device)
         }
   }
}

I've already tried to use both eureka removeAll functions. removeAll(keepingCapacity keepCapacity: Bool = false) public func removeAll(where shouldBeRemoved: (BaseRow) throws -> Bool) rethrows {

I've tried different aproches to refresh just the lines/section what we need to

The number of objects to add might be from 0 to 3

The last crash happen when the application was going to foreground.

i've also check:

2048

2141

it was already happening in the previous version we were using

mats-claassen commented 3 years ago

I don't think you need to call devicesSection.reload() at the beginning if you are to remove all rows later. Does it change anything if you remove that?

navyseai commented 3 years ago

I don't think you need to call devicesSection.reload() at the beginning if you are to remove all rows later. Does it change anything if you remove that?

yea that was probably an attempt to force the app to enter a consistent state before the intended changes

navyseai commented 3 years ago

i've stumble on this

do you think that wrapping my populateDevicesSection() and self.vc.tableView.reloadData()
in beginUpdates and endUpdates would help?

mats-claassen commented 3 years ago

It might help. Have you tried it?

navyseai commented 3 years ago

It might help. Have you tried it?

We are currently testing it but like I said I haven't been able to reproduce it. If it passes in QA we will archive the bug and see if re-appears.

chickendiver commented 3 years ago

Seeing the same issue here. Only appearing in production, can't reproduce in QA.

Eureka version: 5.3.4 XCode verison: 13.0 Swift version: 5.0

Fatal Exception: NSRangeException
0  CoreFoundation                 0x129708 __exceptionPreprocess
1  libobjc.A.dylib                0x287a8 objc_exception_throw
2  CoreFoundation                 0x19b9c8 -[__NSCFString characterAtIndex:].cold.1
3  CoreFoundation                 0x3870 -[__NSArrayM objectAtIndex:]
4  Eureka                         0x19c00 FormViewController.tableView(_:heightForRowAt:)
5  Eureka                         0x19dec @objc FormViewController.tableView(_:heightForRowAt:)
6  UIKitCore                      0xe0018c -[UITableView _classicHeightForRowAtIndexPath:]
7  UIKitCore                      0xe04a6c -[UITableView _heightForRowAtIndexPath:]
8  UIKitCore                      0xe17d90 -[UISectionRowData heightForRow:inSection:canGuess:]
9  UIKitCore                      0xe1cdfc -[UITableViewRowData heightForRow:inSection:canGuess:adjustForReorderedRow:]
10 UIKitCore                      0xe1e4b0 -[UITableViewRowData rectForRow:inSection:heightCanBeGuessed:]
11 UIKitCore                      0xe1e644 -[UITableViewRowData rectForGlobalRow:heightCanBeGuessed:]
12 UIKitCore                      0xdc4d6c -[UITableView _updateVisibleCellsNow:]
13 UIKitCore                      0xdbf268 -[UITableView _setupCellAnimations]
14 UIKitCore                      0xdd9c40 -[UITableView beginUpdates]
15 Eureka                         0x18da4 FormViewController.sectionsHaveBeenRemoved(_:at:) + 709 (Core.swift:709)
16 Eureka                         0x5a930 specialized Form.KVOWrapper.observeValue(forKeyPath:of:change:context:) + 333 (Form.swift:333)
17 Eureka                         0x50940 @objc Form.KVOWrapper.observeValue(forKeyPath:of:change:context:) (<compiler-generated>)
18 Foundation                     0x10be1c NSKeyValueNotifyObserver
19 Foundation                     0x10e394 NSKeyValueDidChange
20 Foundation                     0x10de00 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
21 Foundation                     0x470e0 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
22 Foundation                     0x107ebc _NSSetObjectValueAndNotify
23 Eureka                         0x59158 specialized Form.removeAll(keepingCapacity:) + 320 (Form.swift:320)
24 Air Trail                      0x29bc70 SettingsFormViewController.setupFormViews() + 44 (SettingsViewController.swift:44)
25 Air Trail                      0x29cb30 SettingsFormViewController.viewWillAppear(_:) + 89 (SettingsViewController.swift:89)
26 Air Trail                      0x29cd10 @objc SettingsFormViewController.viewWillAppear(_:) (<compiler-generated>)
27 UIKitCore                      0x4a780c -[UIViewController _setViewAppearState:isAnimating:]
28 UIKitCore                      0x4a7e1c __52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke
29 CoreFoundation                 0xa7564 __NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__
30 CoreFoundation                 0x1b4c -[__NSSingleObjectArrayI enumerateObjectsWithOptions:usingBlock:]
31 UIKitCore                      0x4a7ab8 -[UIViewController _setViewAppearState:isAnimating:]
32 UIKitCore                      0x4a7fcc -[UIViewController __viewWillAppear:]
33 UIKitCore                      0x3b3494 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:]
34 UIKitCore                      0x3aea70 -[UITabBarController _setSelectedViewController:]
35 UIKitCore                      0x3ae874 -[UITabBarController setSelectedViewController:]
36 UIKitCore                      0x3b2648 -[UITabBarController _tabBarItemClicked:]
37 UIKitCore                      0xbd163c -[UIApplication sendAction:to:from:forEvent:]
38 UIKitCore                      0x1e640c -[UITabBar _sendAction:withEvent:]
39 UIKitCore                      0xbd163c -[UIApplication sendAction:to:from:forEvent:]
40 UIKitCore                      0x5036ac -[UIControl sendAction:to:forEvent:]
41 UIKitCore                      0x5039fc -[UIControl _sendActionsForEvents:withEvent:]
42 UIKitCore                      0x1e9374 -[UITabBar _buttonUp:]
43 UIKitCore                      0xbd163c -[UIApplication sendAction:to:from:forEvent:]
44 UIKitCore                      0x5036ac -[UIControl sendAction:to:forEvent:]
45 UIKitCore                      0x5039fc -[UIControl _sendActionsForEvents:withEvent:]
46 UIKitCore                      0x502278 -[UIControl touchesEnded:withEvent:]
47 UIKitCore                      0x7042f8 _UIGestureEnvironmentUpdate
48 CoreFoundation                 0xa2588 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
49 CoreFoundation                 0x9c6ac __CFRunLoopDoObservers
50 CoreFoundation                 0x9cc58 __CFRunLoopRun
51 CoreFoundation                 0x9c308 CFRunLoopRunSpecific
52 GraphicsServices               0x3734 GSEventRunModal
53 UIKitCore                      0xbca75c -[UIApplication _run]
54 UIKitCore                      0xbcffcc UIApplicationMain
REDACTED
56 libdyld.dylib                  0x1cf8 start

And the relevant code:

func setupFormViews() {

        UIView.setAnimationsEnabled(false)

        form.removeAll()

        form
        +++ Section() { section in section.header = ReusableSectionHeaderView.compose(with: "Title") }
        <<< {A ROW}

        if DedicatedAircraftDeviceManager.paired {
            form
            +++ Section() { section in section.header = ReusableSectionHeaderView.compose(with: "Section Title") }
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
                <<< {A ROW}
        }

        if {CONDITIONAL} {
            form
            +++ Section() { section in section.header = ReusableSectionHeaderView.compose(with: "Title ") }
            <<< SettingsRowComposer.userLogoutRow(delegate: self)
        } else {
            form
            +++ Section() { section in section.header = ReusableSectionHeaderView.compose(with: "Title") }
            <<< {A ROW}

            +++ Section() { section in section.header = ReusableSectionHeaderView.compose(with: "Title") }
            <<< {A ROW}
        }

        form
        +++ Section() { section in section.header = ReusableSectionHeaderView.compose(with: "Title") }
        <<< {A ROW}

        UIView.setAnimationsEnabled(true)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        setupFormViews()

    }
chickendiver commented 3 years ago

We are also seeing another crash that may be relevant here.

Here's the stacktrace:

Crashed: com.apple.main-thread
0  UIKitCore                      0xe1d3e4 -[UITableViewRowData sectionLocationForRow:inSection:] + 40
1  UIKitCore                      0xe24d24 -[_UITableViewUpdateSupport(Private) _setupAnimationsForExistingVisibleCells] + 784
2  UIKitCore                      0xe2bc7c -[_UITableViewUpdateSupport _setupAnimations] + 112
3  UIKitCore                      0xdc9890 -[UITableView _updateWithItems:updateSupport:] + 2424
4  UIKitCore                      0xdc1e54 -[UITableView _endCellAnimationsWithContext:] + 11172
5  UIKitCore                      0xdd9d80 -[UITableView endUpdatesWithContext:] + 128
6  Eureka                         0x18ed8 FormViewController.sectionsHaveBeenRemoved(_:at:) + 712 (Core.swift:712)
7  Eureka                         0x5a9a8 specialized Form.KVOWrapper.observeValue(forKeyPath:of:change:context:) + 333 (Form.swift:333)
8  Eureka                         0x509b8 @objc Form.KVOWrapper.observeValue(forKeyPath:of:change:context:) + 4326148536 (<compiler-generated>:4326148536)
9  Foundation                     0x10be1c NSKeyValueNotifyObserver + 292
10 Foundation                     0x10e394 NSKeyValueDidChange + 328
11 Foundation                     0x10de00 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 644
12 Foundation                     0x470e0 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 72
13 Foundation                     0x107ebc _NSSetObjectValueAndNotify + 312
14 Eureka                         0x591d0 specialized Form.removeAll(keepingCapacity:) + 320 (Form.swift:320)
15 Air Trail                      0xbaa8c closure #1 in IndividualAircraftLogViewController.displayAppropriateSegment(defaultSegment:) + 347 (IndividualAircraftLogViewController+General.swift:347)
16 Air Trail                      0x870614 thunk for @escaping @callee_guaranteed () -> () + 4316251668 (<compiler-generated>:4316251668)
17 libdispatch.dylib              0x13484 _dispatch_block_async_invoke2 + 148
18 libdispatch.dylib              0x481c _dispatch_client_callout + 20
19 libdispatch.dylib              0x12c70 _dispatch_main_queue_callback_4CF + 884
20 CoreFoundation                 0xa3340 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
21 CoreFoundation                 0x9d218 __CFRunLoopRun + 2524
22 CoreFoundation                 0x9c308 CFRunLoopRunSpecific + 600
23 GraphicsServices               0x3734 GSEventRunModal + 164
24 UIKitCore                      0xbca75c -[UIApplication _run] + 1072
25 UIKitCore                      0xbcffcc UIApplicationMain + 168
REDACTED
27 libdyld.dylib                  0x1cf8 start + 4
mats-claassen commented 3 years ago

@chickendiver is the purpose of your code to completely reset the form? You could try just creating a form on a separate variable and end with self.form = newForm.

For example:

let newForm = Form()
newForm +++ ...
    <<< ...

self.form = newForm

This should avoid lots of removing and adding to the form

chickendiver commented 3 years ago

This is awesome, thank you! We've implemented this solution, and it appears to be working great in our QA environment. Will monitor in prod.

alexanderhenne commented 2 years ago

Wrapping removeAll calls in beginUpdates/endUpdates fixed it for me:

[formViewController.]tableView.beginUpdates()
[formViewController.]form.removeAll()
[formViewController.]tableView.endUpdates()

You can read why it happens here: https://openradar.appspot.com/36072360

There Apple replied recommending to place data source mutation and table view modification inside batch updates blocks or begin/end calls. That way the table will refresh when endUpdates is called, and not before. Basically form.removeAll removes from the data source and updates the table but it does not do it in one go. So the table sometimes thinks an item still exists in the data source when it doesn't, causing the crash.

alexanderhenne commented 1 year ago

@mats-claassen Have you seen the post above?

mats-claassen commented 1 year ago

Hi @alexanderhenne, yes I have seen it. I haven't heard of any issues in a while.

As the Form does not have direct access to tableView or controller, the way you do it seems reasonable (as opposed to including that logic inside form. removeAll). Also it has to be noted that form.removeAll will eventually trigger a call to FormViewController.sectionsHaveBeenRemoved and/or rowsHaveBeenRemoved, which call beginUpdates and endUpdates around deleting sections/rows from the table (unless these functions are overridden and do not call super, or animateTableView is set to False). So these might also be reasons for crashes I suppose

rahulzabble commented 1 month ago

I am still getting this issue randomly on certain devices. Specifically on an iPhone 11 with iOS 17.7.0, the build was created with Xcode 16 with iOS 18 support.

Snippet of the crash log:

Last Exception Backtrace: 0 CoreFoundation 0x1a8292b28 exceptionPreprocess + 164 (NSException.m:249) 1 libobjc.A.dylib 0x1a010ef78 objc_exception_throw + 60 (objc-exception.mm:356) 2 CoreFoundation 0x1a81cafa4 -[NSArrayM objectAtIndex:] + 592 (NSArrayM.m:0) 3 Eureka 0x106e99e60 Form.subscript.getter + 20 (Form.swift:166) 4 Eureka 0x106e99e60 Form.subscript.getter + 156 (Form.swift:67) 5 Eureka 0x106e75f68 FormViewController.tableView(:didSelectRowAt:) + 192 (Core.swift:790) 6 Eureka 0x106e760bc @objc FormViewController.tableView(:didSelectRowAt:) + 120 (/:0) 7 UIKitCore 0x1ab42fa80 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:deselectPrevious:performCustomSelectionAction:] + 1232 (UITableView.m:9323) 8 UIKitCore 0x1ab42fdac -[UITableView _userSelectRowAtPendingSelectionIndexPath:animatedSelection:] + 268 (UITableView.m:9377) 9 UIKitCore 0x1ab42feac -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 216 (UITableView.m:9396) 10 UIKitCore 0x1aa4cd8f0 -[_UIAfterCACommitBlock run] + 72 (_UIAfterCACommitQueue.m:137) 11 UIKitCore 0x1aa4cd6d4 -[_UIAfterCACommitQueue flush] + 164 (_UIAfterCACommitQueue.m:228) 12 UIKitCore 0x1aa4cd5ec _runAfterCACommitDeferredBlocks + 496 (UIApplication.m:3123) 13 UIKitCore 0x1aa4cd378 _cleanUpAfterCAFlushAndRunDeferredBlocks + 100 (UIApplication.m:3087) 14 UIKitCore 0x1aa4b4a88 _afterCACommitHandler + 64 (UIApplication.m:3138) 15 CoreFoundation 0x1a81dbd3c CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION + 36 (CFRunLoop.c:1789) 16 CoreFoundation 0x1a81da738 CFRunLoopDoObservers + 552 (CFRunLoop.c:1902) 17 CoreFoundation 0x1a81d9e50 CFRunLoopRun + 1028 (CFRunLoop.c:2983) 18 CoreFoundation 0x1a81d9968 CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420) 19 GraphicsServices 0x1ec4cf4e0 GSEventRunModal + 164 (GSEvent.c:2196) 20 UIKitCore 0x1aa64cedc -[UIApplication _run] + 888 (UIApplication.m:3692) 21 UIKitCore 0x1aa64c518 UIApplicationMain + 340 (UIApplication.m:5282) 22 Log Zero 0x104faef5c main + 64 (AppDelegate.swift:28) 23 dyld 0x1cb6fad84 start + 2240 (dyldMain.cpp:1298)