“Ініціалізувати” метод класу для класів у Swift?


74

Я шукаю поведінку, подібну до +(void)initializeметоду класу Objective-C , оскільки метод викликається один раз при ініціалізації класу і ніколи більше після цього.

Простий class init () {}у classзакритті буде дійсно гладким! І очевидно, що коли ми зможемо використовувати " class vars" замість " static vars у закритті структури", це все буде дуже добре відповідати!

Відповіді:


61

Якщо у вас є клас Objective-C, найпростіше просто перезаписати +initialize. Однак переконайтесь, що підкласи вашого класу також замінюються, +initializeінакше ваші класи +initializeможуть викликатися більше одного разу! Якщо ви хочете, ви можете використовувати dispatch_once()(згадане нижче) для захисту від численних дзвінків.

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

 

Якщо у вас є клас Swift, найкраще, що ви можете отримати, знаходиться dispatch_once()всередині init()оператора.

private var once = dispatch_once_t()

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

Це рішення відрізняється від +initialize(яке називається першим повідомленням класу Objective-C) і, отже, не є правдивою відповіддю на запитання. Але це працює досить добре, ІМО.


1
вам слід скористатисяdispatch_once
Bryan Chen

2
@BryanChen Чому? У цілі C initializeбуло гарантовано викликати лише один раз. Це змінилося?
sapi

2
Це відрізняється від +initializeоб'єктиву-C. +initializeв Objective-C називається перший раз клас буде обмінювалися повідомленнями . Це називається першим випадком створення екземпляра класу.
newacct

7
Починаючи з Swift 3.1 (Xcode 8.3), використання override class func initialize()зараз генерує попередження, що включає "... яке Swift не гарантує, що його буде викликати і буде заборонено в наступних версіях". Див: stackoverflow.com/questions/42824541 / ...
Скотт Corscadden

5
Станом на Swift 4 використання override class func initialize()є помилкою.
ThomasW

52

У Swift немає ініціалізатора типу .

“На відміну від властивостей збереженого екземпляра, ти завжди повинен надавати властивостям збереженого типу значення за замовчуванням. Це тому, що сам тип не має ініціалізатора, який може призначати значення властивості збереженого типу під час ініціалізації. "

Витяг від: Apple Inc. “Швидка мова програмування”. iBooks .


Ви можете використовувати властивість типу, значенням за замовчуванням є закриття. Отже, код у закритті буде виконуватися, коли встановлено властивість type (або змінну класу).

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

Але class stored properties not yet supported(протестовано в Xcode 8).

Одна відповідь - використовувати static, це те саме, що class final.

Хороше посилання для цього

Встановлення значення властивості за замовчуванням із закриттям або функцією

Витяг від: Apple Inc. “Швидка мова програмування”. iBooks .


Приклад коду:

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

Відбитки

почати

встановлення значення властивості за замовчуванням із закриттям

Тож ліниво оцінюється.


Ви можете це зробити з staticвластивістю зараз ... static var someProperty = { return SomeType }()... але проблема в тому, що цей код запускається ліниво, коли вперше посилається на властивість. developer.apple.com/swift/blog/?id=7
Aneel

@iGodric "Отже, код у закритті буде виконуватися, коли встановлено властивість type (або змінну класу)". Ви можете це пояснити. Коли саме буде виконано закриття?
Джаррод Сміт

1
@JarrodSmith Я розширив відповідь, щоб показати приклад.
Бінаріан

Навіщо нам закриття? Він працює без закриття ні? Або це тому, що це пов’язано із статичним змінним?
Ван Ду Тран з

@VanDuTran Якщо ви хочете використовувати ініціалізатор класу при створенні класу, це просто метод завантаження програми. Але цікавою поведінкою + initialize є те, що він ледачий (завантажує значення лише при першому використанні класу). Для ледачих я використовував закриття.
Бінаріан

7

Для @objcкласів, class func initialize()безумовно, працює, оскільки +initializeце реалізовано середовищем виконання Objective-C. Але для "рідних" класів Swift вам доведеться бачити інші відповіді.


У мене є швидкий клас, який успадковується від NSObject. Я помічаю, що +initialize(власне, override class func initialize()) мого класу не викликається, доки я не викликаю метод класу. Здається, доступ до властивостей не викликає його. Можливо, це тому, що швидкі властивості не є методами (геттерами та сеттерами) під капотом, як у Objective-C?
Ніколас Міарі

1
@NicolasMiari: Ви говорите про екземпляри чи методи класу? Властивості екземпляра чи класу? У Objective-C для створення екземпляра потрібно викликати метод класу, тому я вважаю, що акт створення екземпляра повинен його ініціювати, ще до того, як ти навіть використовуєш метод екземпляра або властивість екземпляра. Якщо ви говорите про властивості класу, Objective-C не має властивостей класу, тому властивості класу Swift можуть бути несумісними із середовищем виконання ObjC, оскільки немає необхідності бути сумісними (просто здогадуючись, я не перевірив джерело).
newacct

1
@NicolasMiari: Документація для нас +[NSObject initialize]говорить, що вона викликається до того, як класу "буде надіслано перше повідомлення", тому немає підстав думати, що властивості класу все одно викликають це.
newacct

Властивості класу, я бачу ... Вони справді не мають аналогів в Objective-C, але ви можете сфальсифікувати їх, реалізувавши два методи класу з підписами, сумісними із (синтезованим) геттером та сеттером властивості екземпляра. Наприклад, + (порожнеча) setColor: (UIColor *) newColor; і + (UIColor *) колір; Я думаю, швидкі властивості класу - це зовсім інший звір.
Ніколас Міарі,

2

@aleclarson це забив, але станом на останній Swift 4 ви не можете безпосередньо перевизначити initialize. Ви все ще можете досягти цього за допомогою Objective-C та категорій для класів, що успадковуються з NSObjectдопомогою swiftyInitializeметоду class / static , який викликається з Objective-C у MyClass.m, який ви включаєте в джерела компіляції поряд 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

Якщо ваш клас не може успадковувати NSObjectта використовувати +loadзамість, а +initializeне підходить, ви можете зробити щось подібне:

# 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

Є кілька помилок, особливо при використанні цього підходу в статичних бібліотеках, перегляньте повну публікацію на Medium для деталей! ✌️


2

Ви можете використовувати властивості збереженого типу замість initializeметоду.

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

Але оскільки властивості збережених типів насправді ледаче ініціалізуються при першому доступі, вам потрібно буде їх кудись перенаправити. Ви можете зробити це за допомогою звичайного збереженого властивості:

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

Було б непогано, але це швидко? Наскільки я знаю, у швидких деклараціях 'let' не можна обчислювати властивості.
Aquajet

3
@Aquajet: initializerне є обчислюваною властивістю, оскільки призначене закриття фактично виконується. Зверніть увагу на знак рівності та дужки в кінці. Як зазначалося у відповіді, статичні властивості оцінюються ліниво, тому закриття не виконується негайно при завантаженні класу. Натомість він запускається шляхом посилання на властивість десь ще в коді.
Лукас Кубанек,

-5

Я не можу знайти жодного дійсного випадку використання, щоб мати щось на зразок +[initialize]Swift. Можливо, це пояснює те, як його не існує

Навіщо нам +[initialize]в ObjC?

Для ініціалізації якоїсь глобальної змінної

static NSArray *array;

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

який у Свіфті

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

Щоб зробити якийсь хак

+ (void)initialize {
    swizzle_methodImplementation()
}

що не підтримується Swift (я не можу зрозуміти, як це зробити для чистого класу Swift / struct / enum)


18
Дійсним варіантом використання буде реєстрація NSUserDefaults
Рік,

5
інший дійсний випадок - викриття Прив'язки
AJ Venturella

10
Ще один дійсний варіант використання - це реєстрація класу (self) як частини групи. Наприклад, посібник із програмування CoreImage пропонує використовувати +[[CIFilter registerFilterName:constructor:classAttributes:]в +initializeпідкласах CIFilter.
nacho4d

4
Інший дійсний варіант використання - засідання випадкової функції один раз для класу, який використовує випадкові числа.
Кріс Хаттон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.