Objective C类的非指定初始化继承

时间:2015-07-01 12:20:20

标签: objective-c swift inheritance designated-initializer

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

我的结论是:如果:

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

然后这将导致运行时异常,因为 Swift 类在调用 Objective C 超类的指定的初始化时会尝试调用{在initWithXX {1}}上,此方法尚未从超类继承,因为我们已经实现了指定的初始化

此测试的代码为:

View.h

self

View.m

#import <UIKit/UIKit.h>

@interface View : UIView

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

@end

SwiftView.swift

#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

异常生成代码

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")
    }
}

异常

  

致命错误:使用未实现的初始化程序'init(frame :)'用于类'Test.SwiftView'

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

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

除了(非常不受欢迎的)使这些不可变变量可变的方法并在第二个初始化阶段设置它们之外,任何人都可以看到解决这个问题的方法(无论是在Apple修复方法还是代码解决方法方面)?

修改

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

super

作为显而易见的上下文,我有一个简单的单视图iOS项目,我尝试在我的视图控制器#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 () 方法中实例化我的SwiftView对象。

修改

作为奖励问题,另一个结果是,如果为了使我的子类正确实例化,我将我的 Swift 类变量更改为隐式解包的可选项:

viewDidLoad

并通过将我的不可变名称变量设置为let name: String! 来实现缺少的初始化(以便我可以使用指定的初始化我创建但是如果有人使用我必须实现的指定的初始化但从未使用过,那么导致的崩溃就是他们的错误),在我实例化{{1}之后,我得到了完全出乎意料的结果

中带有nil参数的对象
SwiftView

name的值为let view: SwiftView = SwiftView(name: "test") (可能是因为在view.name方法初始设置{{1}后调用nil 初始化 }})。

修改

Radar发布到Apple

1 个答案:

答案 0 :(得分:0)

如果您不需要在Objective-C代码中覆盖 // returns all groups from DB getAllGroups() { apiService.getAllGroups().then((data) => { this.groups = data; }) .catch((error) => { console.log(error.response.data.message); }); }, ,则只需调用initWithFrame:而不是[super initWithFrame:CGRectZero]。这将指示编译器寻找基本的[self initWithFrame:CGRectZero]实现,而不是后代的实现。