Быстрые закрытия: должны ли списки захвата быть исчерпывающими? - PullRequest
0 голосов
/ 20 февраля 2019

Предположим, у меня есть класс Swift, подобный этому:

@objc final MyClass : NSObject
{
    let classPropertyString = "A class property"


    func doStuff() 
    {
        let localString = "An object local to this function"

        DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in
             // Do things with 'classPropertyString' and 'localString'
        }
    }
}

Мой вопрос: когда я пишу список захвата, я несу ответственность за УДОВОЛЬСТВЕННО перечисление всех вещей, к которым я отношусьхотите, чтобы закрытие содержало сильную ссылку?

Другими словами, если я опущу localString из своего списка захвата (как я это сделал здесь), будет ли закрытие автоматически захватывать сильную ссылку на него илия в плохое время?

1 Ответ

0 голосов
/ 20 февраля 2019

Есть несколько незначительных изысков в вашем вопросе, из-за которых сложно дать четкий ответ, но я думаю, что понимаю основную проблему, и короткий ответ - «нет».Но ваш пример невозможен, поэтому ответ «это невозможно».И если бы это было возможно, не было бы сильной ссылки (да и не было бы необходимости в ней), поэтому вопрос все равно был бы вроде «это невозможно».Тем не менее, давайте пройдемся по тому, что здесь происходит.

Во-первых, closure не может ссылаться на localString, если оно не переназначено каким-либо образом в комментарии внутри doStuff().closure назначается на уровне, где localString находится вне области видимости.Замыкания могут захватывать только те переменные, которые находятся в области видимости, когда они назначены, а не когда они вызываются.Но вернемся к исходной версии этого вопроса, прежде чем он был отредактирован.В этой версии был описанный вами случай:

@objc final myClass : NSObject
{
    let classPropertyString = "A class property"


    func doStuff() 
    {
        let localString = "An object local to this function"

        DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in // (1)
             // Do things with 'classPropertyString' and 'localString'
        }
        // (2)
    }
}

Здесь нет проблем.classPropertyString копируется в замыкание, избегая любых удерживающих петель.На localString ссылается закрытие, и поэтому оно сохраняется до тех пор, пока существует закрытие.

Поскольку вы перечислили classPropertyString в списке захвата, оно оценивается в точке (1) и копируется в закрытие,Поскольку вы неявно захватили localString, он рассматривается как ссылка.См. Списки захвата в Справочнике по языку программирования Swift, где приведены отличные примеры того, как это работает в разных случаях.

Ни в коем случае (*) Swift не разрешит базовое хранилище для чего-то, что вы 'использовать в закрытом состоянии, чтобы скрыться за вашей спиной.Вот почему типичная проблема - чрезмерные удержания (утечки памяти), а не свисающие ссылки (сбои).

(*) «Ни в коем случае» здесь ложь.Есть несколько способов, которыми Swift позволит это, но почти все они включают «Небезопасный», который является вашим предупреждением об этом.Основным исключением является unowned и, конечно, все, что связано с ! типами.А Swift, как правило, не ориентирован на многопоточность, поэтому вы должны быть осторожны с этим ...

Последний комментарий о безопасности потоков - это место, где тонкие различия между неявным и явным захватом действительно могут иметь значение.Рассмотрим этот случай, когда вы изменяете неявно захваченное значение в двух очередях:

func doStuff() -> String
{
    var localString = "An object local to this function"

    DispatchQueue.global(qos: .userInitiated).async {
         localString = "something else"
         callFunction(localString)
    }

    localString = "even more changes"
    return localString
}

Что происходит в этом случае?Боже мой, никогда не делай этого.Я верю это неопределенное поведение и что localString может быть что угодно , включая поврежденную память, по крайней мере в наиболее общем случае (это может быть определенное поведение для вызова .async; я неконечно).Но не делайте этого.

Но для ваших обычных случаев нет причин явно захватывать локальные переменные.(Иногда мне хотелось бы, чтобы Свифт прошел путь C ++ и сказал, что было требуется, но это не так.)

Хорошо, еще один неявный и явный отличия, которые могут привести к тому, какони работают.Рассмотрим замыкание с сохранением состояния, подобное этому (я строю это довольно часто):

func incrementor() -> () -> Int {
    var n = 0
    return {
        n += 1
        return n
    }
}

let inc = incrementor()
inc()  // 1
inc()  // 2
inc()  // 3

let inc2 = incrementor()
inc2() // 1

Посмотрите, как локальная переменная n захватывается замыканием и может быть изменена после выхода из области видимости.И посмотрите, как у inc2 есть собственная версия этой локальной переменной.Теперь попробуйте это с явным захватом.

func incrementor() -> () -> Int {
    var n = 0
    return { [n] in // <---- add [n]
        n += 1      // Left side of mutating operator isn't mutable: 'n' is an immutable capture
        return n
    }
}

Явные захваты являются копиями, и они неизменны.Неявные перехваты являются ссылками и поэтому имеют ту же изменчивость, что и вещи, на которые они ссылаются.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...