Динамическое создание подклассов в Swift - PullRequest
0 голосов
/ 27 сентября 2018

Я приехал в Swift из Objective-C, и есть много вещей, которые Objective-C может сделать, но Swift намного сложнее.Например, динамический инициализатор ООП.

Например, у меня есть этот код, работающий в Objective-C:

@interface CommonVC: UIViewController
+ (instancetype)showFrom:(UIViewController *)vc;
@end

@implementation CommonVC

+ (instancetype)showFrom:(UIViewController *)vc {
    CommonVC *instance = [self instantiateFrom:vc.nibBundle];
    [vc presentViewController:instance animated:YES completion:nil];
    return instance;
}

// this is like convenience initializer.
+ (instancetype)instantiateFrom:(NSBundle *)aBundle {
    return [self.alloc initWithNibName:NSStringFromClass(self.class) bundle:aBundle];
}

@end

@interface SubClassVC: CommonVC
@end

И затем используйте подкласс или суперкласс, как это:

SubClassVC *subVC = [SubClassVC showFrom:self];
// or in swift:
SubClassVC.show(from: self)

Однако вбыстро, кажется, невозможно реализовать что-то подобное.Я пробовал несколько, но всегда получал ошибку компиляции.Вот один из них:

class CommonVC: UIViewController {

    class func show(from sender: UIViewController) -> Self {
        let vc = self(sender: sender) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
        sender.present(vc, animated: true, completion: nil)
        return unsafeDowncast(vc, to: self)
    }

    convenience init(sender: UIViewController) {
        self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
        loadView()
    }
}

Итак, как мне написать универсальный удобный инициализатор viewController из суперкласса, а затем вызвать его с помощью подкласса?

Конечно, в моем удобном init есть много вещей, которые я просто сократил до этого простого кода, также функция show(from:) имеет другое представление вместо этого простого present(_:animated:completion:).

Даже если я сделаю функцию для выполнения настройки после инициализации, она все равно не будет работать

class CommonVC: UIViewController {

    class func show(from sender: UIViewController) -> Self {
        let vc = self.init(nibName: type(of: self).className, bundle: sender.nibBundle) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
        vc.setupAfterInitialize()
        sender.present(vc, animated: true, completion: nil)
        return unsafeDowncast(vc, to: self)
    }

    convenience init(sender: UIViewController) {
        self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
        setupAfterInitialize()
    }

    internal func setupAfterInitialize() {
        // do stuff
        loadView()
    }
}

И код выглядит глупо, что не делает удобство init convenience.

Пока я не могу использовать функцию класса show(from:), но перенес презентацию наружу и сделал что-то вроде:

CommonVC.show(from: self)
SubClassVC(sender: self).present()

// instead of this simple presentation:
// SubClassVC.show(from: self)

Я даже пробовал это, но все еще не работает:

class func show<T: CommonVC>(from sender: UIViewController) -> Self {
    T.init(nibName: type(of: self).className, bundle: sender.nibBundle)
    ....

1 Ответ

0 голосов
/ 27 сентября 2018

Когда вы переключаетесь с Objective-C на Swift, заманчиво просто перевести ваш стиль Objective-C в код Swift.Но Swift принципиально отличается в некоторых отношениях.

Может быть возможно реализовать универсальный класс, из которого все ваши контроллеры являются подклассами, но мы стараемся избегать наследования в Swift, где это возможно (в пользу протоколов ирасширения).

Хорошее эмпирическое правило Swift от Apple гласит: «всегда начинай с протокола» ...

На самом деле очень легко реализовать то, что вы хотите, используя протокол,и расширение:

protocol Showable {
    init(className: String, bundle: Bundle?)
    static func show(from: UIViewController) -> Self
}

extension Showable where Self: UIViewController {

    init(className: String, bundle: Bundle?) {
        self.init(nibName: className, bundle: bundle)
    }

    static func show(from: UIViewController) -> Self {
        let nibName = String(describing: self)
        let instance = self.init(className: nibName, bundle: from.nibBundle)
        from.present(instance, animated: true, completion: nil)
        return instance
    }

}

В приведенном выше коде я объявил протокол Showable и расширение, обеспечивающее реализацию по умолчанию, которая применяется там, где принимающий класс является экземпляром UIViewController.

Наконец, чтобы предоставить эту функцию каждому контроллеру представления в вашем проекте , просто объявите пустое расширение:

extension UIViewController: Showable { }

С этими двумя добавленными короткими фрагментами кода вы можетеТеперь сделайте то, что вы описываете в своем вопросе (если для вашего экземпляра контроллера представления существует перо с соответствующим именем):

let secondController = SecondViewController.show(from: self)
ThirdController.show(from: secondController)

AnЭто красота Свифта.Все ваши подклассы UIViewController теперь получают эту функциональность бесплатно;наследование не требуется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...