在对UIKit类进行子类分类并向它们添加不可变变量时,我遇到了一些问题,因此我做了一个测试项目来了解发生了什么。
我的结论是,如果:
[self initWithXX],其中initWithXX是超类上的init方法。然后,这将导致运行时异常,因为Swift类在调用Objective超类的指定初始化项时,将尝试在self上调用self,而且由于我们已经实现了指定的初始化器,因此该方法没有从超类继承。
此测试的代码为:
View.h
#import <UIKit/UIKit.h>
@interface View : UIView
-(instancetype)initWithIdentifier:(NSString *)identifier;
@endView.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;
}
@endSwiftView.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(框架:)‘
我的动机是分类标准库类(UITableViewController,MKAnnotationView.)并添加不可变属性以包含注入的依赖项,然后需要在子类实例化时通过将它们传递到自定义指定的init方法来初始化这些属性,并发现这会导致上述意外异常。
在做了这个实验来确定原因之后,我想我理解为什么会发生这种情况(因为父对象--C类指定的初始化器正在委托给它自己的另一个指定的初始化器,而不是调用它的超类的初始化器)。显然,要解决这个问题,标准库应该更改它们的initialiser方法来调用超类初始化器,而不是self初始化程序,但我可以理解调用self而不是super可能是有效的功能,以便允许子类覆盖默认行为,同时保留特定的初始化符。而且,我可以想象这样的变化可能会造成很多问题,因为很多应用程序可能都建立在这个功能的基础上,而这会破坏它。我认为这里的基本问题是Objective和Swift之间的语言特性不兼容。
除了(非常不受欢迎的)使这些不可变变量可变并将它们设置在第二个初始化阶段之外,还有人能找到解决这个问题的方法(无论是在Apple修复还是代码解决方案方面)吗?
编辑
下面是我的测试项目的堆栈跟踪:
#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:)方法最初设置name之后调用的)。
编辑
雷达发布到苹果
发布于 2020-01-04 14:03:16
如果不需要在Objective代码中覆盖initWithFrame:,那么只需调用[super initWithFrame:CGRectZero]而不是[self initWithFrame:CGRectZero]。这将指示编译器查找基本的UIView实现,而不是后代clas中的实现。
https://stackoverflow.com/questions/31161143
复制相似问题