Неопределенное наследование инициализатора из классов Objective C

Столкнувшись с проблемами при классификации UIKit классы и добавляя к ним неизменяемые переменные, я сделал тестовый проект, чтобы выяснить, что происходит.

Мой вывод таков: если:

  • у нас есть класс Objective C, который наследуется от другого класса, с собственным назначенным инициализатором (неявным или явно аннотированным)
  • в своем инициализаторе он вызывает [self initWithXX] где initWithXX является init метод на суперкласс
  • мы подклассифицируем этот класс в Swift, добавляя неизменное свойство (которое, очевидно, должно быть инициализировано при создании экземпляра)
  • мы реализуем один назначенный инициализатор для этого класса Swift, который устанавливает неизменяемое свойство, затем вызывает родительский класс, назначенный инициализатором

тогда это вызовет исключение во время выполнения, потому что класс Swift при вызове инициализатора суперкласса Objective C будет пытаться вызвать initWithXX на 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")

исключение

фатальная ошибка: использование не реализованного инициализатора 'init(frame:)' для класса 'Test.SwiftView'

Моим мотивом этого было создание подклассов стандартных библиотечных классов (UITableViewController, MKAnnotationView...) и добавление неизменяемых свойств, чтобы они содержали внедренные зависимости от них, которые затем необходимо было инициализировать при создании экземпляра подкласса, передав их в пользовательский назначенный объект. init метод и обнаружив, что это вызвало вышеуказанное, неожиданное исключение.

Проведя этот эксперимент для определения причин, я думаю, что понимаю, почему это происходит (потому что инициализатор, назначенный родительским классом Objective C, делегирует другому назначенному инициализатору, а не вызывает инициализатор суперкласса). Казалось бы, очевидно, что для исправления этого стандартные библиотеки должны изменить свои методы инициализатора, чтобы вместо инициализаторов суперкласса вызывать self инициализаторы, но я могу оценить это призвание self вместо super может быть допустимой функциональностью, чтобы позволить подклассам переопределять поведение по умолчанию при сохранении определенных инициализаторов. Кроме того, я полагаю, что такое изменение может вызвать много проблем, потому что многие приложения могут быть построены на этой функциональности, и это сломает ее. Я полагаю, что основной проблемой здесь является несовместимость языковых функций между Objective C и 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 с одним представлением, и я пытаюсь создать свой экземпляр SwiftView объект в моем представлении контроллераviewDidLoad метод.

РЕДАКТИРОВАТЬ

Что касается проблем с бонусами, то другим результатом этого является то, что, если попытаться правильно создать экземпляр моего подкласса, я должен был изменить свою переменную класса Swift на неявно развернутую необязательную:

let name: String!

и реализовать отсутствующий инициализатор, установив мою неизменную переменную имени в nil (так что я могу использовать назначенный инициализатор, который я создал, но если кто-то использует назначенный инициализатор, который я должен был реализовать, но никогда не использовал, то в результате сбой является их ошибкой), я получаю совершенно неожиданный результат, следующий за моим созданием экземпляра SwiftView объект с name параметр в

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

значение view.name является nil (предположительно потому, что init(frame:) инициализатор вызывается после init(name:) Метод изначально устанавливает name).

РЕДАКТИРОВАТЬ

Радар размещен в Apple

0 ответов

Если вам не нужно отменять initWithFrame: в коде Objective-C, тогда вы можете просто вызвать [super initWithFrame:CGRectZero] вместо того [self initWithFrame:CGRectZero]. Это проинструктирует компилятор искать базуUIView реализация вместо той, что в классе-потомке.

Другие вопросы по тегам