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?
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
}
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!
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
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)
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
).
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 }
}
}
}
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()
}
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 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.
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.
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
}
}
}
}
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()
}
}
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é.
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
Dispatch_barrier_async est le meilleur moyen, sans bloquer le thread actuel.
Dispatch_barrier_async(accessQueue, { dictionnaire[object.ID] = objet })
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)")
}
}
}
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
}
}