Swift中用于类的“初始化”类方法是什么?

77
我正在寻找类似于Objective-C的+(void)initialize类方法的行为,该方法在类初始化时调用一次,之后再也不会调用。
class闭包中使用一个简单的class init () {}会非常简洁!当我们使用"class var"而不是"static var在结构体闭包"时,这一切都将非常匹配!
6个回答

63

如果您拥有一个Objective-C类,最简单的方法是覆盖+initialize。但是,请确保您的类的子类也重写了+initialize,否则您的类的+initialize可能会被调用多次!如果需要,可以使用dispatch_once()(下面会提到)来防止多次调用。

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

如果你有一个Swift类,最好的选择是在init()语句中使用dispatch_once()函数。

private var once = dispatch_once_t()

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

这种解决方案与+initialize(该方法在Objective-C类第一次接收消息时调用)不同,因此并不是对问题的真正答案。但在我看来,它已经足够好了。


2
@BryanChen 为什么?在 Objective C 中,“initialize” 只有在第一次调用时才会被执行,这一点有变化吗? - sapi
2
这与 Objective-C 中的 +initialize 不同。在 Objective-C 中,+initialize 是在第一次向 发送消息时调用的。而这是在创建类的实例的第一次调用。 - newacct
7
从Swift 3.1(Xcode 8.3)开始,使用override class func initialize()会产生警告,包括“...不能保证由Swift调用,并将在未来的版本中被禁止”。参考:https://dev59.com/r1gQ5IYBdhLWcg3wZDGd - Scott Corscadden
1
确保您的 +initialize 代码仅对类运行一次的正常和最佳方法是遵循官方文档建议(https://developer.apple.com/reference/objectivec/nsobject/1418639-initialize?language=objc) - 检查当前的 self 是否为您的类,例如 + (void)initialize { if (self == [MyView self]) { /* ... do the initialization ... */ } } - Slipp D. Thompson
8
从Swift 4开始,使用override class func initialize()是一个错误。 - ThomasW
显示剩余10条评论

58

在 Swift 中没有类型初始化器(type initializer)

与存储的实例属性不同,您必须始终为存储的类型属性提供默认值。这是因为类型本身没有初始化器,可以在初始化时为存储的类型属性分配值。

摘自:Apple Inc. “The Swift Programming Language.” iBooks.


您可以使用默认值为闭包的类型属性(type property)。当设置该类型属性(或类变量)时,将执行闭包中的代码。

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

但是类存储属性目前还不支持(在Xcode 8中测试过)。

一个解决方案是使用 static,它与 class final 相同。

这个链接很好:

使用闭包或函数设置默认属性值

节选自:Apple Inc. “The Swift Programming Language.” 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 }()。但问题是这段代码会被惰性地运行,也就是说,只有当第一次引用该属性时才会执行。https://developer.apple.com/swift/blog/?id=7 - Aneel
@iGodric “因此,当设置类型属性(或类变量)时,闭包中的代码将被执行”。您能澄清一下吗?闭包将在什么时候被执行? - Jarrod Smith
1
@JarrodSmith 我扩展了答案以显示一个示例。 - Binarian
为什么我们需要闭包?没有闭包也能工作,是因为它是静态变量吗? - Van Du Tran
如果您想在创建类时使用类初始化程序,那么这只是应用程序加载方法。但是+initialize的有趣行为是它是懒惰的(仅在第一次使用类时加载值)。对于懒惰,我使用了闭包。 - Binarian

7

对于使用 @objc 的类,class func initialize() 绝对可行,因为 +initialize 是由 Objective-C 运行时实现的。但是对于“原生”的 Swift 类,你需要查看其他答案。


我有一个继承自NSObject的Swift类。我注意到我的类的+initialize(实际上是override class func initialize())直到我调用类的方法之前都没有被调用。访问属性似乎不会触发它。也许这是因为在底层,Swift属性不像Objective-C中的方法(getter和setter)那样? - Nicolas Miari
1
@NicolasMiari:你是在讨论实例方法还是类方法?实例属性还是类属性?在Objective-C中,需要调用类方法才能创建一个实例,因此我认为创建实例的行为应该会触发它,在使用实例方法或实例属性之前。如果你在谈论类属性,Objective-C没有类属性,所以Swift的类属性可能与ObjC运行时不兼容,因为没有必要兼容(只是猜测,我没有检查过源代码)。 - newacct
1
@NicolasMiari:+[NSObject initialize] 的文档确实说它在类“收到第一条消息”之前被调用,所以没有理由认为类属性会触发它。 - newacct
类属性,我明白了...在Objective-C中确实没有对应的属性,但是你可以通过实现两个类方法来模拟它们,这两个方法的签名与实例属性的getter和setter兼容。例如,+(void)setColor:(UIColor*) newColor; 和 +(UIColor*) color; 我猜Swift的类属性是完全不同的东西。 - Nicolas Miari

7
你可以使用存储类型属性来替代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
}

很好,但它是Swift吗?据我所知,在Swift中,“let”声明不能是计算属性。 - Aquajet
3
@Aquajet:“initializer”不是一个计算属性,因为分配的闭包实际上被执行了。请注意等号和末尾的括号。正如答案中所提到的,静态属性是惰性求值的,因此闭包不会在加载类时立即执行。相反,只有当代码中的其他地方引用该属性时,它才会被触发。 - Lukáš Kubánek

3

@aleclarson的回答很好,但在最近的Swift 4中,您无法直接覆盖initialize方法。但是,对于继承自NSObject的类,您仍然可以通过Objective-C和分类实现一个带有类/静态swiftyInitialize方法的方案,该方法会在MyClass.m文件中被Objective-C调用,并且您需要将其与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 上的完整文章获取详细信息!✌️


-5

我找不到在Swift中使用+[initialize]的任何有效用例。也许这就是它不存在的原因。

为什么我们需要在ObjC中使用+[initialize]

初始化一些全局变量。

static NSArray *array;

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

在Swift中

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

进行一些黑客攻击

+ (void)initialize {
    swizzle_methodImplementation()
}

这在Swift中不受支持(我无法弄清如何对纯Swift类/结构体/枚举执行此操作)


19
一个有效的使用案例是用于注册NSUserDefaults。 - Rick
5
另一个有效的情况是暴露绑定。 - AJ Venturella
10
另一个有效的用例是将类(self)注册为组的一部分。例如,CoreImage编程指南建议在CIFilter子类的+initialize方法中使用 +[[CIFilter registerFilterName:constructor:classAttributes:] - nacho4d
4
另一个有效的用例是为使用随机数的类种子化一次随机函数。 - Chris Hatton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接