Swift 3 GCD API 변경 후 dispatch_once


84

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

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

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



답변:


70

로부터 문서 :

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.

3
Swift가 빠르게 변하고 Swift 버전 사이에 많은 깨진 코드를 수정해야한다는 것을 몰랐던 것은 아닙니다.
Abizern

2
가장 큰 고통은 Swift3와 항상 호환되지 않는 타사 포드입니다.
Tinkerbell

4
이것이 제 3 자 종속성 인 @Tinkerbell을 도입 할 때 발생하는 기술적 부채입니다. 저는 Swift를 좋아하지만 바로 이런 이유로 그것을 사용하는 외부 의존성을 가져 오는 데 특히주의합니다.
Chris Wagner

17
dispatch_once명확했다. 불행히도 이것은 추하고 혼란 스럽습니다.
Alexandre G

104

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

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

}

해결책을 주셔서 대단히 감사합니다. 나는 정확히 스위 즐 설정에 갇혀 있었다. 신속한 팀이이 사용 사례를 해결하기를 바랍니다.
salman140

2
당신은 절대적으로 사용해서는 안 objc_sync_enter하고 objc_sync_exit더 이상.
smat88dd

1
왜 그런데?
Tod Cunningham

1
성능을 위해 _onceTrackers에 대한 배열 대신 집합을 사용해야합니다. 이것은 O (N)에서 O (1)로 시간 복잡도를 향상시킵니다.
Werner Altewischer

2
따라서 재사용 가능한 클래스는 그렇게 많이 재사용되지 않는다고 가정하여 작성합니다. :-) 시간 복잡도를 O (N)에서 O (1)로 낮추기 위해 추가 노력이 필요하지 않으면 항상 IMHO를 수행해야합니다.
Werner Altewischer

62

위의 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


이것은 무슨 일이 일어나고 있는지에 대한 좀 더 많은 빛을 비 춥니 다. 감사.
nyxee


정말 고맙습니다 도움이
Manjunath C.Kadani

19

편집하다

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

간단한 해결책은

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

    self.title = "Hello Lazy Guy"

    return
}()

같이 사용

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

1
이것은 전혀 도움이되지 않습니다. 게으른 var 선언은 일반 코드와 함께 인라인으로 만들 수 없기 때문에 구조체 또는 클래스 정의에 있어야합니다. 즉, dispatchOnce의 내용은 인스턴스의 주변 범위를 캡처 할 수 없습니다. 예를 들어 아직 실행되지 않은 클로저를 선언하는 경우 해당 클로저 내부의 구조체를 선언 할 수 없으며 lazy var의 내용이 주변 클로저에서 var를 캡처하는 또 다른 클로저가되도록 할 수 없습니다 ...
CommaToast

3
이 코드가 확실히 있기 때문에을 downvoted 하지 dispatch_once과 같은 의미가. dispatch_once는 코드가에서 호출하는 스레드 에 관계 없이 정확히 한 번 실행되도록합니다 . Lazy vars는 다중 스레드 환경에서 정의되지 않은 동작을 가지고 있습니다.
Frizlab

이 솔루션에서 init 블록은 경우에 따라 두 번 호출 할 것입니다
신성한

8

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

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이 제공하는 것이 적용되지 않았습니다.


7

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

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

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

doOnce()

1
Lazy vars는 클래스로 범위가 지정되므로 dispatch_once처럼 절대적으로 작동하지 않습니다. 기본 클래스의 인스턴스 당 한 번 실행됩니다. 클래스 외부로 이동 [private var doOnce : ()-> () = {}] 또는 정적 표시 [static private var doOnce : ()-> () = {}]
Eli Burke

1
물론 맞습니다! 감사. 대부분의 경우 인스턴스 당 한 번의 작업이 필요합니다.
Bogdan Novikov

2
이것은 정말 훌륭한 솔루션입니다! 우아한 짧고 명확
벤 Leggiero

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

}

업데이트 (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)
   }
}

OSSSpinLock이 iOS 10.0에서 더 이상 사용되지 않는다는 메시지를 받았습니다
markhorrocks

2
감사! 예제 코드가 업데이트되었습니다. OSSpinLock로 대체되었습니다 os_unfair_lock. BTW : 여기에 대한 좋은 WWDC 비디오가 있습니다 Concurrent Programming: developer.apple.com/videos/play/wwdc2016/720
Vlad

0

나는 위의 답변을 개선하여 결과를 얻습니다.

import Foundation
extension DispatchQueue {
    private static var _onceTracker = [AnyHashable]()

    ///only excute once in same file&&func&&line
    public class func onceInLocation(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(line)"
        once(token: token, block: block)
    }

    ///only excute once in same Variable
    public class func onceInVariable(variable:NSObject, block: () -> Void){
        once(token: variable.rawPointer, 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: AnyHashable,block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

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

        _onceTracker.append(token)
        block()
    }

}

extension NSObject {
    public var rawPointer:UnsafeMutableRawPointer? {
        get {
            Unmanaged.passUnretained(self).toOpaque()
        }
    }
}

-3

Swift 1.2 이상을 사용하는 경우 클래스 상수 접근 방식을 사용하고 이전 버전을 지원해야하는 경우 중첩 된 구조체 접근 방식을 사용하십시오. Swift의 Singleton 패턴 탐색. 아래의 모든 접근 방식은 지연 초기화 및 스레드 안전성을 지원합니다. 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 : dispatch_once

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

1
문제는 특히 스위프트 3에 대한 해결책에 대해 질문
thesummersign
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.