Klassenmethode für Klassen in Swift "initialisieren"?

74

Ich suche nach einem Verhalten ähnlich der +(void)initializeKlassenmethode von Objective-C , da die Methode einmal aufgerufen wird, wenn die Klasse initialisiert wird, und danach nie wieder.

Ein einfaches class init () {}in einem classVerschluss wäre wirklich schlank! Und wenn wir " class vars" anstelle von " static vars in einem Strukturabschluss" verwenden, passt das natürlich alles sehr gut zusammen!

Aleclarson
quelle

Antworten:

61

Wenn Sie eine Objective-C-Klasse haben, ist es am einfachsten, diese einfach zu überschreiben +initialize. Stellen Sie jedoch sicher, dass Unterklassen Ihrer Klasse ebenfalls überschrieben werden, da +initializesonst die Klassen Ihrer Klasse +initializemöglicherweise mehrmals aufgerufen werden! Wenn Sie möchten, können Sie dispatch_once()(siehe unten) zum Schutz vor mehreren Anrufen verwenden.

class MyView : UIView {
  override class func initialize () {
    // Do stuff
  }
}

 

Wenn Sie eine Swift-Klasse haben, ist das Beste, was Sie bekommen können, dispatch_once()in der init()Anweisung.

private var once = dispatch_once_t()

class MyObject {
  init () {
    dispatch_once(&once) {
      // Do stuff
    }
  }
}

Diese Lösung unterscheidet sich von +initialize(die beim ersten Messaging einer Objective-C-Klasse aufgerufen wird) und ist daher keine echte Antwort auf die Frage. Aber es funktioniert gut genug, IMO.

Aleclarson
quelle
1
Sie sollten verwendendispatch_once
Bryan Chen
2
@BryanChen Warum? In Ziel C initializewurde garantiert, dass es nur einmal aufgerufen wird. Hat sich das geändert?
Sapi
2
Dies unterscheidet sich von +initializeObjective-C. +initializeIn Objective-C wird das erste Mal aufgerufen, wenn die Klasse eine Nachricht erhält . Dies wird beim ersten Erstellen einer Instanz der Klasse aufgerufen.
Newacct
7
Ab Swift 3.1 (Xcode 8.3) wird bei Verwendung von override class func initialize()now eine Warnung generiert, die "... nicht garantiert von Swift aufgerufen wird und in zukünftigen Versionen nicht mehr zulässig ist". Siehe: stackoverflow.com/questions/42824541/…
Scott Corscadden
5
Ab Swift 4 ist die Verwendung override class func initialize()ein Fehler.
ThomasW
52

In Swift gibt es keinen Typinitialisierer .

„Im Gegensatz zu Eigenschaften gespeicherter Instanzen müssen Sie Eigenschaften gespeicherter Typen immer einen Standardwert geben. Dies liegt daran, dass der Typ selbst keinen Initialisierer hat , der einer gespeicherten Typeneigenschaft zum Zeitpunkt der Initialisierung einen Wert zuweisen kann. “

Auszug aus: Apple Inc. "Die schnelle Programmiersprache". iBooks .


Sie können eine type-Eigenschaft verwenden, deren Standardwert ein Abschluss ist. Der Code im Abschluss würde also ausgeführt, wenn die Eigenschaft type (oder die Klassenvariable) festgelegt wird.

class FirstClass {
    class var someProperty = {
     // you can init the class member with anything you like or perform any code
        return SomeType
    }()
}

Aber class stored properties not yet supported(getestet in Xcode 8).

Eine Antwort ist zu verwenden static, es ist die gleiche wie class final.

Guter Link dafür ist

Festlegen eines Standardeigenschaftswerts mit einem Abschluss oder einer Funktion

Auszug aus: Apple Inc. "Die schnelle Programmiersprache". iBooks .


Codebeispiel:

class FirstClass {
    static let someProperty = {
        () -> [Bool] in
        var temporaryBoard = [Bool]()
        var isBlack = false
        for i in 1...8 {
            for j in 1...8 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }

        print("setting default property value with a closure")
        return temporaryBoard
    }()
}

print("start")
FirstClass.someProperty

Druckt

Start

Festlegen des Standardeigenschaftswerts mit einem Abschluss

Also ist es faul ausgewertet.

Binarian
quelle
Sie können dies staticjetzt mit einer Eigenschaft tun, static var someProperty = { return SomeType }()aber das Problem ist, dass dieser Code beim ersten Verweisen auf die Eigenschaft träge ausgeführt wird. developer.apple.com/swift/blog/?id=7
Aneel
@iGodric "Der Code im Abschluss wird also ausgeführt, wenn die Eigenschaft type (oder die Klassenvariable) festgelegt wird." Können Sie das klarstellen? Wann genau wird die Schließung ausgeführt?
Jarrod Smith
1
@JarrodSmith Ich habe die Antwort erweitert, um ein Beispiel zu zeigen.
Binarian
Warum brauchen wir eine Schließung? Es funktioniert ohne den Verschluss nein? Oder liegt es daran, dass es sich um eine statische Variable handelt?
Van Du Tran
@VanDuTran Wenn Sie den Klasseninitialisierer beim Erstellen der Klasse verwenden möchten, ist dies nur die App-Lademethode. Das interessante Verhalten von + initialize ist jedoch, dass es faul ist (lädt die Werte nur, wenn die Klasse zum ersten Mal verwendet wird). Für Faulheit habe ich den Verschluss benutzt.
Binarian
7

Für @objcKlassen class func initialize()funktioniert das definitiv, da +initializees von der Objective-C-Laufzeit implementiert wird. Aber für "native" Swift-Klassen müssen Sie die anderen Antworten sehen.

newacct
quelle
Ich habe eine schnelle Klasse, die von erbt NSObject. Ich habe Hinweise, dass meine Klasse +initialize(eigentlich override class func initialize()) erst aufgerufen wird, wenn ich eine Methode der Klasse aufrufe. Der Zugriff auf Eigenschaften scheint dies nicht auszulösen. Vielleicht liegt das daran, dass schnelle Eigenschaften keine Methoden (Getter und Setter) unter der Haube sind, wie in Objective-C?
Nicolas Miari
1
@NicolasMiari: Sprechen Sie über Instanz- oder Klassenmethoden? Instanz- oder Klasseneigenschaften? In Objective-C muss eine Klassenmethode aufgerufen werden, um eine Instanz zu erstellen. Daher würde ich denken, dass das Erstellen einer Instanz diese auslösen sollte, bevor Sie überhaupt eine Instanzmethode oder Instanzeigenschaft verwenden. Wenn Sie über Klasseneigenschaften sprechen, verfügt Objective-C nicht über Klasseneigenschaften, sodass die Klasseneigenschaften von Swift möglicherweise nicht mit der ObjC-Laufzeit kompatibel sind, da keine Kompatibilität erforderlich ist (nur Vermutung, ich habe die Quelle nicht überprüft).
Newacct
1
@NicolasMiari: Die Dokumentation für +[NSObject initialize]besagt, dass es aufgerufen wird, bevor der Klasse "ihre erste Nachricht gesendet wird". Es gibt also keinen Grund zu der Annahme, dass Klasseneigenschaften es trotzdem auslösen würden.
Newacct
Ich sehe, Klasseneigenschaften ... Diese haben in Objective-C zwar kein Gegenstück, aber Sie könnten sie fälschen, indem Sie zwei Klassenmethoden mit Signaturen implementieren, die mit dem (synthetisierten) Getter und Setter einer Instanzeigenschaft kompatibel sind. Zum Beispiel + (void) setColor: (UIColor *) newColor; und + (UIColor *) Farbe; Ich denke, schnelle Klasseneigenschaften sind ein ganz anderes Tier.
Nicolas Miari
2

@aleclarson hat es geschafft, aber ab dem letzten Swift 4 können Sie nicht direkt überschreiben initialize. Sie können dies weiterhin mit Objective-C und Kategorien für Klassen erreichen, von denen NSObjectmit einer class / static- swiftyInitializeMethode geerbt wird, die von Objective-C in aufgerufen wird MyClass.mund die Sie neben MyClass.swift:

# MyView.swift

import Foundation
public class MyView: UIView
{
    @objc public static func swiftyInitialize() {
        Swift.print("Rock 'n' roll!")
    }
}

# MyView.m

#import "MyProject-Swift.h"
@implementation MyView (private)
    + (void)initialize { [self swiftyInitialize]; }
@end

Wenn Ihre Klasse nicht von erben erben kann NSObjectund +loadstattdessen +initializeeine geeignete Anpassung verwendet, können Sie Folgendes tun:

# MyClass.swift

import Foundation
public class MyClass
{
    public static func load() {
        Swift.print("Rock 'n' roll!")
    }
}
public class MyClassObjC: NSObject
{
    @objc public static func swiftyLoad() {
        MyClass.load()
    }
}

# MyClass.m

#import "MyProject-Swift.h"
@implementation MyClassObjC (private)
    + (void)load { [self swiftyLoad]; }
@end

Es gibt einige Fallstricke, insbesondere wenn Sie diesen Ansatz in statischen Bibliotheken verwenden. Weitere Informationen finden Sie im vollständigen Beitrag auf Medium! ✌️

Ian Bytchek
quelle
2

Sie können gespeicherte Typeneigenschaften anstelle der initializeMethode verwenden.

class SomeClass: {
  private static let initializer: Void = {
    //some initialization
  }()
}

Da die Eigenschaften gespeicherter Typen beim ersten Zugriff tatsächlich träge initialisiert werden, müssen Sie sie irgendwo weiterleiten. Sie können dies mit gewöhnlichen gespeicherten Objekten tun:

class SomeClass: {
  private static let initializer: Void = {
    //some initialization
  }()
  private let initializer: Void = SomeClass.initializer
}
Cy-4AH
quelle
Wäre schön, aber ist es schnell? Soweit ich weiß, können in schnellen 'let'-Deklarationen keine Eigenschaften berechnet werden.
Aquajet
2
@Aquajet: initializerist keine berechnete Eigenschaft, da der zugewiesene Abschluss tatsächlich ausgeführt wird. Beachten Sie das Gleichheitszeichen und die Klammern am Ende. Wie in der Antwort erwähnt, werden statische Eigenschaften träge ausgewertet, sodass der Abschluss beim Laden der Klasse nicht sofort ausgeführt wird. Stattdessen wird es ausgelöst, indem auf die Eigenschaft an einer anderen Stelle im Code verwiesen wird.
Lukas Kubanek
-5

Ich kann keinen gültigen Anwendungsfall finden, um so etwas wie +[initialize]in Swift zu haben. Vielleicht erklärt dies, wie es nicht existiert

Warum brauchen wir +[initialize]in ObjC?

Initialisieren einer globalen Variablen

static NSArray *array;

+ (void)initialize {
    array = @[1,2,3];
}

was in Swift

struct Foo {
    static let array = [1,2,3]
}

Um etwas zu hacken

+ (void)initialize {
    swizzle_methodImplementation()
}

was von Swift nicht unterstützt wird (ich kann nicht herausfinden, wie es für die reine Swift-Klasse / struct / enum geht)

Bryan Chen
quelle
18
Ein gültiger Anwendungsfall wäre die Registrierung von NSUserDefaults
Rick
5
Ein weiterer gültiger Fall ist die Enthüllung von Bindung
AJ Venturella
10
Ein weiterer gültiger Anwendungsfall ist das Registrieren der Klasse (Selbst) als Teil der Gruppe. Zum Beispiel Coreimage Programmführer schlagen die Verwendung +[[CIFilter registerFilterName:constructor:classAttributes:]in +initializeden Subklassen von CIFilter.
Nacho4d
4
Ein weiterer gültiger Anwendungsfall besteht darin, eine Zufallsfunktion einmal für eine Klasse zu setzen, die Zufallszahlen verwendet.
Chris Hatton