vtourraine / AcknowList

Acknowledgements screen displaying a list of licenses, for example from CocoaPods and Swift Package Manager dependencies.
MIT License
807 stars 60 forks source link

Assertion failure in iOS 15 #89

Closed jminutaglio closed 3 years ago

jminutaglio commented 3 years ago

Using:

let viewController = AcknowListViewController()
navigationController?.pushViewController(viewController, animated: true)

On Xcode 13 RC (Version 13.0 (13A233)) ->

On Simulator iOS 14.5 -> expected behavior (AcknowList viewController shows)

On Simulator iOS 15.0:

2021-09-15 00:12:36.534344-0400 <project>16143:173446] *** Assertion failure in UITraitCollection *UIViewControllerMissingInitialTraitCollection(UIViewController *__strong)(), UIViewController.m:2434
2021-09-15 00:12:36.559665-0400 <project>[16143:173446] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UIViewController is missing its initial trait collection populated during initialization. This is a serious bug, likely caused by accessing properties or methods on the view controller before calling a UIViewController initializer. View controller: <UITableViewController: 0x7fe893379490>'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000011fbdebb4 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x0000000119b76be7 objc_exception_throw + 48
    2   Foundation                          0x0000000114a59da9 -[NSMutableDictionary(NSMutableDictionary) classForCoder] + 0
    3   UIKitCore                           0x00000001347fb6c6 UIViewControllerMissingInitialTraitCollection + 188
    4   UIKitCore                           0x00000001347ffbef -[UIViewController traitCollection] + 155
    5   UIKitCore                           0x00000001347ee93a -[UITableViewController dealloc] + 196
    6   AcknowList                          0x000000010f532a7e $s10AcknowList0aB14ViewControllerC9plistPath5styleACSS_So07UITableC5StyleVtcfc + 206
    7   AcknowList                          0x000000010f532b0b $s10AcknowList0aB14ViewControllerC9plistPath5styleACSS_So07UITableC5StyleVtcfcTo + 59
    8   AcknowList                          0x000000010f532615 $s10AcknowList0aB14ViewControllerCACycfc + 325
    9   AcknowList                          0x000000010f5326bf $s10AcknowList0aB14ViewControllerCACycfcTo + 15
    10  AcknowList                          0x000000010f5324cb $s10AcknowList0aB14ViewControllerCACycfC + 27
    11  <project>                          0x000000010911ad79 $s9<project>19ViewControllerLegalC13ackListActionyyypF + 153
    12  <project>                           0x000000010911ceb1 $s9<project>19ViewControllerLegalC13ackListActionyyypFTo + 65
    13  UIKitCore                           0x0000000134fc2189 -[UIApplication sendAction:to:from:forEvent:] + 83
    14  UIKitCore                           0x000000013485b573 -[UIControl sendAction:to:forEvent:] + 110
    15  UIKitCore                           0x000000013485b955 -[UIControl _sendActionsForEvents:withEvent:] + 332
    16  UIKitCore                           0x0000000134857e8c -[UIButton _sendActionsForEvents:withEvent:] + 148
    17  UIKitCore                           0x000000013485a206 -[UIControl touchesEnded:withEvent:] + 488
    18  UIKitCore                           0x000000013500295d -[UIWindow _sendTouchesForEvent:] + 1287
    19  UIKitCore                           0x00000001350049df -[UIWindow sendEvent:] + 5295
    20  UIKitCore                           0x0000000134fdb4e8 -[UIApplication sendEvent:] + 825
    21  UIKitCore                           0x000000013507128a __dispatchPreprocessedEventFromEventQueue + 8695
    22  UIKitCore                           0x0000000135073a10 __processEventQueue + 8579
    23  UIKitCore                           0x000000013506a1b6 __eventFetcherSourceCallback + 240
    24  CoreFoundation                      0x000000011fb4ce25 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    25  CoreFoundation                      0x000000011fb4cd1d __CFRunLoopDoSource0 + 180
    26  CoreFoundation                      0x000000011fb4c1f2 __CFRunLoopDoSources0 + 242
    27  CoreFoundation                      0x000000011fb46951 __CFRunLoopRun + 875
    28  CoreFoundation                      0x000000011fb46103 CFRunLoopRunSpecific + 567
    29  GraphicsServices                    0x000000012131fcd3 GSEventRunModal + 139
    30  UIKitCore                           0x0000000134fbbe63 -[UIApplication _run] + 928
    31  UIKitCore                           0x0000000134fc0a53 UIApplicationMain + 101
    32  <project>                           0x000000010908a14f main + 63
    33  dyld                                0x000000010ea60e1e start_sim + 10
    34  ???                                 0x0000000000000001 0x0 + 1
    35  ???                                 0x0000000000000002 0x0 + 2

Same codeline, just an error on iOS 15...

Any ideas? ty in advance. AcknowList is awesome btw!

psalzAppDev commented 3 years ago

It is probably a bug that will be fixed in later Xcode versions - some problems with the initializers not visible to objc. However, for now, I got it resolved using this: https://stackoverflow.com/questions/69092599/xcode-13-beta-5-error-uiviewcontroller-is-missing-its-initial-trait-collection

I will try to put this into a PR

psalzAppDev commented 3 years ago

Ok, so I tried my solution, but it turned out that your last convenience initializer that finally calls super.init(style:) uses an array of Acknow, which is a struct. Therefore, this initializer cannot be exposed to @objc.

The solution would be to expose all convenience initializers to @objc and then overriding init(style:), like this:

/**
Bugfix for [Issue #89](https://github.com/vtourraine/AcknowList/issues/89).

On iOS 15 using Xcode 13 RC, the initializer will crash unless this one is overridden.

Solution taken from [StackOverflow](https://stackoverflow.com/questions/69092599/xcode-13-beta-5-error-uiviewcontroller-is-missing-its-initial-trait-collection)
*/
public override init(style: UITableView.Style) {

   self.acknowledgements = []

   super.init(style: style)
}

And then calling self.init(style: style) in the convenience initializer instead of super.init(style: style):

@objc public convenience init(acknowledgements: [Acknow], style: UITableView.Style = .grouped) {

   self.init(style: style)

   self.acknowledgements = acknowledgements

   self.title = AcknowLocalization.localizedTitle()
}

It seems to be your only solution for now is to remove the above convenience initializer, which means acknowledgements have to be set after initialization. Or hope that Apple fixes this annoying bug anytime soon.

Something really hacky might work too: Duplicate Acknow as a class inheriting from NSObject, thus exposing it to @objc. Then replace the struct Acknow array in the initializer with the class Acknow array and create a struct Acknow array by copying the values.

Something like this:

@objc public class AcknowC: NSObject {
   // Duplicate everything as in struct Acknow
}

...

@objc public convenience init(acknowledgements: [AcknowC], style: UITableView.Style = .grouped) {

   self.init(style: style)

   self.acknowledgements = acknowledgements.map {
      Acknow(title: $0.title, string: $0.string, license: $0.license)
   }

   self.title = AcknowLocalization.localizedTitle()
}
psalzAppDev commented 3 years ago

So this actually works. I'll create a PR for you to test and then you can decide whether you want this hacky and breaking change or wait until Apple fixes this bug (hopefully...)

jminutaglio commented 3 years ago

@psalzAppDev - used your code/fork and confirmed it works here also. TY so much! iOS 15 is coming bugs or not! :)

vtourraine commented 3 years ago

Thank you so much for reporting this issue, and for preparing the pull request!

I’m not sure we should merge it as-is, though. Is it really just a bug, or just how the latest Swift compiler works? I would love to better understand the issue before touching the initializers interface. Any indication about what is going on exactly?

But in the meantime, I’m glad we have your workaround for folks who would need it.

psalzAppDev commented 3 years ago

Thank you so much for reporting this issue, and for preparing the pull request!

I’m not sure we should merge it as-is, though. Is it really just a bug, or just how the latest Swift compiler works? I would love to better understand the issue before touching the initializers interface. Any indication about what is going on exactly?

But in the meantime, I’m glad we have your workaround for folks who would need it.

I agree, this is a messy fix and I hope that this is an internal bug that will soon be fixed. There is not much information around about it though. I can't believe that this behavior is intended.

On the downside, anyone who uses your framework will notice that the app crashes on iOS 15. So hopefully they find their way to this thread and can decide whether to use my PR for now to keep their app working. That's what I did when I noticed that my app crashed on my iOS15 test device.

When new Xcode versions arrive, I will test for this bug and report back to you.

jminutaglio commented 3 years ago

I’m not sure we should merge it as-is, though. Is it really just a bug, or just how the latest Swift compiler works? I would love to better understand the issue before touching the initializers interface. Any indication about what is going on exactly?

My $0.02 - this will be the shipping iOS 15 and the impact is that AcknowList will fail on open every time - so the impact to AcknowList is as major as it gets, starting next week when iOS 15 drops. I'd implement the workaround unless & until Apple's build allows the current initializer code to function without failure. Considering the length of the iOS 15 beta I'd suggest it won't be anytime soon... :/

vtourraine commented 3 years ago

@jminutaglio Agreed, we should address that as soon as possible.

I have found a different solution, though. The problem somehow comes from the interoperability between Swift and Objective-C code. If we remove the @objc annotations from the initializers, there’s no more crash. I would also mean that the library would no longer be usable from Objective-C code. That’s bad, but I think I would rather do that, instead of adding all the extra code to dance around the issue.

What do you think?

jminutaglio commented 3 years ago

@vtourraine - makes sense to me. I'd guess most projects have moved from Obj-C to Swift at this point, so the impact to folks using AcknowList may be minor/none...

jminutaglio commented 3 years ago

@vtourraine - AcknowList 2.0.2 is still throwing the assertion failure on iOS 15 - FYI.

:(

vtourraine commented 3 years ago

Hum, it gets weirder indeed. The crash is fixed for the “AcknowExampleManual” project, but not for the “AcknowExampleCocoaPods”. 😞 Looking into it...

vtourraine commented 3 years ago

Alright, I reimplemented the AcknowListViewController default initializer, and it seems to really fix this issue. I’ve released it as version 2.0.3. Please check it out, and let me know if that works for you.

jminutaglio commented 3 years ago

@vtourraine - looks good here. Confirmed fixed!

TY!

vtourraine commented 3 years ago

@jminutaglio Glad to hear it, thanks for the feedback 👍