Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'NSInternalInconsistencyException' crash happening when query is changing for CollectionViewDataSource #234

Closed
ghost opened this issue Feb 1, 2017 · 32 comments
Assignees

Comments

@ghost
Copy link

ghost commented Feb 1, 2017

Step 2: Describe your environment

  • Objective C or Swift: Swift
  • iOS version: 10.2
  • Firebase SDK version: 3.12.0
  • FirebaseUI version: 1.0.0
  • CocoaPods Version: 1.2.0

Step 3: Describe the problem:

For a little more background, I have received help from Morgan Chen regarding my problem on StackOverflow: here

On my collection view, I have a segmented control that allows users to toggle queries from online to offline users, and vice versa.

Even after clearing out the data source as recommended, the app crashes because of NSInternalInconsistencyExceptions.

I've been clearing out my data source like this:

collectionView.dataSource = nil
collectionViewDataSource = nil

I am still getting crashes.

Steps to reproduce:

  1. Create query for users that are online.
  2. Remove old data source and then create new query for users that are offline.
  3. Repeat until crash.

Observed Results:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0.  The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
*** First throw call stack:
(0x1838511b8 0x18228855c 0x18385108c 0x18430902c 0x18a00014c 0x1898d5d4c 0x1002680a8 0x100268ddc 0x100243ef4 0x102435258 0x102435218 0x10243a280 0x1837fe810 0x1837fc3fc 0x18372a2b8 0x1851de198 0x1897717fc 0x18976c534 0x10010a65c 0x18270d5b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

Expected Results:

  • I would like for the app to toggle online and offline users as much as they'd like without an app crash.

Relevant Code:

extension BrowseVC {
func getUserData() {
  let isOnline = (segmentedControl.selectedSegmentIndex == 0) ? true : false
  generateQuery(isOnline: isOnline)
  populateDataSource()
}

func resetQuery() {
  removeOldData()
  getUserData()
}

func removeOldData() {
  collectionView.dataSource = nil
  collectionViewDataSource = nil
}

func generateQuery(isOnline: Bool) {
  if let selectedShowMe = UserDefaults.sharedInstance.string(forKey: Constants.ProfileKeys.ShowMe) {
    let forMen = (selectedShowMe == Constants.ProfileValues.Men) ? true : false
    query = FirebaseDB.sharedInstance.buildQuery(forMen: forMen, isOnline: isOnline)
  } else {
    query = FirebaseDB.sharedInstance.buildQuery(forMen: false, isOnline: isOnline)
  }
}

func populateDataSource() {
  if let query = query {
    collectionViewDataSource = FUICollectionViewDataSource(query: query, view: collectionView, populateCell: { (view, indexPath, snapshot) -> ProfileCVCell in
      if let cell = view.dequeueReusableCell(withReuseIdentifier: Constants.CollectionViewCellIdentifiers.ProfileCVCell, for: indexPath) as? ProfileCVCell {
        if let userDictionary = snapshot.value as? [String: AnyObject] {
          if let user = User(uid: snapshot.key, userDictionary: userDictionary) {
            cell.populate(withUser: user)
          }
        }

        return cell
      }
      return ProfileCVCell()
    })

    collectionView.dataSource = collectionViewDataSource
  }
}
}

Initially getUserData() is being called when the VC is first created, but for every query change, resetQuery() is being called which removes the old data source and runs getUserData() again.

@swftvsn
Copy link

swftvsn commented Feb 2, 2017

This affects also us, please merge and release 2.0.2 as soon as possible, thank you!

@morganchen12
Copy link
Contributor

This has been fixed in v2.0.2. Now, when you don't want a data source to send updates to its view component anymore, just set it to nil.

dataSource.tableView = nil

@swftvsn
Copy link

swftvsn commented Feb 3, 2017

Thanks for the quick fix! Now it is working correctly.

@ghost
Copy link
Author

ghost commented Feb 5, 2017

Hey @morganchen12 ,

Thanks for the quick update, but I've been messing around with the code and I'm still getting crashes. Figuring that these are FUI objects I thought I'd ask you if you know what's up.

To give you an update,

I'm clearing out the old data like this

collectionViewDataSource?.collectionView = nil
collectionView.dataSource = nil
collectionViewDataSource = nil

And I'm getting this crash:

When I reset the data source, then wait a second before starting the next query

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid
update: invalid number of items in section 0.  The number of items contained in an existing section 
after the update (1) must be equal to the number of items contained in that section before the 
update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 
deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 
moved out).'
*** First throw call stack:
(0x1845a91b8 0x182fe055c 0x1845a908c 0x18506102c 0x18ad5814c 0x18a62dd4c 0x100252d5c 
0x100254300 0x100253a10 0x10022eafc 0x102429258 0x102429218 0x10242e280 0x184556810 
0x1845543fc 0x1844822b8 0x185f36198 0x18a4c97fc 0x18a4c4534 0x1000f408c 0x1834655b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

or

When I reset the data source, then immediately start the next query

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 
'request for index path for global index 9223372036854775807 when there are only 0 items in the 
collection view'
*** First throw call stack:
(0x1845a91b8 0x182fe055c 0x1845a908c 0x18506102c 0x18a6307f0 0x18a6347b0 0x18a632cc8 
0x18ad5b200 0x18a46c7dc 0x18ad5a230 0x18ad582d0 0x18a62dd4c 0x1001c2d7c 0x1001c4320 
0x1001c3a30 0x10019eb1c 0x102395258 0x102395218 0x10239a280 0x184556810 0x1845543fc 
0x1844822b8 0x185f36198 0x18a4c97fc 0x18a4c4534 0x100064540 0x1834655b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

Stack trace:

* thread #1: tid = 0xe55a, 0x0000000183577014 libsystem_kernel.dylib`__pthread_kill + 8, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x0000000183577014 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x000000018363f450 libsystem_pthread.dylib`pthread_kill + 112
    frame #2: 0x00000001834eb400 libsystem_c.dylib`abort + 140
    frame #3: 0x0000000182fb52d4 libc++abi.dylib`abort_message + 132
    frame #4: 0x0000000182fd2cc0 libc++abi.dylib`default_terminate_handler() + 304
    frame #5: 0x0000000182fe0844 libobjc.A.dylib`_objc_terminate() + 124
    frame #6: 0x0000000182fcf66c libc++abi.dylib`std::__terminate(void (*)()) + 16
    frame #7: 0x0000000182fcef84 libc++abi.dylib`__cxa_throw + 136
    frame #8: 0x0000000182fe0690 libobjc.A.dylib`objc_exception_throw + 364
    frame #9: 0x00000001845a908c CoreFoundation`+[NSException raise:format:arguments:] + 104
    frame #10: 0x000000018506102c Foundation`-[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 112
    frame #11: 0x000000018a6307f0 UIKit`-[UICollectionViewData indexPathForItemAtGlobalIndex:] + 300
    frame #12: 0x000000018a6347b0 UIKit`-[UICollectionViewData layoutAttributesForGlobalItemIndex:] + 28
    frame #13: 0x000000018a632cc8 UIKit`-[UICollectionView _viewAnimationsForCurrentUpdate] + 6188
    frame #14: 0x000000018ad5b200 UIKit`__71-[UICollectionView _updateWithItems:tentativelyForReordering:animator:]_block_invoke.1993 + 132
    frame #15: 0x000000018a46c7dc UIKit`+[UIView(Animation) performWithoutAnimation:] + 104
    frame #16: 0x000000018ad5a230 UIKit`-[UICollectionView _updateWithItems:tentativelyForReordering:animator:] + 3224
    frame #17: 0x000000018ad582d0 UIKit`-[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] + 13816
    frame #18: 0x000000018a62dd4c UIKit`-[UICollectionView _updateRowsAtIndexPaths:updateAction:] + 364
  * frame #19: 0x00000001001c2d7c CPQK`-[FUICollectionViewDataSource array:didAddObject:atIndex:](self=<unavailable>, _cmd=<unavailable>, array=<unavailable>, object=<unavailable>, index=<unavailable>) + 168 at FUICollectionViewDataSource.m:54 [opt]
    frame #20: 0x00000001001c4320 CPQK`-[FUIArray insertSnapshot:withPreviousChildKey:](self=0x0000000170443e10, _cmd=<unavailable>, snap=<unavailable>, previous=<unavailable>) + 264 at FUIArray.m:183 [opt]
    frame #21: 0x00000001001c3a30 CPQK`__24-[FUIArray observeQuery]_block_invoke((null)=0x0000000170443c60, snapshot=<unavailable>, previousChildKey=<unavailable>) + 100 at FUIArray.m:84 [opt]
    frame #22: 0x000000010019eb1c CPQK`__43-[FChildEventRegistration fireEvent:queue:]_block_invoke.68((null)=<unavailable>) + 96 at FChildEventRegistration.m:61 [opt]
    frame #23: 0x0000000102395258 libdispatch.dylib`_dispatch_call_block_and_release + 24
    frame #24: 0x0000000102395218 libdispatch.dylib`_dispatch_client_callout + 16
    frame #25: 0x000000010239a280 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1200
    frame #26: 0x0000000184556810 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
    frame #27: 0x00000001845543fc CoreFoundation`__CFRunLoopRun + 1660
    frame #28: 0x00000001844822b8 CoreFoundation`CFRunLoopRunSpecific + 444
    frame #29: 0x0000000185f36198 GraphicsServices`GSEventRunModal + 180
    frame #30: 0x000000018a4c97fc UIKit`-[UIApplication _run] + 684
    frame #31: 0x000000018a4c4534 UIKit`UIApplicationMain + 208
    frame #32: 0x0000000100064540 CPQK`main + 140 at AppDelegate.swift:15
    frame #33: 0x00000001834655b8 libdyld.dylib`start + 4

  thread #3: tid = 0xe5c3, 0x0000000183577a88 libsystem_kernel.dylib`__workq_kernreturn + 8
    frame #0: 0x0000000183577a88 libsystem_kernel.dylib`__workq_kernreturn + 8
    frame #1: 0x000000018363b344 libsystem_pthread.dylib`_pthread_wqthread + 1452
    frame #2: 0x000000018363ad8c libsystem_pthread.dylib`start_wqthread + 4

  thread #7: tid = 0xe5cb, 0x0000000183577314 libsystem_kernel.dylib`__semwait_signal + 8, name = 'gputools.smt_poll.0x1700343e0'
    frame #0: 0x0000000183577314 libsystem_kernel.dylib`__semwait_signal + 8
    frame #1: 0x000000018349525c libsystem_c.dylib`nanosleep + 212
    frame #2: 0x000000018349517c libsystem_c.dylib`usleep + 64
    frame #3: 0x000000010240de14 GPUToolsCore`smt_poll_thread_entry(void*) + 136
    frame #4: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #5: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #6: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #8: tid = 0xe5d6, 0x0000000183577314 libsystem_kernel.dylib`__semwait_signal + 8, name = 'gputools.smt_poll.0x170034b80'
    frame #0: 0x0000000183577314 libsystem_kernel.dylib`__semwait_signal + 8
    frame #1: 0x000000018349525c libsystem_c.dylib`nanosleep + 212
    frame #2: 0x000000018349517c libsystem_c.dylib`usleep + 64
    frame #3: 0x000000010240de14 GPUToolsCore`smt_poll_thread_entry(void*) + 136
    frame #4: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #5: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #6: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #9: tid = 0xe5dd, 0x0000000183559188 libsystem_kernel.dylib`mach_msg_trap + 8, name = 'com.apple.uikit.eventfetch-thread'
    frame #0: 0x0000000183559188 libsystem_kernel.dylib`mach_msg_trap + 8
    frame #1: 0x0000000183558ff8 libsystem_kernel.dylib`mach_msg + 72
    frame #2: 0x00000001845565d0 CoreFoundation`__CFRunLoopServiceMachPort + 192
    frame #3: 0x00000001845541ec CoreFoundation`__CFRunLoopRun + 1132
    frame #4: 0x00000001844822b8 CoreFoundation`CFRunLoopRunSpecific + 444
    frame #5: 0x0000000184fbf26c Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 304
    frame #6: 0x0000000184fdfdd0 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 96
    frame #7: 0x000000018ae3dc38 UIKit`-[UIEventFetcher threadMain] + 136
    frame #8: 0x00000001850bce68 Foundation`__NSThread__start__ + 1024
    frame #9: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #10: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #11: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #11: tid = 0xe5e5, 0x0000000183577a88 libsystem_kernel.dylib`__workq_kernreturn + 8
    frame #0: 0x0000000183577a88 libsystem_kernel.dylib`__workq_kernreturn + 8
    frame #1: 0x000000018363b344 libsystem_pthread.dylib`_pthread_wqthread + 1452
    frame #2: 0x000000018363ad8c libsystem_pthread.dylib`start_wqthread + 4

  thread #13: tid = 0xe5e7, 0x000000018363ad88 libsystem_pthread.dylib`start_wqthread
    frame #0: 0x000000018363ad88 libsystem_pthread.dylib`start_wqthread

  thread #14: tid = 0xe5e8, 0x0000000183559188 libsystem_kernel.dylib`mach_msg_trap + 8, name = 'com.apple.NSURLConnectionLoader'
    frame #0: 0x0000000183559188 libsystem_kernel.dylib`mach_msg_trap + 8
    frame #1: 0x0000000183558ff8 libsystem_kernel.dylib`mach_msg + 72
    frame #2: 0x00000001845565d0 CoreFoundation`__CFRunLoopServiceMachPort + 192
    frame #3: 0x00000001845541ec CoreFoundation`__CFRunLoopRun + 1132
    frame #4: 0x00000001844822b8 CoreFoundation`CFRunLoopRunSpecific + 444
    frame #5: 0x0000000184c87a70 CFNetwork`+[NSURLConnection(Loader) _resourceLoadLoop:] + 336
    frame #6: 0x00000001850bce68 Foundation`__NSThread__start__ + 1024
    frame #7: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #8: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #9: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #15: tid = 0xe5ed, 0x0000000183576e1c libsystem_kernel.dylib`__psynch_cvwait + 8
    frame #0: 0x0000000183576e1c libsystem_kernel.dylib`__psynch_cvwait + 8
    frame #1: 0x000000018363c9c0 libsystem_pthread.dylib`_pthread_cond_wait + 640
    frame #2: 0x0000000182f653ec libc++.1.dylib`std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&) + 56
    frame #3: 0x0000000188d865d0 JavaScriptCore`void std::__1::condition_variable_any::wait<std::__1::unique_lock<bmalloc::Mutex> >(std::__1::unique_lock<bmalloc::Mutex>&) + 112
    frame #4: 0x0000000188d86544 JavaScriptCore`bmalloc::AsyncTask<bmalloc::Heap, void (bmalloc::Heap::*)()>::threadRunLoop() + 168
    frame #5: 0x0000000188d86424 JavaScriptCore`bmalloc::AsyncTask<bmalloc::Heap, void (bmalloc::Heap::*)()>::threadEntryPoint(bmalloc::AsyncTask<bmalloc::Heap, void (bmalloc::Heap::*)()>*) + 12
    frame #6: 0x0000000188d866d4 JavaScriptCore`void* std::__1::__thread_proxy<std::__1::tuple<void (*)(bmalloc::AsyncTask<bmalloc::Heap, void (bmalloc::Heap::*)()>*), bmalloc::AsyncTask<bmalloc::Heap, void (bmalloc::Heap::*)()>*> >(void*) + 92
    frame #7: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #8: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #9: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #16: tid = 0xe5ee, 0x0000000183559188 libsystem_kernel.dylib`mach_msg_trap + 8, name = 'com.squareup.SocketRocket.NetworkThread'
    frame #0: 0x0000000183559188 libsystem_kernel.dylib`mach_msg_trap + 8
    frame #1: 0x0000000183558ff8 libsystem_kernel.dylib`mach_msg + 72
    frame #2: 0x00000001845565d0 CoreFoundation`__CFRunLoopServiceMachPort + 192
    frame #3: 0x00000001845541ec CoreFoundation`__CFRunLoopRun + 1132
    frame #4: 0x00000001844822b8 CoreFoundation`CFRunLoopRunSpecific + 444
    frame #5: 0x0000000184fbf26c Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 304
    frame #6: 0x000000010019dbe8 CPQK`-[_FSRRunLoopThread main](self=<unavailable>, _cmd=<unavailable>) + 252 at FSRWebSocket.m:1840 [opt]
    frame #7: 0x00000001850bce68 Foundation`__NSThread__start__ + 1024
    frame #8: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #9: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #10: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #17: tid = 0xe604, 0x000000018357723c libsystem_kernel.dylib`__select + 8, name = 'com.apple.CFSocket.private'
    frame #0: 0x000000018357723c libsystem_kernel.dylib`__select + 8
    frame #1: 0x000000018455d468 CoreFoundation`__CFSocketManager + 640
    frame #2: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #3: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #4: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #18: tid = 0xe608, 0x0000000183576e1c libsystem_kernel.dylib`__psynch_cvwait + 8, name = 'JIT Worklist Worker Thread'
    frame #0: 0x0000000183576e1c libsystem_kernel.dylib`__psynch_cvwait + 8
    frame #1: 0x000000018363c9c0 libsystem_pthread.dylib`_pthread_cond_wait + 640
    frame #2: 0x0000000182f653ec libc++.1.dylib`std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&) + 56
    frame #3: 0x0000000188d6ed64 JavaScriptCore`WTF::ParkingLot::parkConditionallyImpl(void const*, WTF::ScopedLambda<bool ()> const&, WTF::ScopedLambda<void ()> const&, std::__1::chrono::time_point<std::__1::chrono::steady_clock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l> > >) + 2132
    frame #4: 0x0000000188b46a5c JavaScriptCore`JSC::JITWorklist::runThread() + 192
    frame #5: 0x0000000188b46eac JavaScriptCore`std::__1::__function::__func<JSC::JITWorklist::JITWorklist()::$_0, std::__1::allocator<JSC::JITWorklist::JITWorklist()::$_0>, void ()>::operator()() + 16
    frame #6: 0x00000001884b700c JavaScriptCore`WTF::threadEntryPoint(void*) + 212
    frame #7: 0x00000001884b6f1c JavaScriptCore`WTF::wtfThreadEntryPoint(void*) + 24
    frame #8: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #9: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #10: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

  thread #19: tid = 0xe609, 0x0000000183576e1c libsystem_kernel.dylib`__psynch_cvwait + 8, name = 'WTF Parallel Helper Thread'
    frame #0: 0x0000000183576e1c libsystem_kernel.dylib`__psynch_cvwait + 8
    frame #1: 0x000000018363c9c0 libsystem_pthread.dylib`_pthread_cond_wait + 640
    frame #2: 0x0000000182f653ec libc++.1.dylib`std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&) + 56
    frame #3: 0x0000000188d6ed64 JavaScriptCore`WTF::ParkingLot::parkConditionallyImpl(void const*, WTF::ScopedLambda<bool ()> const&, WTF::ScopedLambda<void ()> const&, std::__1::chrono::time_point<std::__1::chrono::steady_clock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l> > >) + 2132
    frame #4: 0x0000000188d6e2e8 JavaScriptCore`WTF::ParallelHelperPool::waitForClientWithTask(WTF::Locker<WTF::LockBase> const&) + 288
    frame #5: 0x0000000188d6e088 JavaScriptCore`WTF::ParallelHelperPool::helperThreadBody() + 76
    frame #6: 0x00000001884b700c JavaScriptCore`WTF::threadEntryPoint(void*) + 212
    frame #7: 0x00000001884b6f1c JavaScriptCore`WTF::wtfThreadEntryPoint(void*) + 24
    frame #8: 0x000000018363d850 libsystem_pthread.dylib`_pthread_body + 240
    frame #9: 0x000000018363d760 libsystem_pthread.dylib`_pthread_start + 284
    frame #10: 0x000000018363ad94 libsystem_pthread.dylib`thread_start + 4

@swftvsn hey, if you got yours to work, and you know what my issue is, please chime in.

I appreciate your help!

@morganchen12
Copy link
Contributor

This looks like an app-specific timing/shared state issue. Can you post more of your code?

@ghost
Copy link
Author

ghost commented Feb 7, 2017

@morganchen12 I've created a gist with the two files that are involved in the crash.

BrowseVC & FirebaseDB gist

The BrowseVC is where users toggle between online and offline users through queries, and the FirebaseDB file is where the queries are generated with singleton.

Thanks for looking into it, Morgan! :)

@morganchen12
Copy link
Contributor

Hm, you may need to call reloadData() when you swap out the data source for a new one. That way the collection view isn't confused, for example, if you change from a data source that has 12 elements to one that has 7--the data source sends child added events to populate the initial data, but the collection view it looks like children were removed.

If your collection views are massively different, I'd suggest keeping two separate views in a tab bar controller (or other similar controller), as this may help avoid stale state bugs.

Let me know if you still run into crashes.

@ghost
Copy link
Author

ghost commented Feb 7, 2017

Thank you for your input, @morganchen12. I was thinking that the reloading of data was still free, but I guess it's not after I null the data source's associated view.

I'll work on the code some and report back. If it continues acting funky, I'll also take your suggestion on keeping two views. That was something I had in mind, but thought the memory and data would've been wasteful. Better than crashes by all measures though!

@swftvsn
Copy link

swftvsn commented Feb 7, 2017

Hi,

I actually couldn't get it to work correctly, the problem is just more hidden.

Isn't there always a chance for an error because if we take a look at this pattern:

self.dataSource = FUITableViewDataSource(collection: sortedArray, view: table) { tableView, indexPath, snapshot in
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCell
    ...
    return cell
}
table.dataSource = self.dataSource
table.reloadData()

We can see that the sortedArray will start observing in the constructor of the FUITableViewDataSource (actually deep in FUIDataSource, initWithCollection) and then it will race with the table.reloadData() call to do various UITableView insert etc. operations?

So what we actually would need to do, is to delay the [_collection observeQuery]; call after the table.reloadData() call. Then the table would be first emptied of old cells using the (at the time) empty datasource, and then it would resume normal operations and "push" the rows using the FUI* family of methods.

What do you think @morganchen12

Using FUIArraythis works 99% of the time, but when I switch to FUISortedArray, it always fails with

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (48) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (24 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

where the numbers work out so that the "after update" count is always double the number inserted.

@morganchen12
Copy link
Contributor

morganchen12 commented Feb 7, 2017

Yeah, imo this is an issue in the design of our data source class. We should be separating initialization from the pushing of events, since it's surprising (and bad) that initializing an object should have side effects.

I'm thinking something like

/** initializes the data source, doesn't do anything */
public init(collection: FUICollection)

/** attaches the data source to the view and starts sending updates */
public func bind(toView: UICollectionView) -> Void

/** unbinds the data source from the view */
public func unbind() -> Void

Unfortunately, this would be a breaking API change.

@swftvsn
Copy link

swftvsn commented Feb 9, 2017

It sounds exactly right to remove the side effects. Do you have any estimate when this could be merged and released? We actually have an app waiting to go to approval, but this is the last major issue that prevents us from going forward.

@morganchen12
Copy link
Contributor

There's nothing blocking a new release, but I'd like to aggregate any breaking API changes we'd want now so I don't end up bumping the major version every 2 weeks.

Any suggestions?

@swftvsn
Copy link

swftvsn commented Feb 10, 2017

Not really, the only thing we'd like to see added is #230, but I don't think this release should wait any new features as the issue may be affecting multitude of apps resulting in hard to trace (seemingly) random bugs.

So I vote for quick releases now and in future too.

@ghost
Copy link
Author

ghost commented Feb 10, 2017

I appreciate Firebase and the developers who are in charge of it. The bug I have created a ticket for, would be great to have the Firebase team help me and the community by solving it.

But, @swftvsn, you do know that you're pretty much demanding iOS Firebase to be updated to solve a problem that you are dealing with on an app that no one yet uses, to break the code bases on the entire iOS Firebase community, right? Are you even a paying Firebase customer? 🤣

The bugs we're dealing with, not all that many developers are dealing with them (vocally and up in arms), so I think your reasoning for quick releases is bull$hit.

@swftvsn
Copy link

swftvsn commented Feb 10, 2017

@datureezy I too am thankfull for the great product and associated code that we can use to speed up the development of our apps.

I'm not demanding anything, sorry if the text came out too harsh, just gave my wish how to proceed, of course from my point of view.

I'm strong proponent for quick and numerous releases. It's way better than the old once a year thing that some companies seem to do. After all devs can and should limit / nail down the version in their pod / what not file and update when they want to. We're in control of that. But we can't update if the fixes are not released. We're not in control of that.

And it's not like this is the main firebase release, but the ios accompanying components that we're talking about.

And yes, we're happy to pay the price they ask for the service.

Back to this issue though - @morganchen12 could we get some kind of time frame / educated guess for this release? Are we talking about weeks or months? If it's going to take long then I probably should fork your branch and build it from there?

@morganchen12
Copy link
Contributor

morganchen12 commented Feb 11, 2017

No worries, this release has been cut. I appreciate all your concerns!

https://github.com/firebase/FirebaseUI-iOS/releases/tag/v3.0.0

@swftvsn
Copy link

swftvsn commented Feb 12, 2017

Awesome! Thank you!

@ghost
Copy link
Author

ghost commented Feb 17, 2017

Hey @morganchen12,
Thanks for the 3.0.0 release. I have implemented in the bind and unbind functions and they've gotten me to an acceptable state with the functionality I'm looking for. My app still crashes when the query changes fast (really fast), but really, I can't imagine my users frantically toggling back and forth like maniacs for any useful purpose. To block their ability to do any craziness, I've just disabled my segmented control which resets queries for 750 milliseconds after firing, and I haven't been able to crash once. Thank you for your help, BIG TIME!

@swftvsn
Copy link

swftvsn commented Feb 17, 2017

Hi,

The way our app is designed we need to change the data source during infinite left / right swipe scenario (Changing the month user is observing), so we can't disable the user controls. (Which is extremely bad anyway from UX standpoint.) Also disabling for a moment is bad because the time frame when the error will manifest itself will grow when you have more data or slower devices.

I hope this helps someone in the future, I ended up doing this, and don't get the crashes anymore (so far..., fingers crossed)

//Unbind the old datasource from the tableview if there is already one
self.dataSource?.unbind()

//Set empty datasource to the tableview
self.tableView.dataSource = EmptyDataSource()

//Instruct tableview to reload.
//This causes it to remove any rows there might be lingering around.
self.tableView.reloadData() 

// Assign new datasource to self.dataSource here. Do not start to observe yet.

//This will replace the empty datasource we just set and
//start observing and updating the empty tableView
self.dataSource?.bind(to: self.tableView) 

//Ready. Do not reload tableView past this point, datasource will push changes to it!

One future improvement point would probably be for the data source to clean after itself to reset the table to empty state, but that's not very important as we can do it ourselves.

@ghost
Copy link
Author

ghost commented Feb 18, 2017

@swftvsn, are you populating your cells in the closure after you initialize your dataSource? I'm running my code just like yours, step by step (but just for collection views), and my app still crashes with:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for index path for global index 9223372036854775807 when there are only 0 items in the collection view'

With your queries being even more varied than mine (offline & online), I'm not sure how yours is not crashing, and mine crashes just after 5-6 new queried calls.

My Empty DataSource
class EmptyCollectionViewDataSource: NSObject, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return UICollectionViewCell() } }

@swftvsn
Copy link

swftvsn commented Feb 20, 2017

Hmm, that's really odd, or then some codepath in UICollectionDataViewSource wasn't updated vs. the tableview one.

That said, I do as little as possible in the closure (I only extract some ints and strings from the FIRDataSnapshot and set those to my own data transfer object and set it to the cell), and nothing that would change the UI.

The real work is done in

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 

method that sets the label texts and images, starts the animations etc.

@ghost
Copy link
Author

ghost commented Feb 20, 2017

Thanks for your input, @swftvsn. Perhaps me pulling out 3-4 values per row in that closure is causing the slow-up followed by a crash. I'll try extracting only a lookup key and do some willDisplay type method for collectionView cells and report back. 👍

@SebastianBO
Copy link

Extremely good thread, thanks!

@felixfrtz
Copy link

I also getting the "NSInternalInconsistencyException", but I have 2 Views, each has a tableview and gets populated via self.dataSource = self.tableView.bind(to: getQuery()) { tableView, indexPath, snap in ....

Does this also apply for me? I can't figure out how to fix this Exception.

@morganchen12
Copy link
Contributor

@Warhost can you file a new issue with full stack trace and other info?

@felixfrtz
Copy link

@morganchen12 Solved. Updated my code to the lates sample practices, does the Job.

@lucaventura
Copy link

lucaventura commented Jun 30, 2017

@datureezy Did you ever solve your problem? I am facing the exact error, after changing data sources 4-5 times I will get the internal inconsistency crash. Before binding to a new ref I do the steps listed above (unbind, clear data source, etc). I'm also using a CollectionView and not a TableView.

@felixfrtz
Copy link

@adornoventura Same thing here.

@lucaventura
Copy link

lucaventura commented Jun 30, 2017

@Warhost It seems like no matter what I do it will always crash after loading a new data source 4-5 times. On the contrary, if I perform the button clicks that change the data source very slowly I can maybe get 10 data source changes before it fails.

@felixfrtz
Copy link

@adornoventura Could you post the code to what you are describing?
I have the exact same problem I think

@lucaventura
Copy link

lucaventura commented Jun 30, 2017

@Warhost I have one CollectionView that has to switch between 4 different data sets based on two segmented controls (one "outside" control and one "inside" control). I have one method that swaps the data source and I pass it the string representation of the child node from where the data should be loaded. The method is called from various places, most notably the segmentValueChanged callback from one of the segment controls. In the if collectionViewDataSource != nil block I've tried a different combinations of things, none of which solves the problem (such as setting an empty data source as mentioned in previous posts). I didn't include my code from the closure as I tried running with nothing in the closure (just returning an empty cell) and it does nothing in regards to the problem.

func loadBooksWith(stringRef: String) {
        if collectionViewDataSource != nil {
            self.collectionViewDataSource.unbind()
            self.collectionViewDataSource.collectionView = nil
            self.collectionView.reloadData()
        }
            if let user = Auth.auth().currentUser {
                
                let ref = Database.database().reference().child("users").child(user.uid).child(stringRef)
                
                self.collectionViewDataSource = self.collectionView?.bind(to: ref) { collectionView, indexPath, outerSnap in
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "User_Book_Cell", for: indexPath) as! UserBookCollectionViewCell

@felixfrtz
Copy link

@adornoventura Okay this looks sort of similar. I am trying to follow the database example of the firebase quickstart repo, but also face these Exception issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants