Есть ли альтернатива initialize() в macOS теперь, когда Swift устарел?

Objective-C объявляет функцию класса initialize(), которая запускается один раз для каждого класса до его использования. Он часто используется в качестве точки входа для обмена реализацией методов (swizzling), между прочим. Его использование устарело в Swift 3.1.

Это то, что я использовал:

extension NSView {
    public override class func initialize() {
        // This is called on class init and before 'applicationDidFinishLaunching'
    }
}

Как я могу достичь того же самого элемента без initialize?

Мне нужно это для фреймворка, поэтому требовать вызова в AppDelegate не стоит. Мне нужно, чтобы он вызывался перед applicationDidFinishLaunching.

Мне действительно нравится это решение. Это именно то, что я ищу, но это для iOS. Мне это нужно для macOS. Может ли кто-нибудь предложить версию macOS?

Чтобы быть конкретным, мне нужен эквивалент этого, но для macOS:

extension UIApplication {
    private static let runOnce: Void = {
        // This is called before 'applicationDidFinishLaunching'
    }()

    override open var next: UIResponder? {
        UIApplication.runOnce
        return super.next
    }
}

Я пробовал переопределять различные свойства в NSApplication без успеха.

Решение должно быть в чистом Swift. Нет цели-C.

Ответ 1

Нет, альтернативу Swift для initialize() не существует, главным образом потому, что Swift статически отправляется, поэтому вызовы методов не могут быть перехвачены. И действительно больше не требуется, поскольку этот метод обычно использовался для инициализации статических переменных из файлов Objective-C, а в переменных Swift статические/глобальные переменные всегда ленивы и могут быть инициализированы результатом выражения (вещь невозможна в Objective- С).

Даже если бы можно было добиться чего-то подобного, я бы отговорил вас неявно запустить материал без ведома пользователей инфраструктуры. Я бы рекомендовал добавить метод configure где-то в библиотеке и попросить пользователей вашей библиотеки позвонить ему. Таким образом, они контролируют точку инициализации. Есть только несколько вещей хуже, чем наличие рамки, с которой я связан, но больше не использую (или еще не), чтобы начать выполнять код без моего согласия.

Это просто разумно, чтобы дать пользователям структуры управления, когда они хотят, чтобы начальный код был запущен. Если действительно ваш код должен запускаться в самом начале жизненного цикла приложения, попросите пользователей инфраструктуры сделать вызов в точку входа в инфраструктуру перед любыми другими вызовами. Также в их интересах обеспечить правильную настройку вашей инфраструктуры.

Например:

MyFramework.configure(with: ...)
// or
MyManager.start()

Ответ 2

EDIT: Поскольку я написал этот ответ, OP добавил "чистый Swift" к вопросу в редактировании. Однако я оставляю этот ответ здесь, потому что он остается единственным правильным способом сделать это на момент написания этой статьи. Хотелось бы надеяться, что крючки инициализации модуля будут добавлены в Swift 6 или 7 или 8, но по состоянию на март 2018 года чистый Swift является неправильным инструментом для этого варианта использования.

Исходный ответ:

К сожалению, Swift не имеет прямого эквивалента старым методам initialize() и load(), поэтому это невозможно сделать в чистом Swift AFAIK. Однако, если вы не прочь смешивать небольшое количество Objective-C в своем проекте, это не сложно сделать. Просто создайте класс Swift, полностью подверженный Objective-C:

class MyInitThingy: NSObject {
    @objc static func appWillLaunch(_: Notification) {
        print("App Will Launch")
    }
}

Затем добавьте этот короткий файл Objective-C в проект:

#import <Cocoa/Cocoa.h>

static void __attribute__ ((constructor)) Initer() {
    // Replace "MyFrameworkName" with your framework module name.

    // You can also just #import <MyFrameworkName/MyFrameworkName-Swift.h>
    // and then access the class directly, but that requires the class to
    // be public, which pollutes the framework external interface.
    // If we just look up the class and selector via the Objective-C runtime,
    // we can keep everything internal.

    Class class = NSClassFromString(@"MyFrameworkName.MyInitThingy");
    SEL selector = NSSelectorFromString(@"appWillLaunch:");

    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

    [center addObserver:class
               selector:selector
                   name:NSApplicationWillFinishLaunchingNotification
                 object:nil];
}

С этими двумя фрагментами кода приложение, которое ссылается на вашу структуру, должно получить "приложение запустится", зарегистрированное на консоли когда-нибудь до applicationDidFinishLaunching.

Кроме того, если у вас уже есть открытый класс ObjC-видимости в вашем модуле, вы можете сделать это, не используя функции выполнения, через категорию:

public class SomeClass: NSObject {
    @objc static func appWillLaunch(_: Notification) {
        print("App Will Launch")
    }
}

а также:

#import <Cocoa/Cocoa.h>
#import <MyFrameworkName/MyFrameworkName-Swift.h>

@interface SomeClass (InternalSwiftMethods)

+ (void)appWillLaunch:(NSNotification *)notification;

@end

@implementation SomeClass (FrameworkInitialization)

+ (void)load {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

    [center addObserver:self
               selector:@selector(appWillLaunch:)
                   name:NSApplicationWillFinishLaunchingNotification
                 object:nil];
}

@end

Ответ 3

Как указывалось ранее другими, не возможно (и хорошее программирование) делать то, что вы просите в Framework в Swift. Достижение функциональности самого приложения (где такое поведение принадлежит) довольно просто, но не нужно связываться с уведомлениями или селекторами. Вы просто переопределяете init вашего NSApplicationDelegate (или UIApplicationDelegate) и настраиваете свой инициализатор класса там:

class AppDelegate: NSObject, NSApplicationDelegate {

    override init() {
        super.init()
        YourClass.initializeClass()
    }
}

И соответствующая статическая функция:

class YourClass {
    static func initializeClass() {
        // do stuff
    }
}

Это позволит реализовать те же функции, что и initialize().