dispatch_once
언어 버전 3에서 변경된 후 Swift 의 새로운 구문은 무엇입니까 ? 이전 버전은 다음과 같습니다.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
이것은 libdispatch에 대한 변경 사항 입니다.
dispatch_once
언어 버전 3에서 변경된 후 Swift 의 새로운 구문은 무엇입니까 ? 이전 버전은 다음과 같습니다.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
이것은 libdispatch에 대한 변경 사항 입니다.
pod 'SwiftDispatchOnce', '~> 1.0'
. :]
답변:
로부터 문서 :
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
명확했다. 불행히도 이것은 추하고 혼란 스럽습니다.
지연 초기화 전역을 사용하는 것은 일회성 초기화에 대해 의미가 있지만 다른 유형에는 의미가 없습니다. 싱글 톤과 같은 것들에 대해 지연 초기화 된 전역을 사용하는 것은 많은 의미가 있습니다.
다음은 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()
}
}
objc_sync_enter
하고 objc_sync_exit
더 이상.
위의 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
편집하다
간단한 해결책은
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_once
Swift 에서 사용할 수 있습니다 .
대부분 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)
}
}
OSSpinLock
로 대체되었습니다 os_unfair_lock
. BTW : 여기에 대한 좋은 WWDC 비디오가 있습니다 Concurrent Programming
: developer.apple.com/videos/play/wwdc2016/720
나는 위의 답변을 개선하여 결과를 얻습니다.
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()
}
}
}
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!
}
}