Swift中初始化方法的分段

Segmentation of Initialization Method in Swift

作为一名 OS X/iOS 开发人员多年以来,我对 Apples Swift 语言产生了浓厚的兴趣。为了保持我的初始化方法简洁明了,我将初始化的各个部分隔离到各个方法中。特别是在处理长初始化时——就像经常在 SpriteKit SKScene subclass 中发现的那样,这有助于我保持一个干净简单的结构。这是它的样子:

典型 Objective-C Class:

@interface SomeScene ()

@property (strong, nonatomic) SKSpriteNode* backgroundNode;
@property (strong, nonatomic) SKNode* hudNode;
@property (strong, nonatomic) SKSpriteNode* playerNode;

@end

@implementation SomeScene

-(id)init {

    if (self = [super init]) {

        [self setupBackgroundNode];
        [self setupHUD];
        [self setupPlayer];
    }

    return self;
}

-(void)setupBackgroundNode {

    self.backgroundNode = [SKSpriteNode new];
    self.backgroundNode.position = CGPointMake(0., 0.);
    /* Some More Options */
    [self addChild:self.backgroundNode];
}

-(void)setupHUD {

    self.hudNode = [SKSpriteNode new];
    self.hudNode.position = CGPointMake(0., 0.);
    /* Some More Options */
    [self addChild:self.hudNode];
}

-(void)setupPlayer {

    self.playerNode = [SKSpriteNode new];
    self.playerNode.position = CGPointMake(0., 0.);
    /* Some More Options */
    [self addChild:self.playerNode];
}

@end

这只是一个演示,向您展示我如何将初始化的不同部分封装到各个方法中,然后在调用 -(id)init 时调用这些方法。我在我的任何项目中都使用这种模式。

现在通过学习 Swift 我了解了基本概念和可选值,并尝试适应略有不同的初始化类型。为了保持上面显示的我自己的 Objective-C 模式,Swift 代码将如下所示:

Swift中的相同class:

class SomeScene: SKScene {

    var backgroundNode: SKSpriteNode?
    var playerNode: SKSpriteNode?
    var hudNode: SKSpriteNode?

    override convenience init() {

        self.init()

        self.setupBackground()
        self.setupPlayer()
        self.setupHUD()
    }

    func setupBackground() {

        backgroundNode = SKSpriteNode(/*Some Initialisation Parameters*/)
        backgroundNode?.position = CGPointMake(0.0, 0.0)
        /* even more lines */
        self.addChild(backgroundNode!);
    }

    func setupPlayer() {

        playerNode = SKSpriteNode(/*Some Initialisation Parameters*/)
        playerNode?.position = CGPointMake(0.0, 0.0)
        /* even more lines */
        self.addChild(playerNode!);
    }

    func setupHUD() {

        hudNode = SKSpriteNode(/*Some Initialisation Parameters*/)
        hudNode?.position = CGPointMake(0.0, 0.0)
        /* even more lines */
        self.addChild(hudNode!);
    }

}

这确实像预期的那样工作,但我问自己这是否是使用可选值的正确方法。恐怕我滥用它们只是为了将 Objective-C 样式转换为 Swift。我真的必须在每个 属性 之后都写上所有问号吗?这是什么东西还是更像是一种切换编译器错误的方法 属性 "might be nil" - 这就是 Apples 文档对 ?-suffix 的描述。

我提出这个问题是因为以这种方式使用 swift 感觉有点奇怪。有没有更好的方法来封装 init 方法的代码部分/块,以防止它不断增长和失去焦点?

Do I really have to write all that question marks after every property?

基本上是的。 Swift 对初始化程序有严格的规定。您必须在初始化结束时初始化所有实例属性。如果要将实例属性的初始化移出初始化程序,则必须以其他方式为这些实例属性提供默认初始值。您可以使用等号和显式值来做到这一点,或者您可以使用 Optional 来做到这一点,因为它会自动分配 nil.

这就是为什么出口 (@IBOutlet) 通常是可选的。我们知道它们不会在初始化期间被初始化,所以我们需要一个临时值,直到它们 在 nib 加载时被初始化。

另外,通常的做法是对任何实例使用 Optionals 属性,只有在实例本身完全初始化之后才能对其进行初始化。例如,您可能需要执行一些耗时的操作,而您不想在初始化程序中执行这些操作。

但是,还有另一种方法可能适用于您的某些实例属性:将它们标记为 lazy 并提供默认初始化程序,即定义和调用函数。例如:

lazy var prog : UIProgressView = {
    let p = UIProgressView(progressViewStyle: .Default)
    p.alpha = 0.7
    p.trackTintColor = UIColor.clearColor()
    p.progressTintColor = UIColor.blackColor()
    p.frame = CGRectMake(0, 0, self.view.bounds.size.width, 20)
    p.progress = 1.0
    return p
}()

那有封装的好处。我们提供了一个默认值,即从定义和调用函数返回的东西,所以我们不必在初始化程序中初始化。但是编译器允许我们推迟 运行 定义和调用函数,直到其他代码第一次实际访问该实例 属性。这种模式对你来说可能更有意义。

我还发现自己经常使用计算机属性。在许多情况下,这是另一种很好的替代模式。

所以基本上是的,你所做的是正确的,但是 Swift 有一些其他的模式通常可以使它变得不必要,当你来自 Objective-C 时你会想要也对这些模式感到满意。