Подтвердить что ты не робот

Как выполнить код один раз и только один раз в Swift?

Ответы, которые я видел до сих пор (1, 2, 3) рекомендуем использовать GCD dispatch_once, таким образом:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

Выход:

This is printed only on the first call to test()
This is printed for each call to test()

Но подождите минуту. token - переменная, поэтому я мог бы легко сделать это:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

token = 0

test()

Выход:

This is printed only on the first call to test()
This is printed for each call to test()
This is printed only on the first call to test()
This is printed for each call to test()

Итак, dispatch_once бесполезно, если я могу изменить значение token! И превращение token в константу не так просто, как нужно для типа UnsafeMutablePointer<dispatch_once_t>.

Итак, мы должны отказаться от dispatch_once в Swift? Есть ли более безопасный способ выполнить код только один раз?

4b9b3361

Ответ 1

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

/*
run like:

    swift once.swift
    swift once.swift run

to see both cases
*/
class Once {
    static let run: Void = {
        print("Behold! \(__FUNCTION__) runs!")
        return ()
    }()
}

if Process.arguments.indexOf("run") != nil {
    let _ = Once.run
    let _ = Once.run
    print("Called twice, but only printed \"Behold\" once, as desired.")
} else {
    print("Note how it run lazily, so you won't see the \"Behold\" text now.")
}

Пример выполнения:

~/W/WhenDoesStaticDefaultRun> swift once.swift
Note how it run lazily, so you won't see the "Behold" text now.
~/W/WhenDoesStaticDefaultRun> swift once.swift run
Behold! Once runs!
Called twice, but only printed "Behold" once, as desired.

Ответ 2

Человек пошел к доктору и сказал: "Доктор, больно, когда я топчусь ногой". Врач ответил: "Так прекрати это делать".

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

Если вы обеспокоены тем, что кто-то может случайно reset токен, вы можете завершить его в методе и сделать его столь же очевидным, насколько это может быть последствиями. Что-то вроде следующего будет охватывать токен для метода и не позволяет кому-либо изменять его без серьезных усилий:

func willRunOnce() -> () {
    struct TokenContainer {
        static var token : dispatch_once_t = 0
    }

    dispatch_once(&TokenContainer.token) {
        print("This is printed only on the first call")
    }
}

Ответ 3

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

Существует несколько вариантов. Как уже упоминалось, вы можете инициализировать статическое свойство внутри типа с закрытием.

Однако самый простой способ - определить глобальную переменную (или константу) и инициализировать ее закрытием, а затем ссылаться на эту переменную в любом месте, где требуется инициализационный код:

let resourceInit : Void = {
  print("doing once...")
  // do something once
}()

Другой вариант - обернуть тип внутри функции, чтобы он лучше читал при вызове. Например:

func doOnce() {
    struct Resource {
        static var resourceInit : Void = {
            print("doing something once...")
        }()
    }

    let _ = Resource.resourceInit
}

Вы можете делать вариации по мере необходимости. Например, вместо того, чтобы использовать внутренний тип функции, вы можете использовать конфиденциальную глобальную и внутреннюю или публичную функцию по мере необходимости.

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