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

Dispatch_once после изменения API Swift 3 GCD

Каков новый синтаксис для dispatch_once в Swift после изменений, внесенных в языковой версии 3? Старая версия была следующей.

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
    }
}

Эти являются изменениями в libdispatch, которые были сделаны.

4b9b3361

Ответ 1

Из doc:

Отправка
Бесплатная функция dispatch_once больше не доступна в Swift. В Swift вы можете использовать лениво инициализированные глобальные или статические свойства и получить те же гарантии безопасности потоков и звонков как указано dispatch_once. Пример:

let myGlobal = { … global contains initialization in a call to a closure … }()
_ = myGlobal  // using myGlobal will invoke the initialization code only the first time it is used.

Ответ 2

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

Вот реализация стиля dispatch_once в стиле Swift 3:

public extension DispatchQueue {

    private static var _onceTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String, block:@noescape(Void)->Void) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTracker.contains(token) {
            return
        }

        _onceTracker.append(token)
        block()
    }
}

Вот пример использования:

DispatchQueue.once(token: "com.vectorform.test") {
    print( "Do This Once!" )
}

или используя UUID

private let _onceToken = NSUUID().uuidString

DispatchQueue.once(token: _onceToken) {
    print( "Do This Once!" )
}

Как мы сейчас находимся в процессе перехода от быстрого 2 к 3, вот пример реализации swift 2:

public class Dispatch
{
    private static var _onceTokenTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token token: String, @noescape block:dispatch_block_t) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTokenTracker.contains(token) {
            return
        }

        _onceTokenTracker.append(token)
        block()
    }

}

Ответ 3

Расширяя ответ Тода Каннингема выше, я добавил еще один метод, который автоматически создает токен из файла, функции и строки.

public extension DispatchQueue {
    private static var _onceTracker = [String]()

    public class func once(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(line)"
        once(token: token, block: block)
    }

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String,
                           block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        guard !_onceTracker.contains(token) else { return }

        _onceTracker.append(token)
        block()
    }
}

Так что может быть проще позвонить:

DispatchQueue.once {
    setupUI()
}

и вы все равно можете указать токен, если хотите:

DispatchQueue.once(token: "com.hostname.project") {
    setupUI()
}

Я полагаю, вы можете получить коллизию, если у вас есть один и тот же файл в двух модулях. Жаль, что нет # #module

Ответ 4

редактировать

Ответ @Frizlab - это решение не гарантирует поточнобезопасности. Если это важно, следует использовать альтернативу

Простое решение

lazy var dispatchOnce : Void  = { // or anyName I choose

    self.title = "Hello Lazy Guy"

    return
}()

используется как

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}

Ответ 5

Вы можете использовать его, если вы добавите заголовок моста:

typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);

Тогда в .m где-то:

void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
    dispatch_once(predicate, block);
}

Теперь вы можете использовать mxcl_dispatch_once из Swift.

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

Ответ 6

Swift 3: для тех, кто любит многоразовые классы (или структуры):

public final class /* struct */ DispatchOnce {
   private var lock: OSSpinLock = OS_SPINLOCK_INIT
   private var isInitialized = false
   public /* mutating */ func perform(block: (Void) -> Void) {
      OSSpinLockLock(&lock)
      if !isInitialized {
         block()
         isInitialized = true
      }
      OSSpinLockUnlock(&lock)
   }
}

Использование:

class MyViewController: UIViewController {

   private let /* var */ setUpOnce = DispatchOnce()

   override func viewWillAppear() {
      super.viewWillAppear()
      setUpOnce.perform {
         // Do some work here
         // ...
      }
   }

}

Обновление (28 апреля 2017 г.): OSSpinLock заменено на os_unfair_lock из-за предупреждений об утомлении в macOS SDK 10.12.

public final class /* struct */ DispatchOnce {
   private var lock = os_unfair_lock()
   private var isInitialized = false
   public /* mutating */ func perform(block: (Void) -> Void) {
      os_unfair_lock_lock(&lock)
      if !isInitialized {
         block()
         isInitialized = true
      }
      os_unfair_lock_unlock(&lock)
   }
}

Ответ 7

Вы можете объявить переменную верхнего уровня следующим образом:

private var doOnce: ()->() = {
    /* do some work only once per instance */
    return {}
}()

тогда назовите это где-нибудь:

doOnce()

Ответ 8

Используйте подход с константой класса, если вы используете Swift 1.2 или выше, и подход с вложенной структурой, если вам нужно поддерживать более ранние версии. Исследование модели Синглтона в Свифте. Все нижеприведенные подходы поддерживают отложенную инициализацию и безопасность потоков. Подход dispatch_once не работает в Swift 3.0

Подход A: константа класса

class SingletonA {

    static let sharedInstance = SingletonA()

    init() {
        println("AAA");
    }

}

Подход B: Вложенная структура

class SingletonB {

    class var sharedInstance: SingletonB {
        struct Static {
            static let instance: SingletonB = SingletonB()
        }
        return Static.instance
    }

}

Подход C: диспетчеризация

class SingletonC {

    class var sharedInstance: SingletonC {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: SingletonC? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = SingletonC()
        }
        return Static.instance!
    }
}

Ответ 9

  I have created below function 

 func executeOnce(code: @escaping () -> Void)
        {
                if UserDefaults.standard.value(forKey: "3333##112233") == nil
                {
                    code()
                    UserDefaults.standard.setValue("vv", forKey: "3333##112233")
                    UserDefaults.standard.synchronize()
                }
        }

И используйте как ниже

  executeOnce {

        print("onces")
    }