Swift变量在使用前未初始化(但未使用)

时间:2015-08-11 13:09:59

标签: swift closures initializer

目前我有一些像这样的快速代码:

class C {
   let type: Type;
   var num = 0;
   init() {
       self.type = Type({ (num: Int) -> Void in
           self.num = num;
       });
   }
}

Swift编译器拒绝允许它,说我在初始化之前引用了self.type,尽管这显然是完全不真实的。此外,我不能使用其他问题/答案中找到的解决方法,因为type不是可选的,并且它是不可变的,因此无法首先用nil无意义地初始化它。

如何让Swift编译器接受这个完全有效的代码?

这与早期从初始化程序返回无关。回调是异步执行的 - 它被存储然后再使用。

我还有一些let在此之后被初始化。我必须将它们 all 变成可变选项,即使它们不是可选的也不能被变异。

4 个答案:

答案 0 :(得分:10)

这有效:

class C {
    var type: Type?;
    var num = 0;
    init() {
        self.type = Type({ (num: Int) -> Void in
            self.num = num;
        });
    }
}
我猜你知道的。但是你想知道你的版本无法正常工作的原因。

现在是棘手的部分:为行

self.num = num;

工作,编译器必须将self传递给闭包内部。闭包可能并且可能在Type的构造函数内执行。

这就像你写过

一样
self.type = Type({ (self: C, num: Int) -> Void in
    self.num = num    
});

这在语法上是错误的,但解释了编译器编译代码时必须做的事情。

要将此必需的self实例传递给Type的构造函数,必须初始化self。但是self没有被初始化,因为你仍然在构造函数中。

当您尝试将self传递给Type的构造函数时,编译器会告诉您self的哪一部分未初始化。

<强> P.S。

很明显,Type在您的代码中知道num。 如果你想在C中使用let而不是var,你可以做...

class Type {
    let num: Int
    init () {
        num = 3
    }
}
class C {
    let type: Type;
    var num = 0;
    init() {
        self.type = Type();
        num = type.num
    }
}

甚至

class C {
    let type: Type;
    var num: Int {
        return type.num
    }
    init() {
        self.type = Type();
    }
}

取决于您是否要更改num。这两个例子都编译没有错误。

答案 1 :(得分:4)

首先,重要的是要解释为什么这不是完全有效的代码,并且根本不清楚self.type在初始化之前是否未被使用。请考虑以下代码扩展:

struct A {
    init(_ f: (Int) -> Void) { f(1) }
}

class C {
    let type: A
    var num = 0 {
        didSet { print(type) }
    }
    init() {
        self.type = A({ (num: Int) -> Void in
            self.num = num
        })
    }
}

如果您完成逻辑,则会注意到self.type在初始化之前通过print访问。斯威夫特目前无法证明这种情况不会发生,所以不允许这样做。 (理论上的Swift编译器可能会证明在某些特定情况下不会发生这种情况,但对于大多数非平凡的代码,它可能会遇到暂停问题。无论如何,当前的Swift编译器不够强大,无法实现这一点。证明,这是一个非平凡的证明。)

一种解决方案虽然有些不尽如人意,但却使用隐式解包的选项:

private(set) var type: A! = nil

除声明外,代码的每个其他部分都是相同的。您不必将其视为可选项。实际上,这只会关闭此变量的“初始化前使用”检查。它还令人遗憾地使它在当前文件中可设置,但确实使其对其他所有人都不可变。

这是我最常使用的技术,虽然我经常尝试重新设计系统,这样就不需要这种封闭(并不总是可行,但我经常试着试试)。它并不漂亮,但它是一致的并且是丑陋的。

另一种可以在某些情况下起作用的技术是懒惰:

class C {
    lazy var type: A = {
        A({ (num: Int) -> Void in self.num = num })}()
    var num = 0
    init() {}
}

有时它起作用,有时它不起作用。在你的情况下它可能。当它工作时,它非常好,因为它使属性真正不可变,而不仅仅是公开不可变,当然因为它不需要!

答案 2 :(得分:2)

有趣。

如果你避免在闭包中引用self ,看起来错误就会消失。

如果回调是同步的,您可以按照以下步骤更改您的代码:

class C {
    let type: Type
    var num = 0
    init() {
        var numTemp = 0 // create a temporary local var
        let initialType = Type({ (num: Int) -> () in
            numTemp = num // avoid self in the closure
        });
        self.type = initialType
        self.num = numTemp
    }
}
  

重要提示:如果闭包是异步,这将 NOT

使用Xcode(Playground)6.4 + Swift 1.2

进行测试

希望这有帮助。

答案 3 :(得分:0)

正如appzYourLife所说,num的临时变量就足够了:

class Type{
    var y: (Int)->Void
    init(y2:((Int)->Void)){
        self.y = y2
    }
}

class C {
    let type: Type
    var num: Int = 0
    init() {
        var num2 = 0 
        self.type = Type(y2: { (num3: Int) -> () in
            num2 = num3
        });
        self.num = num2
    }
}

但是,需要type的临时变量,此错误消息具有误导性。