web-dev-qa-db-fra.com

Quel est l'équivalent Swift du "@synchronized" d'Objective-C?

J'ai consulté le livre Swift, mais je ne trouve pas la version Swift de @synchronized. Comment est-ce que je fais l'exclusion mutuelle dans Swift?

206
Bill

Utilisez GCD. Il est un peu plus verbeux que @synchronized, mais fonctionne parfaitement bien en remplacement:

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

Je le recherchais moi-même et suis arrivé à la conclusion qu'il n'y avait pas encore de construction autochtone à l'intérieur de Swift.

J'ai créé cette petite fonction d'aide à partir du 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)
}

L'utilisation est assez simple

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

J'ai trouvé un problème avec cela. Passer dans un tableau en tant qu'argument de verrouillage semble provoquer une erreur de compilation très obtuse à ce stade. Sinon, cela semble fonctionner comme vous le souhaitez.

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!
164
Bryan McLemore

J'aime et utilise beaucoup de réponses ici, alors je choisirais celle qui vous convient le mieux. Cela dit, la méthode que je préfère quand j'ai besoin de quelque chose comme le @synchronized de objective-c utilise l'instruction defer 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 est que votre section critique peut sortir du bloc contenant de la manière souhaitée (par exemple, return, break, continue, throw) et "les instructions contenues dans l'instruction différée sont exécutées quelle que soit la méthode utilisée pour transférer le contrôle du programme ." 1

139
ɲeuroburɳ

Vous pouvez combiner des déclarations 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)
75
Matt Bridges

L'analogue de la directive @synchronized de Objective-C peut avoir un type de retour arbitraire et Nice rethrows behavior 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 l'instruction defer permet de renvoyer directement une valeur sans introduire de variable temporaire.


Dans Swift 2, ajoutez l'attribut @noescape à la fermeture pour permettre d'autres 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).

70
werediver

Swift 4

Dans Swift 4, vous pouvez utiliser les files d'attente de répartition GCD 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 }
        }
    }
} 
34
Sebastian Boldt

En utilisant la réponse de Bryan McLemore, je l’ai étendue pour prendre en charge les objets qui jettent un voile sur un manoir sûr avec la capacité de report 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

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
GNewc

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 semaphore.wait () jusqu'à ce que signal () soit appelé. internalQueue.sync ne doit pas être utilisé car il 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 traitement des erreurs.

10
Hanny

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

Attention La classe NSLock utilise des threads POSIX pour implémenter son comportement de verrouillage. Lorsque vous envoyez un message de déverrouillage à un objet NSLock, vous devez être sûr que ce message est envoyé à partir du même thread que celui qui a envoyé le message de verrouillage initial. Déverrouiller un verrou d'un autre thread peut entraîner un comportement indéfini.

7
User9527

Je viens de trouver la réponse dans "Comprendre les accidents et les journaux d'accidents" session 414 de la WWDC 2018. Comme Conmulligan l’a souligné, il convient d’utiliser DispatchQueues avec sync. 

Dans Swift 4, cela devrait ressembler à ceci:

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
          }
        }
    }
}
4
rockdaswift

En conclusion, voici donner plus commun façon qui incluent la valeur de retour ou vide, et jeter

fondation d'importation

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

Je vais publier mon implémentation Swift 5, construite à partir des réponses précédentes. Merci les gars! J'ai trouvé utile d'en avoir une qui renvoie également une valeur. J'ai donc deux méthodes.

Voici un cours simple à faire en premier:

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

Puis utilisez-le comme si vous aviez besoin d'une valeur de retour:

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

Ou:

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

Détails

xCode 8.3.1, Swift 3.1

Tâche

Lire valeur d'écriture à partir de différents threads (async).

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

Usage

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

Pourquoi rendre la tâche difficile et sans tracas avec les serrures? Utilisez les barrières d’expédition.

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

Lorsqu’il est en cours d’exécution, aucun autre bloc de la file n’est autorisé à s’exécuter, même si des cœurs simultanés et autres sont disponibles.

Si cela ressemble à un verrou exclusif (en écriture), il s'agit de . Les blocs non-barrière peuvent être considérés comme des verrous partagés (en lecture).

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

0
Frederick C. Lee

dispatch_barrier_async est la meilleure solution, sans bloquer le thread actuel.

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

0
Jacky

Sur la base de ɲeuroburɳ , 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
AechoLiu

Essayez: NSRecursiveLock

Un verrou pouvant être acquis plusieurs fois par le même thread sans provoquer d'interblocage.

let lock = NSRecursiveLock()

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

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