web-dev-qa-db-ja.com

Objective-Cの "@synchronized"と同等のSwiftは何ですか?

私はSwiftの本を検索しましたが、Swift版の@synchronizedが見つかりません。 Swiftで相互排除を行うにはどうすればよいですか?

212
Bill

あなたはGCDを使うことができます。これは@synchronizedよりも少し冗長ですが、代わりとして機能します。

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
    // code
}
169
conmulligan

私はこれを自分で探していて、Swiftの内部にはまだネイティブコンストラクトがないという結論に達しました。

私はMatt Bridgesと他の人から見たコードのいくつかに基づいてこの小さなヘルパー関数を作りました。

func synced(_ lock: Any, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

使い方はかなり簡単です

synced(self) {
    println("This is a synchronized closure")
}

私はこれで見つけた一つの問題があります。 lock引数として配列を渡すと、この時点では非常に曖昧なコンパイラエラーが発生するようです。そうでなければそれは望みどおりに動作するようです。

Bitcast requires both operands to be pointer or neither
  %26 = bitcast i64 %25 to %objc_object*, !dbg !378
LLVM ERROR: Broken function found, compilation aborted!
168
Bryan McLemore

私はここで多くの答えを気に入って使っていますので、あなたに最適なものを選んでください。ただし、Objective-cの@synchronizedのようなものが必要なときに私が好む方法は、Swift 2で導入された defer ステートメントを使用します。

{ 
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    //
    // code of critical section goes here
    //

} // <-- lock released when this block is exited

このメソッドのいいところは、あなたのクリティカルセクションは望みの方法(例えばreturnbreakcontinuethrow)で包含ブロックを終了できることです。 " 1

140
ɲeuroburɳ

objc_sync_enter(obj: AnyObject?)objc_sync_exit(obj: AnyObject?)の間にステートメントを挟むことができます。 @synchronizedキーワードは、カバーの下でこれらのメソッドを使用しています。すなわち.

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
76
Matt Bridges

Objective-Cの @synchronized ディレクティブの類似物は、Swiftでは任意の戻り型とNice rethrows の動作を持つことができます。

// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

defer ステートメントを使用すると、一時変数を導入せずに直接値を返すことができます。


Swift 2では、クロージャに@noescape属性を追加して、最適化をさらに進めます。

// Swift 2
func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

GNewc [1] (ここでは任意の戻り値の型が好きです)およびTod Cunningham [2] (私はdeferが好きです)の回答に基づいています。

71
werediver

Swift 4

Swift 4では、GCDディスパッチキューを使用してリソースをロックできます。

class MyObject {
    private var internalState: Int = 0
    private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default

    var state: Int {
        get {
            return internalQueue.sync { internalState }
        }

        set (newState) {
            internalQueue.sync { internalState = newState }
        }
    }
} 
34
Sebastian Boldt

返品機能を追加するには、次のようにします。

func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
  objc_sync_enter(lockObj)
  var retVal: T = closure()
  objc_sync_exit(lockObj)
  return retVal
}

それ以降は、次のように呼び出します。

func importantMethod(...) -> Bool {
  return synchronize(self) {
    if(feelLikeReturningTrue) { return true }
    // do other things
    if(feelLikeReturningTrueNow) { return true }
    // more things
    return whatIFeelLike ? true : false
  }
}
23
GNewc

Bryan McLemoreの回答を使用して、Swift 2.0の遅延機能を使って安全な方法で投下するオブジェクトをサポートするように拡張しました。

func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
    objc_sync_enter(lock)
    defer {
        objc_sync_exit(lock)
    }

    try block()
}
23
Tod Cunningham

Swift

このコードには再入力機能があり、非同期関数呼び出しで機能します。このコードでは、someAsyncFunc()が呼び出された後、シリアルキューの別の関数クロージャが処理されますが、signal()が呼び出されるまでsemaphore.wait()によってブロックされます。間違えない限りinternalQueue.syncはメインスレッドをブロックするため、internalQueue.syncは使用しないでください。

let internalQueue = DispatchQueue(label: "serialQueue")
let semaphore = DispatchSemaphore(value: 1)

internalQueue.async {

    self.semaphore.wait()

    // Critical section

    someAsyncFunc() {

        // Do some work here

        self.semaphore.signal()
    }
}

objc_sync_enter/objc_sync_exitはエラー処理なしではお勧めできません。

10
Hanny

Swift4では NSLock を使用します。

let lock = NSLock()
lock.lock()
if isRunning == true {
        print("Service IS running ==> please wait")
        return
} else {
    print("Service not running")
}
isRunning = true
lock.unlock()

警告NSLockクラスはPOSIXスレッドを使用してそのロック動作を実装します。 NSLockオブジェクトにロック解除メッセージを送信するときは、メッセージが最初のロックメッセージを送信したのと同じスレッドから送信されていることを確認する必要があります。別のスレッドからロックを解除すると、未定義の動作が発生する可能性があります。

7
User9527

私はちょうど2018年のWWDCの「理解クラッシュとクラッシュログ」 セッション414 で答えを見つけました。 conmulliganが指摘したように正しい方法はsyncと一緒にDispatchQueuesを使うことです。

Swift 4では、次のようになります。

class ImageCache {
    private let queue = DispatchQueue(label: "sync queue")
    private var storage: [String: UIImage] = [:]
    public subscript(key: String) -> UIImage? {
        get {
          return queue.sync {
            return storage[key]
          }
        }
        set {
          queue.sync {
            storage[key] = newValue
          }
        }
    }
}

障壁と同時に、読み取りは非同期で、書き込みは前の要求を待ちます。

class ImageCache {
    private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
    private var storage: [String: UIImage] = [:]

    func get(_ key: String, onResult: @escaping (UIImage?)->Void) {
        queue.async { [weak self] in
            guard let self = self else { return }
            onResult(self.storage[key])
        }
    }

    func set(_ image: UIImage, for key: String) {
        queue.async(flags: .barrier) { [weak self] in
            guard let self = self else { return }
            self.storage[key] = image
        }
    }
}
4
rockdaswift

詳細

xCode 8.3.1、Swift 3.1

仕事

異なるスレッドから値を読み取ります(非同期)。

コード

class AsyncObject<T>:CustomStringConvertible {
    private var _value: T
    public private(set) var dispatchQueueName: String

    let dispatchQueue: DispatchQueue

    init (value: T, dispatchQueueName: String) {
        _value = value
        self.dispatchQueueName = dispatchQueueName
        dispatchQueue = DispatchQueue(label: dispatchQueueName)
    }

    func setValue(with closure: @escaping (_ currentValue: T)->(T) ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                _self._value = closure(_self._value)
            }
        }
    }

    func getValue(with closure: @escaping (_ currentValue: T)->() ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                closure(_self._value)
            }
        }
    }


    var value: T {
        get {
            return dispatchQueue.sync { _value }
        }

        set (newValue) {
            dispatchQueue.sync { _value = newValue }
        }
    }

    var description: String {
        return "\(_value)"
    }
}

使用法

print("Single read/write action")
// Use it when when you need to make single action
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
obj.value = 100
let x = obj.value
print(x)

print("Write action in block")
// Use it when when you need to make many action
obj.setValue{ (current) -> (Int) in
    let newValue = current*2
    print("previous: \(current), new: \(newValue)")
    return newValue
}

フルサンプル

extension DispatchGroup

extension DispatchGroup {

    class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) {
        let group = DispatchGroup()
        for index in 0...repeatNumber {
            group.enter()
            DispatchQueue.global(qos: .utility).async {
                action(index)
                group.leave()
            }
        }

        group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
            completion()
        }
    }
}

クラスViewController

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //sample1()
        sample2()
    }

    func sample1() {
        print("=================================================\nsample with variable")

        let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")

        DispatchGroup.loop(repeatNumber: 5, action: { index in
            obj.value = index
        }) {
            print("\(obj.value)")
        }
    }

    func sample2() {
        print("\n=================================================\nsample with array")
        let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
        DispatchGroup.loop(repeatNumber: 15, action: { index in
            arr.setValue{ (current) -> ([Int]) in
                var array = current
                array.append(index*index)
                print("index: \(index), value \(array[array.count-1])")
                return array
            }
        }) {
            print("\(arr.value)")
        }
    }
}
1

結論として、ここでは戻り値や無効を含むより一般的な方法を示し、

輸入財団

extension NSObject {


    func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows ->  T
    {
        objc_sync_enter(lockObj)
        defer {
            objc_sync_exit(lockObj)
        }

        return try closure()
    }


}
1
Victor Choy

図前の回答から構築されたSwift 5実装を投稿します。みんなありがとう!値を返すものもあると便利なので、2つの方法があります。

最初に作成する簡単なクラスを次に示します。

import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        closure()
    }
    public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        return closure()
    }
}

戻り値が必要な場合は、次のように使用します。

return Sync.syncedReturn(self, closure: {
    // some code here
    return "hello world"
})

または:

Sync.synced(self, closure: {
    // do some work synchronously
})
1
TheJeff

なぜそれを困難にしてロックに面倒なのでしょうか。ディスパッチバリアを使用する.

ディスパッチバリアは、コンカレントキュー内に同期ポイントを作成します。

実行中は、コンカレントコアや他のコアが使用可能であっても、キュー上の他のブロックは実行できません。

それが排他的(書き込み)ロックのように聞こえるならば、それはそうです。非バリアブロックは、共有(読み取り)ロックと見なすことができます。

リソースへのすべてのアクセスがキューを介して実行される限り、バリアは非常に安価な同期を提供します。

0

現在のスレッドをブロックしない一方で、dispatch_barrier_asyncがより良い方法です。

dispatch_barrier_async(accessQueue、{dictionary [object.ID] = object})

0
Jacky

ɲeuroburɳに基づく 、サブクラスのケースをテストする

class Foo: NSObject {
    func test() {
        print("1")
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
            print("3")
        }

        print("2")
    }
}


class Foo2: Foo {
    override func test() {
        super.test()

        print("11")
        objc_sync_enter(self)
        defer {
            print("33")
            objc_sync_exit(self)
        }

        print("22")
    }
}

let test = Foo2()
test.test()

出力:

1
2
3
11
22
33
0
AechoLiu

試してください: NSRecursiveLock

デッドロックを引き起こすことなく、同じスレッドによって複数回取得されるロック。

let lock = NSRecursiveLock()

func f() {
    lock.lock()
    //Your Code
    lock.unlock()
}

func f2() {
    lock.lock()
    defer {
        lock.unlock()
    }
    //Your Code
}
0
ZevsVU