Alternatif untuk dispatch_get_current_queue () untuk blok penyelesaian di iOS 6?

101

Saya memiliki metode yang menerima blok dan blok penyelesaian. Blok pertama harus berjalan di latar belakang, sedangkan blok penyelesaian harus berjalan dalam antrian apa pun yang dipanggil metode tersebut.

Untuk yang terakhir saya selalu menggunakan dispatch_get_current_queue(), tetapi sepertinya itu sudah usang di iOS 6 atau lebih tinggi. Apa yang harus saya gunakan?

cfischer
sumber
mengapa menurut Anda dispatch_get_current_queue()tidak digunakan lagi di iOS 6? para dokter tidak mengatakan apa-apa tentang itu
jere
3
Kompilator mengeluhkannya. Cobalah.
cfischer
4
@jere Periksa file header, itu menyatakan bahwa itu depricated
WDUK
Selain diskusi tentang apa itu praktik terbaik, saya melihat [NSOperationQueue currentQueue] yang mungkin menjawab pertanyaan tersebut. Tidak yakin tentang peringatan untuk penggunaannya.
Matt
menemukan peringatan ------ [NSOperationQueue currentQueue] tidak sama dengan dispatch_get_current_queue () ----- Terkadang mengembalikan null ---- dispatch_async (dispatch_get_global_queue (0, 0), ^ {NSLog (@ "q (0, 0) adalah% @ ", dispatch_get_current_queue ()); NSLog (@" cq (0,0) adalah% @ ", [NSOperationQueue currentQueue]);}); ----- q (0,0) adalah <OS_dispatch_queue_root: com.apple.root.default-qos [0x100195140]> cq (0,0) adalah (null) ----- dispatch_get_current_queue () tampaknya menjadi satu-satunya solusi yang saya lihat untuk melaporkan antrian saat ini dalam semua kondisi
godzilla

Jawaban:

64

Pola "jalankan di antrean apa pun yang digunakan penelepon" memang menarik, tetapi pada akhirnya bukan ide yang bagus. Antrean tersebut dapat berupa antrean prioritas rendah, antrean utama, atau antrean lain dengan properti ganjil.

Pendekatan favorit saya untuk ini adalah dengan mengatakan "blok penyelesaian berjalan pada implementasi yang ditentukan antrian dengan properti ini: x, y, z", dan biarkan pengiriman blok ke antrian tertentu jika pemanggil menginginkan kontrol lebih dari itu. Serangkaian properti khas untuk ditentukan akan menjadi sesuatu seperti "serial, non-reentrant, dan async sehubungan dengan antrian aplikasi-terlihat lainnya".

** EDIT **

Catfish_Man memberi contoh pada komentar di bawah, saya hanya menambahkannya ke jawabannya.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{ 
    dispatch_async(self.workQueue, ^{ 
        [self doSomeWork]; 
        dispatch_async(self.callbackQueue, completionHandler); 
    } 
}
Catfish_Man
sumber
7
Sangat setuju. Anda dapat melihat bahwa Apple selalu mengikuti ini; kapan pun Anda ingin melakukan sesuatu di antrean utama, Anda selalu perlu mengirim ke antrean utama karena apple selalu menjamin bahwa Anda berada di utas yang berbeda. Sebagian besar waktu Anda menunggu proses yang berjalan lama untuk menyelesaikan pengambilan / manipulasi data dan kemudian Anda dapat memprosesnya di latar belakang tepat di blok penyelesaian Anda dan kemudian hanya menempelkan panggilan UI ke blok pengiriman di antrean utama. Selain itu, selalu baik untuk mengikuti apa yang ditetapkan Apple dalam hal ekspektasi karena pengembang akan terbiasa dengan polanya.
Jack Lawrence
1
jawaban yang bagus .. tapi saya berharap untuk setidaknya beberapa kode contoh untuk menggambarkan apa yang Anda katakan
abbood
3
- (void) aMethodWithCompletionBlock: (dispatch_block_t) completeHandler {dispatch_async (self.workQueue, ^ {[self doSomeWork]; dispatch_async (self.callbackQueue, completeHandler);}}
Catfish_Man
(Untuk contoh yang benar-benar sepele)
Catfish_Man
3
Ini tidak mungkin dalam kasus umum karena mungkin (dan sebenarnya cukup mungkin) berada di lebih dari satu antrean secara bersamaan, karena dispatch_sync () dan dispatch_set_target_queue (). Ada beberapa subset dari kasus umum yang memungkinkan.
Catfish_Man
27

Ini pada dasarnya adalah pendekatan yang salah untuk API yang Anda jelaskan. Jika API menerima blok dan blok penyelesaian untuk dijalankan, fakta-fakta berikut harus benar:

  1. "Block to run" harus dijalankan pada antrian internal, misalnya antrian yang privat ke API dan karenanya sepenuhnya di bawah kendali API itu. Satu-satunya pengecualian untuk ini adalah jika API secara khusus menyatakan bahwa blok akan dijalankan di antrean utama atau salah satu antrean serentak global.

  2. Blok penyelesaian harus selalu dinyatakan sebagai tupel (antrian, blok) kecuali asumsi yang sama seperti untuk # 1 berlaku benar, misalnya blok penyelesaian akan dijalankan pada antrian global yang diketahui. Blok penyelesaian selanjutnya harus dikirim secara asinkron pada antrian yang diteruskan.

Ini bukan hanya poin gaya, mereka sepenuhnya diperlukan jika API Anda ingin aman dari kebuntuan atau perilaku edge-case lainnya yang AKAN menggantung Anda dari pohon terdekat suatu hari nanti. :-)

jkh
sumber
11
Kedengarannya masuk akal, tetapi untuk beberapa alasan itu bukan pendekatan yang diambil oleh Apple untuk API mereka sendiri: sebagian besar metode yang mengambil blok penyelesaian tidak juga mengambil antrian ...
cfischer
2
Benar, dan untuk sedikit memodifikasi pernyataan saya sebelumnya, jika jelas terlihat bahwa blok penyelesaian akan dijalankan pada antrian utama atau antrian serentak global. Saya akan mengubah jawaban saya untuk menunjukkan sebanyak mungkin.
jkh
Untuk mengomentari Apple yang tidak mengambil pendekatan itu: Apple tidak selalu "benar" per definisi. Argumen yang tepat selalu lebih kuat daripada otoritas tertentu, yang akan dikonfirmasi oleh ilmuwan mana pun. Saya pikir jawaban di atas menyatakannya dengan sangat baik dari perspektif arsitektur perangkat lunak yang tepat.
Werner Altewischer
14

Jawaban lainnya bagus, tapi bagi saya jawabannya struktural. Saya memiliki metode seperti ini di Singleton:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

yang memiliki dua dependensi, yaitu:

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

dan

typedef void (^simplest_block)(void); // also could use dispatch_block_t

Dengan cara itu saya memusatkan panggilan saya untuk dikirim ke utas lainnya.

Dan Rosenstark
sumber
12

Anda harus berhati-hati saat menggunakannya dispatch_get_current_queue. Dari file header:

Direkomendasikan untuk tujuan debugging dan logging saja:

Kode tidak boleh membuat asumsi apa pun tentang antrian yang dikembalikan, kecuali itu adalah salah satu antrian global atau antrian yang dibuat kode itu sendiri. Kode tidak boleh berasumsi bahwa eksekusi sinkron ke antrian aman dari kebuntuan jika antrian tersebut bukan yang dikembalikan oleh dispatch_get_current_queue ().

Anda dapat melakukan salah satu dari dua hal berikut:

  1. Simpan referensi ke antrean tempat Anda awalnya diposting (jika Anda membuatnya melalui dispatch_queue_create), dan gunakan sejak saat itu.

  2. Gunakan antrian yang ditentukan sistem lewat dispatch_get_global_queue, dan pantau antrean mana yang Anda gunakan.

Secara efektif sementara sebelumnya mengandalkan sistem untuk melacak antrian Anda, Anda harus melakukannya sendiri.

WDUK
sumber
16
Bagaimana kami dapat "menyimpan referensi ke antrian awal yang Anda posting" jika kami tidak dapat menggunakan dispatch_get_current_queue()untuk mengetahui antrian mana itu? Terkadang kode yang perlu mengetahui antrian mana yang dijalankan tidak memiliki kontrol atau pengetahuan apa pun tentangnya. Saya memiliki banyak kode yang dapat (dan harus) dijalankan pada antrian latar belakang tetapi kadang-kadang perlu memperbarui gui (bilah kemajuan, dll), dan oleh karena itu perlu dispatch_sync () ke antrian utama untuk operasi tersebut. Jika sudah ada di antrian utama, dispatch_sync () akan terkunci selamanya. Saya butuh waktu berbulan-bulan untuk mengubah kode saya untuk ini.
Abhi Beckert
3
Saya pikir NSURLConnection memberikan callback penyelesaiannya pada utas yang sama dengan asalnya. Apakah itu akan menggunakan API yang sama "dispatch_get_current_queue" untuk menyimpan antrian yang dipanggil untuk digunakan pada saat panggilan balik?
defactodeity
5

Apple sudah tidak digunakan lagi dispatch_get_current_queue(), tetapi meninggalkan lubang di tempat lain, jadi kami masih bisa mendapatkan antrean pengiriman saat ini:

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

Ini berfungsi untuk antrian utama setidaknya. Perhatikan, underlyingQueueproperti itu tersedia sejak iOS 8.

Jika Anda perlu melakukan blok penyelesaian dalam antrian asli, Anda juga dapat menggunakan OperationQueuesecara langsung, maksud saya tanpa GCD.

kelin
sumber
4

Bagi mereka yang masih perlu membandingkan antrian, Anda bisa membandingkan antrian dengan label mereka atau menentukan. Periksa https://stackoverflow.com/a/23220741/1531141 ini

alexey.hippie
sumber
0

Ini adalah jawaban saya juga. Jadi saya akan berbicara tentang kasus penggunaan kami.

Kami memiliki lapisan layanan dan lapisan UI (di antara lapisan lainnya). Lapisan layanan menjalankan tugas di latar belakang. (Tugas manipulasi data, tugas CoreData, panggilan Jaringan, dll.). Lapisan layanan memiliki beberapa antrian operasi untuk memenuhi kebutuhan lapisan UI.

Lapisan UI bergantung pada lapisan layanan untuk melakukan tugasnya dan kemudian menjalankan blok penyelesaian sukses. Blok ini dapat memiliki kode UIKit di dalamnya. Contoh penggunaan sederhana adalah untuk mendapatkan semua pesan dari server dan memuat ulang tampilan koleksi.

Di sini kami menjamin bahwa blok yang diteruskan ke lapisan layanan dikirim pada antrean tempat layanan dipanggil. Karena dispatch_get_current_queue adalah metode yang tidak digunakan lagi, kami menggunakan NSOperationQueue.currentQueue untuk mendapatkan antrean pemanggil saat ini. Catatan penting tentang properti ini.

Memanggil metode ini dari luar konteks operasi yang sedang berjalan biasanya menghasilkan nol yang dikembalikan.

Karena kami selalu menjalankan layanan kami pada antrean yang diketahui (antrean khusus kami dan antrean Utama), ini berfungsi dengan baik untuk kami. Kami memiliki kasus di mana serviceA dapat memanggil serviceB yang dapat memanggil serviceC. Karena kami mengontrol dari mana panggilan layanan pertama dilakukan, kami tahu layanan lainnya akan mengikuti aturan yang sama.

Jadi NSOperationQueue.currentQueue akan selalu mengembalikan salah satu Antrean kami atau MainQueue.

Kris Subramanian
sumber