0

I just upgraded to Xcode 9.0 Beta and now my app crashes on launch.

2017-06-09 14:35:18.817213-0700 recharge-consumer-ios[13524:1720597] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[recharge_consumer_ios.SignedOutContainerViewController<0x105c691b8> init]: cannot init a class object.'
*** First throw call stack:
(
0   CoreFoundation                      0x000000010caf4f6b __exceptionPreprocess + 171
1   libobjc.A.dylib                     0x000000010ba3e121 objc_exception_throw + 48
2   CoreFoundation                      0x000000010cb7f6ff +[NSObject(NSObject) init] + 127
3   recharge-consumer-ios               0x0000000105322223 _T021recharge_consumer_ios22RechargeViewControllerCACycfCTD + 19

The call that is failing is (simplified):

func setAndRefreshChildViewControllersWithTypes(_ types: [MyViewControllerProtocol.Type]) {
    for type in types {
        let controller = type.init() as! UIViewController // This is what crashes
    }
    ...
}

Where MyViewControllerProtocol is:

protocol MyViewControllerProtocol {
    // some other fields here too
    init()
}

And the instance that is failing looks like:

class SignedOutContainerViewController: MyViewController {

    required init() {
        super.init(nibName: "SignedOutContainerViewController")
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

where MyViewController is:

class MyViewController: UIViewController, MyViewControllerProtocol {
    required init() {
        fatalError("init has not been implemented")
    }

    init(nibName nibNameOrNil: String?) {
        super.init(nibName: nibNameOrNil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Any idea what's going wrong, or pointers on where else to look? Xcode 9.0 Beta (9M136h), swift 3.

Eric
  • 5,323
  • 6
  • 30
  • 35
  • No repro, show us how you declared the conforming class and how you called the method. – Kevin Jun 09 '17 at 22:46
  • @Kevin updated with the full hierarchy. the call is the "type.init()" in the first code block. – Eric Jun 09 '17 at 23:10
  • If you use `MyViewController.Type` instead, does it still crashes? – Paulo Mattos Jun 10 '17 at 00:32
  • @PauloMattos nope, but the idea is that I'm controlling the flow of my app by calling setAndRefreshChildViewControllersWithTypes with various arrays of ViewControllers that implement MyViewControllerProtocol. So only taking MyViewController defeats the purpose. – Eric Jun 10 '17 at 09:49
  • 1
    @Eric ...yeah, I get that ;) My hope was that *all* your view controllers were `MyViewController` subclasses as well, just like the `SignedOutContainerViewController` in your example, so using the superclass *or* protocol metatype wouldn't matter much. – Paulo Mattos Jun 10 '17 at 11:08
  • Are you not using @objc – VIP-DEV Jun 10 '17 at 14:40
  • @VIP-DEV I have no idea why, but that works. If you want to post this as an answer I'll accept it. Thanks so much! – Eric Jun 11 '17 at 20:11
  • The reason is while migrating to Swift 4 there is something called minimize inference Swift 4 which tells to add an @objc attribute to your code only where it is needed based on static inference which helps in reducing overall code size. – VIP-DEV Jun 18 '17 at 04:04

2 Answers2

1

I wanted to follow up because the accepted answer didn't work for me. All of this was tested in a playground using the swift 4.0 development snapshot toolchain for Xcode from 6/12/17.

This will fail with a runtime exception

import Foundation

public protocol MyProtocol {
    init()
}

@objc // This doesn't work
public class MyClass: NSObject, MyProtocol {

    @objc // This doesn't work either
    required public override init() {
        super.init()
    }
}

let anonymousType: AnyClass = MyClass.self

let metaType: MyProtocol.Type = anonymousType as! MyProtocol.Type
let instance = metaType.init()

For me, it wasn't as simple as marking a class or method with @objc. I found 2 solutions that could work for me.

  1. Mark the class final

    import Foundation
    
    public protocol MyProtocol {
        init()
    }
    
    final public class MyClass: NSObject, MyProtocol {
    
        required public override init() {
            super.init()
        }
    }
    
    let anonymousType: AnyClass = MyClass.self
    
    let metaType: MyProtocol.Type = anonymousType as! MyProtocol.Type
    let instance = metaType.init()
    
  2. Mark the protocol @objc

    import Foundation
    
    @objc
    public protocol MyProtocol {
        init()
    }
    
    public class MyClass: NSObject, MyProtocol {
    
        required public override init() {
            super.init()
        }
    }
    
    let anonymousType: AnyClass = MyClass.self
    
    let metaType: MyProtocol.Type = anonymousType as! MyProtocol.Type
    let instance = metaType.init()
    
allenh
  • 6,582
  • 2
  • 24
  • 40
0

Use @objc for the method which is crashing

VIP-DEV
  • 221
  • 1
  • 6