3

在对类进行子类化并向它们添加不可变变量时遇到问题UIKit,我做了一个测试项目来弄清楚发生了什么。

我的结论是,如果:

  • 我们有一个Objective-C类,它继承自另一个类,具有自己指定的初始化程序(隐式或显式注释)
  • 在其初始化程序中,它调用[self initWithXX]where initWithXXisinit超类上的方法
  • 我们在Swift 中子类化这个类,添加一个不可变属性(显然必须在实例化时初始化)
  • 我们为这个Swift类实现了一个指定的初始化器,它设置不可变属性,然后调用父类的指定初始化器

那么这将导致运行时异常,因为Swift类在调用Objective-C超类的指定初始化程序时会尝试调用initWithXX并且self该方法尚未从超类继承,因为我们已经实现了指定初始化程序

此测试的代码将是:

查看.h

#import <UIKit/UIKit.h>

@interface View : UIView

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

@end

查看.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:)”

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

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

除了使这些不可变变量可变并在第二个初始化阶段设置它们的(非常不受欢迎的)方法之外,任何人都可以看到解决这个问题的任何方法(无论是在 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 项目,我尝试在我的视图控制器的方法中实例化我的SwiftView对象。viewDidLoad

编辑

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

let name: String!

并通过将我的不可变名称变量设置为来实现缺少的初始化程序(这样我就可以使用我创建的指定初始化程序,但是如果有人使用我必须实现但从未使用过的指定初始化程序,那么导致的崩溃是他们的错),我得到在我用参数实例化对象之后,完全出乎意料的结果nilSwiftViewname

let view: SwiftView = SwiftView(name: "test")

的值view.namenil(可能是因为在方法最初设置之后调用了init(frame:) 初始化程序)。init(name:)name

编辑

雷达发布到 Apple

4

1 回答 1

0

如果您不需要initWithFrame:在 Objective-C 代码中重写,那么您可以简单地调用[super initWithFrame:CGRectZero]而不是[self initWithFrame:CGRectZero]. 这将指示编译器查找基本UIView实现而不是后代类中的实现。

于 2020-01-04T14:03:16.853 回答