Saya telah mencari di buku Swift, tetapi tidak dapat menemukan versi Swift dari @sinkronisasi. Bagaimana saya melakukan pengecualian bersama dalam Swift?
removeFirst()
?
Saya telah mencari di buku Swift, tetapi tidak dapat menemukan versi Swift dari @sinkronisasi. Bagaimana saya melakukan pengecualian bersama dalam Swift?
removeFirst()
?
Jawaban:
Anda bisa menggunakan GCD. Ini sedikit lebih verbose daripada @synchronized
, tetapi berfungsi sebagai pengganti:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Saya sendiri sedang mencari ini dan sampai pada kesimpulan belum ada konstruksi asli dalam swift untuk ini.
Saya membuat fungsi pembantu kecil ini berdasarkan pada beberapa kode yang saya lihat dari Matt Bridges dan yang lainnya.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
Penggunaannya cukup mudah
synced(self) {
println("This is a synchronized closure")
}
Ada satu masalah yang saya temukan dengan ini. Melewati dalam array sebagai argumen kunci tampaknya menyebabkan kesalahan kompiler yang sangat tumpul pada saat ini. Kalau tidak, tampaknya berfungsi seperti yang diinginkan.
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!
@synchronized
blok dengan baik, tetapi perhatikan bahwa itu tidak identik dengan pernyataan blok builtin nyata seperti @synchronized
blok di Objective-C, karena return
dan break
pernyataan tidak lagi berfungsi untuk melompat keluar dari fungsi / loop di sekitarnya seperti itu akan terjadi jika ini adalah pernyataan biasa.
defer
kata kunci baru untuk memastikan objc_sync_exit
dipanggil bahkan jika closure
melempar.
Saya suka dan menggunakan banyak jawaban di sini, jadi saya akan memilih mana yang paling cocok untuk Anda. Yang mengatakan, metode yang saya sukai ketika saya membutuhkan sesuatu seperti objektif-c @synchronized
menggunakan defer
pernyataan yang diperkenalkan di Swift 2.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
Bagus hal tentang metode ini, adalah bahwa bagian kritis Anda dapat keluar blok yang mengandung dalam setiap busana yang diinginkan (misalnya, return
, break
, continue
, throw
), dan "laporan dalam pernyataan defer dijalankan tidak peduli bagaimana kontrol program ditransfer." 1
lock
? Bagaimana lock
diinisialisasi?
lock
adalah objek objektif-c.
Anda dapat menjepit pernyataan antara objc_sync_enter(obj: AnyObject?)
dan objc_sync_exit(obj: AnyObject?)
. Kata kunci @sinkronisasi menggunakan metode-metode di bawah penutup. yaitu
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
dan objc_sync_exit
apakah metode didefinisikan dalam Objc-sync.h dan merupakan sumber terbuka: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
& objc_sync_exit(…)
adalah tajuk publik yang disediakan oleh iOS / macOS / etc. API (sepertinya ada di dalam ….sdk
di jalur usr/include/objc/objc-sync.h
) . Cara termudah untuk mengetahui apakah sesuatu adalah API publik atau tidak adalah dengan (dalam Xcode) ketik nama fungsi (misalnya objc_sync_enter()
; argumen tidak perlu ditentukan untuk fungsi C) , lalu coba klik perintah. Jika itu menunjukkan kepada Anda file header untuk API itu, maka Anda baik (karena Anda tidak akan dapat melihat header jika itu tidak publik) .
Analog @synchronized
arahan dari Objective-C dapat memiliki jenis pengembalian sewenang-wenang dan rethrows
perilaku yang baik di 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()
}
Penggunaan defer
pernyataan memungkinkan untuk secara langsung mengembalikan nilai tanpa memperkenalkan variabel sementara.
Di Swift 2 tambahkan @noescape
atribut ke penutupan untuk memungkinkan lebih banyak optimisasi:
// 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()
}
Berdasarkan jawaban dari GNewc [1] (di mana saya suka tipe pengembalian sewenang-wenang) dan Tod Cunningham [2] (di mana saya suka defer
).
SWIFT 4
Di Swift 4 Anda dapat menggunakan antrian pengiriman GCD untuk mengunci sumber daya.
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 }
}
}
}
.serial
tampaknya tidak tersedia. Tetapi .concurrent
tersedia. : /
myObject.state = myObject.state + 1
secara bersamaan, itu tidak akan menghitung total operasi tetapi menghasilkan nilai nondeterministic. Untuk mengatasi masalah itu, kode panggilan harus dibungkus dalam antrian serial sehingga membaca dan menulis terjadi secara atomis. Tentu saja Obj-c @synchronised
memiliki masalah yang sama, jadi dalam arti implementasi Anda sudah benar.
myObject.state += 1
merupakan kombinasi dari operasi baca dan kemudian penulisan. Beberapa utas lainnya masih dapat datang di antara untuk menetapkan / menulis nilai. Sesuai objc.io/blog/2018/12/18/atomic-variables , akan lebih mudah untuk menjalankan set
blok / penutupan sinkronisasi sebagai gantinya dan tidak di bawah variabel itu sendiri.
Menggunakan jawaban Bryan McLemore, saya memperluasnya untuk mendukung benda-benda yang melemparkan manor yang aman dengan kemampuan menunda Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
untuk menyederhanakan penggunaan dengan penutupan non-lempar (tidak perlu digunakan try
), seperti yang ditunjukkan dalam jawaban saya .
Untuk menambahkan fungsionalitas pengembalian, Anda dapat melakukan ini:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Selanjutnya, Anda dapat memanggilnya menggunakan:
func importantMethod(...) -> Bool {
return synchronize(self) {
if(feelLikeReturningTrue) { return true }
// do other things
if(feelLikeReturningTrueNow) { return true }
// more things
return whatIFeelLike ? true : false
}
}
Cepat 3
Kode ini memiliki kemampuan masuk kembali dan dapat bekerja dengan panggilan fungsi Asynchronous. Dalam kode ini, setelah someAsyncFunc () dipanggil, penutupan fungsi lain pada antrian serial akan memproses tetapi diblokir oleh semaphore.wait () hingga signal () dipanggil. internalQueue.sync tidak boleh digunakan karena akan memblokir utas utama jika saya tidak salah.
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 bukan ide yang baik tanpa penanganan kesalahan.
Dalam sesi "Memahami Kecelakaan dan Kecelakaan" 414 dari WWDC 2018, mereka menunjukkan cara berikut menggunakan DispatchQueues dengan sinkronisasi.
Di swift 4 harus seperti ini:
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
}
}
}
}
Pokoknya Anda juga dapat membuat pembacaan lebih cepat menggunakan antrian bersamaan dengan hambatan. Sinkronisasi dan pembacaan async dilakukan bersamaan dan menulis nilai baru menunggu operasi sebelumnya selesai.
class ImageCache {
private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
private var storage: [String: UIImage] = [:]
func get(_ key: String) -> UIImage? {
return queue.sync { [weak self] in
guard let self = self else { return nil }
return 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
}
}
}
Gunakan NSLock di 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()
Peringatan Kelas NSLock menggunakan utas POSIX untuk menerapkan perilaku pengunciannya. Saat mengirim pesan pembuka kunci ke objek NSLock, Anda harus yakin bahwa pesan dikirim dari utas yang sama dengan yang mengirim pesan kunci awal. Membuka kunci kunci dari utas yang berbeda dapat mengakibatkan perilaku yang tidak terdefinisi.
Di Swift 5 modern, dengan kemampuan pengembalian:
/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
@discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
Gunakan seperti ini, untuk memanfaatkan kapabilitas nilai pengembalian:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Atau seperti itu sebaliknya:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Tampaknya pada dasarnya tidak ada yang menggunakan atau mengerti cara menggunakannya Thread
. Saya senang dengan itu - sedangkan GCD
penuh dengan gotcha dan keterbatasan.
Coba: NSRecursiveLock
Kunci yang mungkin diperoleh beberapa kali oleh utas yang sama tanpa menyebabkan kebuntuan.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Gambar Saya akan memposting implementasi Swift 5 saya, dibangun dari jawaban sebelumnya. Terima kasih kawan! Saya merasa terbantu memiliki satu yang mengembalikan nilai juga, jadi saya punya dua metode.
Berikut ini adalah kelas sederhana yang harus dibuat terlebih dahulu:
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()
}
}
Kemudian gunakan seperti itu jika membutuhkan nilai kembali:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Atau:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, bekerja untuk keduanya, batal dan jenis lainnya. Ada juga hal-hal yang tumbuh kembali.
xCode 8.3.1, cepat 3.1
Baca nilai tulis dari utas berbeda (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
}
ekstensi 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()
}
}
}
kelas 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)")
}
}
}
Dengan pembungkus properti Swift, inilah yang saya gunakan sekarang:
@propertyWrapper public struct NCCSerialized<Wrapped> {
private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")
private var _wrappedValue: Wrapped
public var wrappedValue: Wrapped {
get { queue.sync { _wrappedValue } }
set { queue.sync { _wrappedValue = newValue } }
}
public init(wrappedValue: Wrapped) {
self._wrappedValue = wrappedValue
}
}
Maka Anda bisa melakukan:
@NCCSerialized var foo: Int = 10
atau
@NCCSerialized var myData: [SomeStruct] = []
Kemudian akses variabel seperti biasa.
DispatchQueue
yang tersembunyi dari pengguna. Saya menemukan referensi SO ini untuk menenangkan pikiran saya: stackoverflow.com/a/35022486/1060314
Kesimpulannya, Di sini memberikan cara yang lebih umum yang mencakup nilai balik atau batal, dan melempar
import Foundation
extension NSObject {
func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T
{
objc_sync_enter(lockObj)
defer {
objc_sync_exit(lockObj)
}
return try closure()
}
}
Mengapa membuatnya sulit dan repot dengan kunci? Gunakan Dispatch Barriers.
Penghalang pengiriman membuat titik sinkronisasi dalam antrian bersamaan.
Ketika sedang berjalan, tidak ada blok lain di antrian yang diizinkan untuk berjalan, bahkan jika itu bersamaan dan inti lainnya tersedia.
Jika itu terdengar seperti kunci (tulis) eksklusif, ya. Blok non-penghalang dapat dianggap sebagai kunci bersama (baca).
Selama semua akses ke sumber daya dilakukan melalui antrian, hambatan menyediakan sinkronisasi yang sangat murah.
Berdasarkan ɲeuroburɳ , uji kasus kelas bawah
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
Metode lain adalah membuat superclass dan kemudian mewarisinya. Dengan cara ini Anda dapat menggunakan GCD secara langsung
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
}
}