Quel est L'équivalent Swift de Objective-C "@synchronized"?

J'ai cherché dans le livre Swift, mais je ne trouve pas la version Swift de @ synchronized. Comment puis-je faire l'exclusion mutuelle dans Swift?

196
demandé sur Bill 2014-06-04 23:26:00

17 réponses

Utilisez GCD. C'est un peu plus bavarde que @synchronized, mais fonctionne parfaitement comme un remplacement:

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
    // code
}
159
répondu conmulligan 2018-07-26 15:52:40

Je cherchais moi-même cela et je suis arrivé à la conclusion qu'il n'y avait pas encore de construction native à l'intérieur de swift pour cela.

J'ai créé cette petite fonction d'aide basée sur le code que J'ai vu de Matt Bridges et d'autres.

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

Utilisation est assez simple

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

Il y a un problème que j'ai trouvé avec ceci. Passer dans un tableau comme argument de verrouillage semble provoquer une erreur de compilation très obtus à ce stade. Sinon bien que cela semble fonctionner comme voulu.

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!
157
répondu Bryan McLemore 2017-04-10 04:28:13

J'aime et j'utilise beaucoup de réponses ici, donc je choisirais celui qui vous convient le mieux. Cela dit, la méthode que je préfère quand j'ai besoin de quelque chose comme objective-C @synchronized utilise le defer déclaration introduite dans swift 2.

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

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

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

La bonne chose à propos de cette méthode, c'est que votre section critique peut quitter le bloc contenant de la manière souhaitée (par exemple, return, break, continue, throw), et " les instructions dans l'instruction defer sont exécutées quel que soit le contrôle du programme transférer."1

133
répondu ɲeuroburɳ 2015-08-11 19:05:35

Vous pouvez prendre en sandwich des instructions entre objc_sync_enter(obj: AnyObject?) et objc_sync_exit(obj: AnyObject?). Le mot-clé @synchronized utilise ces méthodes sous les couvertures. c'est à dire

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
72
répondu Matt Bridges 2014-06-04 20:20:28

Analogique du @synchronized directive de Objective-C peut avoir un type de retour arbitraire et agréable rethrows comportement dans Swift.

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

L'utilisation de la defer instruction permet de retourner directement une valeur, sans l'introduction d'une variable temporaire.


Dans Swift 2 Ajoutez l'attribut @noescape à la fermeture pour permettre plus d'optimisations:

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

Basé sur les réponses de GNewc [1] (où j'aime le type de retour arbitraire) et Tod Cunningham [2] (où j'aime defer).

68
répondu werediver 2017-05-23 11:47:27

SWIFT 4

Dans Swift 4, Vous pouvez utiliser les files D'attente GCDS dispatch pour verrouiller les ressources.

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 }
        }
    }
} 
29
répondu Sebastian Boldt 2018-02-28 07:20:17

En utilisant la réponse de Bryan McLemore, Je l'ai étendue pour supporter les objets qui jettent dans un manoir sûr avec la capacité Swift 2.0 defer.

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

    try block()
}
23
répondu Tod Cunningham 2016-07-11 17:10:26

Pour ajouter return functionalty, vous pouvez faire ceci:

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

Par la suite, vous pouvez l'appeler en utilisant:

func importantMethod(...) -> Bool {
  return synchronize(self) {
    if(feelLikeReturningTrue) { return true }
    // do other things
    if(feelLikeReturningTrueNow) { return true }
    // more things
    return whatIFeelLike ? true : false
  }
}
22
répondu GNewc 2014-11-04 19:44:02

Swift 3

Ce code a la capacité de ré-entrée et peut fonctionner avec des appels de fonctions asynchrones. Dans ce code, après l'appel de someAsyncFunc (), une autre fermeture de fonction sur la file d'attente série sera traitée mais bloquée par sémaphore.attendez () jusqu'à ce que signal () soit appelé. internalQueue.la synchronisation ne devrait pas être utilisée car elle bloquera le thread principal si Je ne me trompe pas.

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 n'est pas une bonne idée sans gestion des erreurs.

10
répondu Hanny 2016-11-12 17:23:56

Utiliser NSLock dans Swift4:

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

Avertissement La classe NSLOCK utilise des threads POSIX pour implémenter son comportement de verrouillage. Lors de l'envoi d'un message de déverrouillage à un objet NSLock, vous devez être sûr que le message est envoyé à partir du même thread qui a envoyé le message de verrouillage initial. Déverrouiller un verrou à partir d'un thread différent peut entraîner un comportement indéfini.

5
répondu User9527 2017-10-31 10:07:49

Je viens de trouver la réponse dans la" compréhension des accidents et des journaux de Crash " session 414 de la WWDC 2018. Comme conmulligan l'a souligné, la bonne façon devrait être d'utiliser DispatchQueues avec sync.

Dans swift 4 devrait être quelque chose comme ce qui suit:

class ImageCache {
    private let queue = DispatchQueue(label: "com.company.name.cache")
    private var storage: [String: UIImage] = [:]
    public subscript(key: String) -> UIImage? {
        get {
          return queue.sync {
            return storage[key]
          }
        }
        set {
          queue.sync {
            storage[key] = newValue
          }
        }
    }
}
2
répondu rockdaswift 2018-06-27 15:11:23

En conclusion, donnez ici une manière plus commune qui inclut la valeur de retour ou void, et lancez

Importation De La Fondation

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
répondu Victor Choy 2016-06-21 18:36:39

Pourquoi rendre les serrures difficiles et compliquées? Utiliser Des Barrières De Répartition.

Une barrière de répartition crée un point de synchronisation dans une file d'attente concurrente.

Pendant son exécution, aucun autre bloc de la file d'attente n'est autorisé à s'exécuter, même s'il est simultané et que d'autres cœurs sont disponibles.

Si cela ressemble à un verrou exclusif (écriture), c'est le cas. Les blocs sans barrière peuvent être considérés comme des verrous partagés (lus).

Tant que tous les accès à la ressource sont effectués via file d'attente, les barrières fournissent une synchronisation très bon marché.

0
répondu Frederick C. Lee 2015-08-07 18:13:20

Basé sureueurobur {, Testez un cas de sous-classe

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

Sortie:

1
2
3
11
22
33
0
répondu AechoLiu 2017-05-23 12:18:15

Dispatch_barrier_async est le meilleur moyen, sans bloquer le thread actuel.

Dispatch_barrier_async(accessQueue, { dictionnaire[object.ID] = objet })

0
répondu Jacky 2016-05-06 13:23:16

Détails

XCode 8.3.1, swift 3.1

Tâche

Lire la valeur d'écriture de différents threads (asynchrone).

Code

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

Utilisation

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
}

Échantillon Complet

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

Classe 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)")
        }
    }
}
0
répondu Vasily Bodnarchuk 2017-05-10 18:58:09

Une autre méthode consiste à créer une superclasse, puis à l'hériter. De cette façon, vous pouvez utiliser GCD plus directement

class Lockable {
    let lockableQ:dispatch_queue_t

    init() {
        lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
    }

    func lock(closure: () -> ()) {
        dispatch_sync(lockableQ, closure)
    }
}


class Foo: Lockable {

    func boo() {
        lock {
            ....... do something
        }
    }
-2
répondu Jim 2015-07-10 22:31:31