Swift - переменная, не инициализированная перед использованием (но она не используется)

В настоящее время у меня есть быстрый код:

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, которые инициализируются после этого. Я должен был бы превратить их все в изменяемые опции, даже если они не являются необязательными и не могут быть изменены.

Ответ 1

Это работает:

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

Я предполагаю, что вы это знали. Но вы хотите знать, почему ваша версия не работает.

Теперь для сложной части: для строки

self.num = num;

компилятор должен передать себя внутри закрытия. Закрытие может быть и возможно выполнено внутри конструктора Type.

Это как если бы вы написали

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

который является синтаксически неправильным, но объясняет, что компилятор должен сделать, чтобы скомпилировать ваш код.

Чтобы передать этот необходимый экземпляр объекта self в конструктор Type, self должен быть инициализирован. Но само не инициализируется, потому что вы все еще в конструкторе.

Компилятор сообщает вам, какая часть self не инициализируется, когда вы пытаетесь передать self конструктору Type.

P.S.

Очевидно, что тип знает число в вашем коде. Если вы хотите использовать let в C вместо 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();
    }
}

в зависимости от того, хотите ли вы изменить число или нет. Оба примера скомпилированы без ошибок.

Ответ 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 недостаточно мощным, чтобы сделать это доказательство, и это нетривиальное доказательство.)

Одним из решений, хотя и несколько неудовлетворительным, является использование неявно развернутых опций:

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 (игровая площадка) 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, это сообщение об ошибке вводит в заблуждение.