[swift] Swift 3 GCD API 변경 후 dispatch_once

dispatch_once언어 버전 3에서 변경된 후 Swift 의 새로운 구문은 무엇입니까 ? 이전 버전은 다음과 같습니다.

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

이것은 libdispatch에 대한 변경 사항 입니다.



답변

로부터 문서 :

Dispatch
무료 함수 dispatch_once는 더 이상 Swift에서 사용할 수 없습니다. Swift에서는 지연 초기화 된 전역 또는 정적 속성을 사용할 수 있으며 dispatch_once가 제공하는 것과 동일한 스레드 안전성 및 1 회 호출 보장을 얻을 수 있습니다. 예:

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.


답변

지연 초기화 전역을 사용하는 것은 일회성 초기화에 대해 의미가 있지만 다른 유형에는 의미가 없습니다. 싱글 톤과 같은 것들에 대해 지연 초기화 된 전역을 사용하는 것은 많은 의미가 있습니다.

다음은 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!" )
}

현재 swift 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()
    }

}


답변

위의 Tod Cunningham의 답변을 확장하여 파일, 함수 및 줄에서 토큰을 자동으로 만드는 또 다른 방법을 추가했습니다.

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


답변

편집하다

@Frizlab의 대답-이 솔루션은 스레드 안전이 보장되지 않습니다. 이것이 중요한 경우 대안을 사용해야합니다.

간단한 해결책은

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

    self.title = "Hello Lazy Guy"

    return
}()

같이 사용

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


답변

브리징 헤더를 추가하면 계속 사용할 수 있습니다.

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_onceSwift 에서 사용할 수 있습니다 .

대부분 Apple이 제안한 것을 사용해야하지만, dispatch_once두 가지 기능에서 단일 토큰 을 사용하는 데 필요한 합법적 인 용도가 있었으며 대신 Apple이 제공하는 것이 적용되지 않았습니다.


답변

다음과 같이 최상위 변수 함수를 선언 할 수 있습니다.

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

그런 다음 어디서나 호출하십시오.

doOnce()


답변

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
         // ...
      }
   }

}

업데이트 (2017 년 4 월 28 일) : macOS SDK 10.12에서 지원 중단 경고 OSSpinLock로 대체되었습니다 os_unfair_lock.

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)
   }
}