来自Objective-C类的非指定初始化器继承

来自Objective-C类的非指定初始化器继承,objective-c,swift,inheritance,designated-initializer,Objective C,Swift,Inheritance,Designated Initializer,在对UIKit类进行子分类并向类中添加不可变变量时遇到了问题,因此我制定了一个测试项目来了解发生了什么 我的结论是,如果: 我们有一个Objective-C类,它继承自另一个类,具有自己指定的初始化器(隐式或显式注释) 在其初始化器中,它调用[self initWithXX],其中initWithXX是超类上的init方法 我们在Swift中对这个类进行了子类化,添加了一个不可变属性(显然必须在实例化时初始化) 我们为这个Swift类实现了一个指定的初始化器,它设置了不可变属性,然后调用父类指

在对
UIKit
类进行子分类并向类中添加不可变变量时遇到了问题,因此我制定了一个测试项目来了解发生了什么

我的结论是,如果:

  • 我们有一个Objective-C类,它继承自另一个类,具有自己指定的初始化器(隐式或显式注释)
  • 在其初始化器中,它调用
    [self initWithXX]
    ,其中
    initWithXX
    是超类上的
    init
    方法
  • 我们在Swift中对这个类进行了子类化,添加了一个不可变属性(显然必须在实例化时初始化)
  • 我们为这个Swift类实现了一个指定的初始化器,它设置了不可变属性,然后调用父类指定的初始化器
然后,这将导致运行时异常,因为Swift类在调用Objective-C超类的指定初始化器时,将尝试在
self
上调用
initWithXX
,并且此方法未从超类继承,因为我们实现了指定的初始化器

此测试的代码为:

View.h

#import <UIKit/UIKit.h>

@interface View : UIView

-(instancetype)initWithIdentifier:(NSString *)identifier;

@end
SwiftView.swift

异常生成代码

let view:SwiftView=SwiftView(名称:“测试”)
例外情况

致命错误:对类“Test.SwiftView”使用未实现的初始值设定项“init(frame:)”

我这样做的动机是对标准库类(
UITableViewController
MKAnnotationView
…)进行子分类,并添加不可变属性以包含对它们的注入依赖项,然后需要在实例化子类时通过将它们传递到自定义指定的
init
方法来初始化这些属性,并发现这导致了上述意外异常

在做了这个实验来确定原因之后,我想我理解了为什么会发生这种情况(因为父Objective-C类指定的初始化器正在委托给它自己的另一个指定的初始化器,而不是调用它的超类的初始化器)。显然,要解决这个问题,标准库应该改变它们的初始化器方法来调用超类初始化器,而不是
self
initialiser,但是我可以理解,调用
self
而不是
super
可能是有效的功能,以便允许子类重写默认行为,同时保留特定的初始值设定项。此外,我认为这样的改变可能会导致很多问题,因为许多应用程序可能已经建立在这个功能上,这将破坏它。我想这里的基本问题是Objective-C和Swift之间的语言特性不兼容

除了使这些不可变变量可变并在第二个初始化阶段进行设置的(非常不受欢迎的)方法之外,有人能找到解决这个问题的方法吗(无论是苹果修复还是代码解决方案)

编辑

以下是我的测试项目中的堆栈跟踪:

#0  0x000000010b0c9f78 in Test.SwiftView.init (Test.SwiftView.Type)(frame : C.CGRect) -> Test.SwiftView at /Users/xxx/Documents/Test/Test/SwiftView.swift:12
#1  0x000000010b0c9fa0 in @objc Test.SwiftView.init (Test.SwiftView.Type)(frame : C.CGRect) -> Test.SwiftView ()
#2  0x000000010b0c37bb in -[View initWithIdentifier:] at /Users/xxx/Documents/Test/Test/View.m:14
#3  0x000000010b0c97a6 in Test.SwiftView.init (Test.SwiftView.Type)(name : Swift.String) -> Test.SwiftView at /Users/xxx/Documents/Test/Test/SwiftView.swift:18
#4  0x000000010b0c9914 in Test.SwiftView.__allocating_init (Test.SwiftView.Type)(name : Swift.String) -> Test.SwiftView ()
#5  0x000000010b0c3b0e in Test.ViewController.viewDidLoad (Test.ViewController)() -> () at /Users/xxx/Documents/Test/Test/ViewController.swift:18
#6  0x000000010b0c3be2 in @objc Test.ViewController.viewDidLoad (Test.ViewController)() -> () ()
#7  0x000000010bbcb1d0 in -[UIViewController loadViewIfRequired] ()
#8  0x000000010bbcb3ce in -[UIViewController view] ()
#9  0x000000010bae6289 in -[UIWindow addRootViewControllerViewIfPossible] ()
#10 0x000000010bae664f in -[UIWindow _setHidden:forced:] ()
#11 0x000000010baf2de1 in -[UIWindow makeKeyAndVisible] ()
#12 0x000000010ba96417 in -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] ()
#13 0x000000010ba9919e in -[UIApplication _runWithMainScene:transitionContext:completion:] ()
#14 0x000000010ba98095 in -[UIApplication workspaceDidEndTransaction:] ()
#15 0x000000010f84c5e5 in __31-[FBSSerialQueue performAsync:]_block_invoke_2 ()
#16 0x000000010d7df41c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ ()
#17 0x000000010d7d5165 in __CFRunLoopDoBlocks ()
#18 0x000000010d7d4f25 in __CFRunLoopRun ()
#19 0x000000010d7d4366 in CFRunLoopRunSpecific ()
#20 0x000000010ba97b02 in -[UIApplication _run] ()
#21 0x000000010ba9a8c0 in UIApplicationMain ()
#22 0x000000010b0c90a7 in main at /Users/xxx/Documents/Test/Test/AppDelegate.swift:12
#23 0x000000010e54f145 in start ()
#24 0x000000010e54f145 in start ()
显然,我有一个简单的单视图iOS项目,我试图在我的视图控制器的
viewDidLoad
方法中实例化我的
SwiftView
对象

编辑

作为额外问题,另一个结果是,如果为了使我的子类正确实例化,我将Swift类变量更改为隐式展开的可选变量:

let name: String!
并通过将我的不可变名称变量设置为
nil
(这样我就可以使用我创建的指定初始化器,但如果有人使用我必须实现但从未使用的指定初始化器,则导致的崩溃是他们的错),我得到了一个完全出乎意料的结果,在我使用中的
name
参数实例化
SwiftView
对象之后

let view: SwiftView = SwiftView(name: "test")
view.name
的值为
nil
(可能是因为
init(frame:)
初始化器是在
init(name:)
方法初始设置
名称之后调用的)

编辑


如果您不需要在Objective-C代码中覆盖
initWithFrame:
,那么您只需调用
[super initWithFrame:CGRectZero]
,而不是
[self initWithFrame:CGRectZero]
。这将指示编译器查找基本的
UIView
实现,而不是子类中的实现。

如果子类没有实现方法,则在调用
self
super
时应该没有区别。。。在这种情况下,如果您可以更新您的问题以包含它,那么看到实际的堆栈跟踪可能会很好。View.m中的代码是正确的,因为只有子类的DIs执行超类的DIs(
[super init…]
)。所有其他初始值设定项都使用子类的DI(
[self init…]
),而不管它是否是超类的DI。但我不理解“当调用目标C超类的指定初始化器时,将尝试在self上调用initWithXX,并且此方法没有从超类继承,因为我们实现了指定的初始化器。”因为方法的继承不取决于是否是DI。@nhgrif在目标C中没有区别,但在Swift中,如果实现自己的DI,则不会继承DIs,因此不能调用
self
@AminNegm Awad-从异常中可以看出,试图实例化我的
SwiftView
类会导致在同一
SwiftView
类上尝试调用
init(frame:)
,它没有实现,也没有从其超类继承—“与Objective-C中的子类不同,Swift子类不会继承它们的sup
let name: String!
let view: SwiftView = SwiftView(name: "test")