Захват ссылки на структуру в замыкании не допускает возникновения мутаций

Я пытаюсь понять, могу ли я использовать структуры для моей модели, и пытался это сделать. Когда я звоню vm.testClosure(), это не меняет значение x и я не уверен почему.

struct Model
{
    var x = 10.0
}


var m = Model()

class ViewModel
{
    let testClosure:() -> ()

    init(inout model: Model)
    {
        testClosure =
        {
            () -> () in
            model.x = 30.5
        }
    }
}



var vm = ViewModel(model:&m)

m.x

vm.testClosure()

m.x

3 ответа

inout Аргумент не является ссылкой на тип значения - это просто теневая копия этого типа значения, которая записывается обратно в значение вызывающей стороны, когда функция возвращает.

В вашем коде происходит то, что ваш inout переменная экранирует время жизни функции (будучи захваченной в замыкании, которое затем сохраняется) - это означает, что любые изменения в inout Переменная после того, как функция вернулась, никогда не будет отражена вне этого замыкания.

Из-за этого распространенного заблуждения о inout аргументы, было предложение Swift Evolution только для разрешения inout аргументы, которые будут захвачены @noescape затворы. Начиная с Swift 3, ваш текущий код больше не будет компилироваться.

Если вам действительно нужно передавать ссылки в вашем коде - тогда вы должны использовать ссылочные типы (сделайте ваши Model class). Хотя я подозреваю, что вы, вероятно, сможете реорганизовать свою логику, чтобы избежать передачи ссылок в первую очередь (однако, не видя ваш реальный код, советовать невозможно).

(Изменить: После публикации этого ответа, inout параметры теперь могут быть скомпилированы как передача по ссылке, что можно увидеть, посмотрев на излучаемый SIL или IR. Однако вы не можете рассматривать их как таковые из-за того, что нет никакой гарантии, что значение вызывающей стороны останется действительным после вызова функции.)

Экземпляры замыкания получат свою собственную независимую копию захваченного значения, которое оно и только оно может изменить. Значение фиксируется во время выполнения закрытия. Давайте посмотрим ваш слегка измененный код

struct Model
{
    var x = 10.0

    mutating func modifyX(newValue: Double) {
        let this = self
        let model = m
        x = newValue
// put breakpoint here
//(lldb) po model
//▿ Model
//  - x : 30.0
//
//(lldb) po self
//▿ Model
//  - x : 301.0
//
//(lldb) po this
//▿ Model
//  - x : 30.0            
    }
}

var m = Model()

class ViewModel
{

    let testClosure:() -> ()

    init(inout model: Model)
    {
        model.x = 50
        testClosure =
            { () -> () in
                model.modifyX(301)
        }
        model.x = 30
    }
}

let mx = m.x

vm.testClosure()

let mx2 = m.x

Вот что Apple говорит об этом.

Классы и структуры

Тип значения - это тип, который копируется, когда он присваивается переменной или константе или когда он передается в функцию. [...] Все структуры и перечисления являются типами значений в Swift

методы

Структуры и перечисления являются типами значений. По умолчанию свойства типа значения не могут быть изменены из его методов экземпляра.

Однако, если вам нужно изменить свойства вашей структуры или перечисления в конкретном методе, вы можете выбрать мутацию поведения для этого метода. Затем метод может видоизменять (то есть изменять) свои свойства изнутри метода, и любые изменения, которые он вносит, записываются обратно в исходную структуру после завершения метода. Метод также может назначить совершенно новый экземпляр своему неявному свойству self, и этот новый экземпляр заменит существующий, когда метод завершится.

Взято отсюда

Другие вопросы по тегам