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?
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
}
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!
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
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)
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
).
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 }
}
}
}
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()
}
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
}
}
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.
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.
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
}
}
}
}
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()
}
}
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
})
xCode 8.3.1, Swift 3.1
Lire valeur d'écriture à partir de différents threads (async).
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()
}
}
}
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)")
}
}
}
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.
dispatch_barrier_async est la meilleure solution, sans bloquer le thread actuel.
dispatch_barrier_async (accessQueue, { dictionary [object.ID] = object })
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()
1
2
3
11
22
33
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
}