77

I'm looking for behavior similar to Objective-C's +(void)initialize class method, in that the method is called once when the class is initialized, and never again thereafter.

A simple class init () {} in a class closure would be really sleek! And obviously when we get to use "class vars" instead of "static vars in a struct closure", this will all match really well!

aleclarson
  • 18,087
  • 14
  • 64
  • 91

6 Answers6

63

If you have an Objective-C class, it's easiest to just override +initialize. However, make sure subclasses of your class also override +initialize or else your class's +initialize may get called more than once! If you want, you can use dispatch_once() (mentioned below) to safeguard against multiple calls.

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

 

If you have a Swift class, the best you can get is dispatch_once() inside the init() statement.

private var once = dispatch_once_t()

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

This solution differs from +initialize (which is called the first time an Objective-C class is messaged) and thus isn't a true answer to the question. But it works good enough, IMO.

aleclarson
  • 18,087
  • 14
  • 64
  • 91
  • 1
    you should use `dispatch_once` – Bryan Chen Jun 10 '14 at 10:36
  • 2
    @BryanChen Why? In Objective C, `initialize` was guaranteed to only be called once. Has that changed? – sapi Aug 25 '14 at 10:18
  • @sapi there is no reason to create a class and use `initialize` purely to achieve something can be done with `dispatch_once` – Bryan Chen Aug 25 '14 at 10:55
  • @BryanChen - ah, gotcha. I was thinking more of cases where the class is created already (I found this thread searching for why AppDelegate.initialize wasn't being auto-completed for me.) – sapi Aug 25 '14 at 11:49
  • Added `dispatch_once` as requested – aleclarson Aug 25 '14 at 15:03
  • Do I have to add it to every override init method? – newme Apr 07 '15 at 09:00
  • Who said `+initialize` is called only once in Objective C? It can be called various times: http://stackoverflow.com/questions/14110396/initialize-called-more-than-once Careful! – nacho4d May 12 '15 at 10:32
  • 2
    This is different from `+initialize` in Objective-C. `+initialize` in Objective-C is called the first time the *class* is *messaged*. This is called the first time an instance of the class is created. – newacct May 15 '15 at 18:52
  • @newacct This is true and I should have noted so. Thanks! – aleclarson May 16 '15 at 04:56
  • your `dispatch_once` approach does not work and `// Do stuff` is called every time the class is initialised, which is not the same how `+initialize` works. – Yevhen Dubinin Mar 19 '16 at 12:25
  • Do you need to call `super.initialize()` in `override class func initialize()` if the object inherits from `NSObject`? – JAL Mar 15 '17 at 18:45
  • @JAL: No, `initialize` is automatically sent to all superclasses before it is sent to the subclass. – Martin R Mar 15 '17 at 19:38
  • 7
    As of Swift 3.1 (Xcode 8.3), using `override class func initialize()` now generates a warning including "... which is not guaranteed to be invoked by Swift and will be disallowed in future versions". See: http://stackoverflow.com/questions/42824541/swift-3-1-deprecates-initialize-how-can-i-achieve-the-same-thing – Scott Corscadden Mar 30 '17 at 17:05
  • 1
    The normal & best way to make sure your `+initialize`-ation code is only run once for your class is to do what the official documentation recommends ( https://developer.apple.com/reference/objectivec/nsobject/1418639-initialize?language=objc )— __check that the current `self` is your class__, e.g. `+ (void)initialize { if (self == [MyView self]) { /* ... do the initialization ... */ } }` – Slipp D. Thompson May 19 '17 at 22:50
  • 8
    As of Swift 4 using `override class func initialize()` is an error. – ThomasW Feb 19 '18 at 08:34
58

There is no type initializer in Swift.

“Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time.”

Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.


You could use a type property which default value is a closure. So the code in the closure would be executed when the type property (or class variable) is set.

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

But class stored properties not yet supported (tested in Xcode 8).

One answer is to use static, it is the same as class final.

Good link for that is

Setting a Default Property Value with a Closure or Function

Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.


Code example:

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

Prints

start

setting default property value with a closure

So it is lazy evaluated.

Community
  • 1
  • 1
Binarian
  • 12,296
  • 8
  • 53
  • 84
  • You can sort of do this with a `static` property now... `static var someProperty = { return SomeType }()` ... but the problem is that that code is run lazily, the first time the property is referenced. https://developer.apple.com/swift/blog/?id=7 – Aneel Mar 16 '16 at 02:57
  • @iGodric "So the code in the closure would be executed when the type property (or class variable) is set". Can you clarify this. When exactly will the closure be executed? – sleep May 30 '16 at 07:19
  • 1
    @JarrodSmith I extended the answer to show an example. – Binarian May 30 '16 at 07:41
  • Why do we need a closure? It works without the closure no? Or is it because it's due to being a static var? – Van Du Tran May 24 '17 at 15:36
  • @VanDuTran If you want to use the class initializer when the class is created, that would just be the app load method. But the interesting behavior of +initialize is, that it is lazy (only loads the values when class is first used). For lazy I used the closure. – Binarian May 24 '17 at 16:26
7

For @objc classes, class func initialize() definitely works, since +initialize is implemented by the Objective-C runtime. But for "native" Swift classes, you'll have to see the other answers.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • I have a swift class that is inherits from `NSObject`. I have notices that my class's `+initialize` (actually, `override class func initialize()`) is not called until I call a method of the class. Accessing properties does not seem to trigger it. Perhaps this is because swift properties are not methods (getters and setters) under the hood, like in Objective-C? – Nicolas Miari Jan 08 '16 at 08:44
  • 1
    @NicolasMiari: Are you talking about instance or class methods? Instance or class properties? In Objective-C, a class method needs to be called in order to create an instance, so I would think that the act of creating an instance should trigger it, before you even use an instance method or instance property. If you are talking about class properties, Objective-C doesn't have class properties, so Swift's class properties might not be compatible with the ObjC runtime since there's no need to be compatible (just guessing, I haven't checked the source). – newacct Jan 08 '16 at 20:06
  • 1
    @NicolasMiari: The documentation for `+[NSObject initialize]` does say it is called before the class "is sent its first message", so there's no reason to think that class properties would trigger it anyway. – newacct Jan 08 '16 at 20:09
  • Class properties, I see... Those indeed have no counterpart in Objective-C, but you _could_ fake them by implementing two class methods with signatures compatible with the (synthesized) getter and setter of an instance property. For example, +(void)setColor:(UIColor*) newColor; and +(UIColor*) color; I guess swift class properties are an entirely different beast. – Nicolas Miari Jan 11 '16 at 06:00
7

You can use stored type properties instead of initialize method.

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

But since stored types properties are actually lazily initialized on their first access, you will need refer them somewhere. You can do this with ordinary stored property:

class SomeClass: {
  private static let initializer: Void = {
    //some initialization
  }()
  private let initializer: Void = SomeClass.initializer
}
Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
  • Would be nice, but is it swift? As far as I know in swift 'let' declarations cannot be computed properties. – Aquajet Jul 12 '19 at 12:07
  • 3
    @Aquajet: `initializer` is not a computed property since the assigned closure is actually executed. Notice the equal sign and the parentheses at the end. As mentioned in the answer static properties are evaluated lazily so the closure is not executed immediately when loading the class. Instead it is triggered via referencing the property somewhere else in the code. – Lukáš Kubánek Jul 29 '19 at 09:34
3

@aleclarson nailed it, but as of recent Swift 4 you cannot directly override initialize. You still can achieve it with Objective-C and categories for classes inheriting from NSObject with a class / static swiftyInitialize method, which gets invoked from Objective-C in MyClass.m, which you include in compile sources alongside 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

If your class cannot inherit from NSObject and using +load instead of +initialize is a suitable fit, you can do something like this:

# 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

There are couple of gotchas, especially when using this approach in static libraries, check out the complete post on Medium for details! ✌️

Ian Bytchek
  • 8,804
  • 6
  • 46
  • 72
-5

I can't find any valid use case to have something like +[initialize] in Swift. Maybe this explains way it does not exist

Why do we need +[initialize] in ObjC?

To initialize some global variable

static NSArray *array;

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

which in Swift

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

To do some hack

+ (void)initialize {
    swizzle_methodImplementation()
}

which is not supported by Swift (I can't figure out how to do it for pure Swift class/struct/enum)

Community
  • 1
  • 1
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • 19
    A valid use case would be for registering NSUserDefaults – Rick Dec 19 '14 at 16:30
  • 5
    another valid case is to exposeBinding – AJ Venturella Jan 11 '15 at 00:07
  • 10
    Another valid use case it to register the class (self) as part of group. For example CoreImage programming guide suggests to use `+[[CIFilter registerFilterName:constructor:classAttributes:]` in `+initialize` of subclasses of `CIFilter`. – nacho4d Jul 07 '15 at 02:53
  • 4
    Another valid use case is to seed a random function once, for a class which uses random numbers. – Chris Hatton Nov 11 '15 at 06:30