【问题标题】:Swift- variable not initialized before use (but it's not used)Swift-变量在使用前未初始化(但未使用)
【发布时间】:2015-11-03 17:39:52
【问题描述】:

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

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 编译器接受这个完全有效的代码?

这与提前从初始化程序返回无关。回调是异步执行的——它被存储并在以后使用。

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

【问题讨论】:

  • 请问什么是“类型”?
  • 没关系。在构造函数中接受该签名的回调的任何类型。]
  • 你说得对,我删除了它。

标签: swift closures initializer


【解决方案1】:

这行得通:

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 的哪一部分没有被初始化。

附言

显然 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。两个例子都编译没有错误。

【讨论】:

  • 可能是,但绝对不是,因为我在 C 之前不到五分钟就写了 Type。哦,即使是,self 访问的部分已经初始化了。
  • 您正在使用的 self 部分已初始化。但它必须完全初始化才能通过,但事实并非如此。
  • 原则上你可以很容易地引用未初始化或部分初始化的东西。这只是一个问题,你必须在脸上打败 Swift 编译器,直到它放弃并停止抱怨无脑。
  • 是的,如果你使用 var 和 optionals。但是你使用 let 没有可选的,它必须在 self 可以在其他地方使用之前被初始化。
【解决方案2】:

首先,重要的是要解释为什么这不是完全有效的代码,并且根本不清楚 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 编译器可能会证明在某些特定情况下不会发生这种情况,但对于大多数非平凡的代码来说,它可能会遇到停止问题。无论如何,当前的 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() {}
}

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

【讨论】:

    【解决方案3】:

    有趣。

    如果您避免在闭包内引用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
        }
    }
    

    重要提示:如果闭包是异步的,这将起作用。

    使用 Xcode (Playground) 6.4 + Swift 1.2 测试

    希望这会有所帮助。

    【讨论】:

    • 闭包确实是完全异步的。
    【解决方案4】:

    正如 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 的临时变量,此错误消息具有误导性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-24
      • 2021-09-23
      • 1970-01-01
      • 2017-05-06
      • 2016-11-06
      • 2018-10-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多