Есть ли альтернатива 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.

3 ответа

РЕДАКТИРОВАТЬ: так как я написал этот ответ, ОП добавил "чистый Свифт" к вопросу в редактировании. Тем не менее, я оставляю этот ответ здесь, потому что он остается единственным правильным способом сделать это на момент написания этой статьи. Надеемся, что хуки инициализации модуля будут добавлены в 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's 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's 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];
}

С этими двумя частями кода приложение, которое связывается с вашей платформой, должно получить "App Will Launch", зарегистрированный на консоли ранее. 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

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

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

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

Например:

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

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

class AppDelegate: NSObject, NSApplicationDelegate {

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

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

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

Это достигнет той же функциональности, что и initialize(),

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