【发布时间】:2015-07-01 12:20:20
【问题描述】:
在对 UIKit 类进行子类化并向它们添加不可变变量时遇到问题,我做了一个测试项目来弄清楚发生了什么。
我的结论是,如果:
- 我们有一个 Objective-C 类,它继承自另一个类,具有自己的指定初始化程序(隐式或显式注释)
- 在其初始化程序中,它调用
[self initWithXX],其中initWithXX是超类上的init方法 - 我们在 Swift 中子类化这个类,添加一个不可变属性(显然必须在实例化时初始化)
- 我们为这个 Swift 类实现了一个designated initialiser,它设置不可变属性然后调用父类designated initialiser
那么这将导致运行时异常,因为 Swift 类在调用 Objective-C 超类的指定初始化程序时会尝试调用initWithXX on self 并且这个方法没有从超类继承,因为我们已经实现了一个指定的初始化器。
这个测试的代码是:
View.h
#import <UIKit/UIKit.h>
@interface View : UIView
-(instancetype)initWithIdentifier:(NSString *)identifier;
@end
View.m
#import "View.h"
@implementation View
-(instancetype)initWithIdentifier:(NSString *)identifier {
self = [self initWithFrame:CGRectZero];
if (self) {
if ([identifier length] > 0) {
return self;
}
}
return nil;
}
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
return self;
}
return nil;
}
@end
SwiftView.swift
import Foundation
import UIKit
class SwiftView: View {
let name: String
init(name: String) {
self.name = name
super.init(identifier: "test")
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
异常生成代码
let view: SwiftView = SwiftView(name: "test")
例外
致命错误:对“Test.SwiftView”类使用未实现的初始化程序“init(frame:)”
我这样做的动机是对标准库类(UITableViewController、MKAnnotationView...)进行子类化并添加不可变属性以包含对它们的注入依赖项,然后需要在子类的实例化时对其进行初始化通过将它们传递给自定义指定的init 方法,并发现这导致了上述意外异常。
做了这个实验来确定原因,我想我明白为什么会发生了(因为父 Objective-C 类 designated initialiser 正在委托给另一个 指定的初始化器,而不是调用其超类的初始化器)。很明显,要解决这个问题,标准库应该更改它们的 initialiser 方法来调用超类 initialisers 而不是 self initialisers,但是我可以理解调用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!
并通过将我的不可变 name 变量设置为 nil 来实现缺少的 initialiser(这样我就可以使用我的 designated initialiser已创建,但如果有人使用我必须实现但从未使用过的指定初始化程序,那么导致的崩溃是他们的错),我得到完全出乎意料的结果,在我用 @ 实例化 SwiftView 对象之后987654345@参数在
let view: SwiftView = SwiftView(name: "test")
view.name的值是nil(大概是因为init(frame:)initialiser是在init(name:)方法初始设置name之后调用的)。
编辑
Radar 发布到 Apple
【问题讨论】:
-
如果子类没有实现某个方法,那么在
self或super上调用它应该没有区别......这种情况下,如果您可以更新您的问题以包含它。 -
View.m 中的代码是正确的,因为只有子类的 DI 执行超类的 DI (
[super init…])。所有其他初始化程序都使用子类 ([self init…]) 的 DI,无论它是否是超类的 DI。但是我不明白“在调用Objective C超类的指定初始化程序时,将尝试在self上调用initWithXX,并且此方法尚未从超类继承,因为我们已经实现了指定初始化程序。”因为方法的继承并不取决于是否是 DI。 -
@nhgrif 在 Objective C 中没有区别,但在 Swift 中你不继承 DIs if你实现了自己的 DI,因此你不能调用
self。 -
@AminNegm-Awad — 从异常中可以看出,尝试实例化我的
SwiftView类会导致在同一个SwiftView类上尝试调用init(frame:),但它没有已实现且不从其超类继承 - developer.apple.com/library/prerelease/ios/documentation/Swift/… "与 Objective-C 中的子类不同,Swift 子类默认不继承其超类初始化器" -
我希望 Objective-C 超类的初始化程序总是被继承。但我错了。谢谢。
标签: objective-c swift inheritance designated-initializer