Apa cara terbaik untuk menyelesaikan tabrakan namespace Objective-C?

174

Objective-C tidak memiliki ruang nama; itu seperti C, semuanya dalam satu namespace global. Praktik umum adalah untuk mengawali kelas dengan inisial, misalnya jika Anda bekerja di IBM, Anda bisa mengawali mereka dengan "IBM"; jika Anda bekerja untuk Microsoft, Anda dapat menggunakan "MS"; dan seterusnya. Terkadang inisial merujuk pada proyek, misalnya kelas awalan Adium dengan "AI" (karena tidak ada perusahaan di belakangnya sehingga Anda dapat mengambil inisial). Awalan Apple kelas dengan NS dan mengatakan awalan ini dicadangkan untuk Apple saja.

Sejauh ini sangat baik. Tetapi menambahkan 2 hingga 4 huruf ke nama kelas di depan adalah namespace yang sangat, sangat terbatas. Misalnya MS atau AI dapat memiliki arti yang sama sekali berbeda (AI bisa jadi Kecerdasan Buatan misalnya) dan beberapa pengembang lain mungkin memutuskan untuk menggunakannya dan membuat kelas yang sama namanya. Bang , tabrakan namespace.

Oke, jika ini adalah tabrakan antara salah satu kelas Anda dan salah satu kerangka eksternal yang Anda gunakan, Anda dapat dengan mudah mengubah penamaan kelas Anda, bukan masalah besar. Tetapi bagaimana jika Anda menggunakan dua kerangka kerja eksternal, kedua kerangka kerja yang Anda tidak memiliki sumbernya dan Anda tidak dapat mengubahnya? Aplikasi Anda terhubung dengan keduanya dan Anda mendapat konflik nama. Bagaimana Anda menyelesaikannya? Apa cara terbaik untuk mengatasi mereka sedemikian rupa sehingga Anda masih dapat menggunakan kedua kelas?

Di C Anda dapat mengatasi ini dengan tidak menautkan langsung ke perpustakaan, sebaliknya Anda memuat perpustakaan saat runtime, menggunakan dlopen (), kemudian menemukan simbol yang Anda cari menggunakan dlsym () dan menetapkannya ke simbol global (bahwa Anda dapat memberi nama dengan cara apa pun yang Anda suka) dan kemudian mengaksesnya melalui simbol global ini. Misalnya jika Anda memiliki konflik karena beberapa pustaka C memiliki fungsi bernama open (), Anda bisa mendefinisikan variabel bernama myOpen dan mengarahkannya ke fungsi open () pustaka, jadi ketika Anda ingin menggunakan sistem open () , Anda hanya menggunakan open () dan ketika Anda ingin menggunakan yang lain, Anda mengaksesnya melalui pengenal myOpen.

Apakah sesuatu yang serupa mungkin terjadi di Objective-C dan jika tidak, apakah ada solusi cerdas dan rumit lainnya yang dapat Anda gunakan menyelesaikan konflik namespace? Ada ide?


Memperbarui:

Untuk memperjelas ini: jawaban yang menyarankan cara menghindari tabrakan namespace di muka atau cara membuat namespace yang lebih baik tentu disambut; namun, saya tidak akan menerimanya sebagai jawaban karena mereka tidak menyelesaikan masalah saya. Saya memiliki dua perpustakaan dan nama kelas mereka bertabrakan. Saya tidak bisa mengubahnya; Saya tidak punya sumber keduanya. Tabrakan sudah ada dan tips tentang bagaimana hal itu bisa dihindari sebelumnya tidak akan membantu lagi. Saya dapat meneruskannya ke pengembang kerangka kerja ini dan berharap mereka memilih namespace yang lebih baik di masa depan, tetapi untuk saat ini saya sedang mencari solusi untuk bekerja dengan kerangka kerja sekarang dalam satu aplikasi. Ada solusi untuk membuat ini mungkin?

Mecki
sumber
7
Anda memiliki pertanyaan yang bagus (apa yang harus dilakukan jika Anda membutuhkan dua kerangka kerja yang memiliki tabrakan nama) tetapi itu terkubur dalam teks. Merevisi untuk membuatnya lebih jelas, dan Anda akan menghindari jawaban sederhana seperti yang Anda miliki sekarang.
benzado
4
Ini adalah keluhan terbesar saya dengan desain bahasa Objective-C saat ini. Lihatlah jawaban di bawah ini; yang benar-benar menjawab pertanyaan (NSBundle membongkar, menggunakan DO, dll) adalah peretasan yang mengerikan yang seharusnya tidak perlu untuk sesuatu yang sepele seperti menghindari konflik namespace.
erikprice
@erikprice: Amin. Saya belajar obj-c, dan mengenai masalah ini. Datang ke sini mencari solusi sederhana .... lumpuh.
Dave Mateer
1
Sebagai catatan, secara teknis baik C dan Objective-C memberikan dukungan untuk beberapa ruang nama - tidak persis apa yang dicari OP. Lihat objectivistc.tumblr.com/post/3340816080/…
Hmm, saya tidak tahu itu. Jenis keputusan desain yang mengerikan, bukan?
Nico

Jawaban:

47

Jika Anda tidak perlu menggunakan kelas dari kedua kerangka kerja secara bersamaan, dan Anda menargetkan platform yang mendukung NSBundle unloading (OS X 10.4 atau lebih baru, tidak ada dukungan GNUStep), dan kinerja benar-benar bukan masalah bagi Anda, saya percaya bahwa Anda bisa memuat satu kerangka kerja setiap kali Anda perlu menggunakan kelas dari itu, dan kemudian membongkar dan memuat yang lain ketika Anda perlu menggunakan kerangka lainnya.

Ide awal saya adalah menggunakan NSBundle untuk memuat salah satu framework, lalu menyalin atau mengganti nama kelas-kelas di dalam framework itu, dan kemudian memuat framework lainnya. Ada dua masalah dengan ini. Pertama, saya tidak bisa menemukan fungsi untuk menyalin data yang diarahkan untuk mengubah nama atau menyalin kelas, dan kelas lain mana pun dalam kerangka kerja pertama yang merujuk kelas yang diubah namanya sekarang akan merujuk kelas dari kerangka kerja lain.

Anda tidak perlu menyalin atau mengganti nama kelas jika ada cara untuk menyalin data yang ditunjukkan oleh IMP. Anda bisa membuat kelas baru dan kemudian menyalin lebih dari ivar, metode, properti, dan kategori. Jauh lebih banyak pekerjaan, tetapi itu mungkin. Namun, Anda masih akan memiliki masalah dengan kelas-kelas lain dalam kerangka kerja yang merujuk kelas yang salah.

EDIT: Perbedaan mendasar antara runtime C dan Objective-C adalah, seperti yang saya pahami, ketika pustaka dimuat, fungsi-fungsi di pustaka berisi pointer ke simbol apa pun yang mereka referensi, sedangkan di Objective-C, mereka berisi representasi string dari nama simbolnya. Jadi, dalam contoh Anda, Anda dapat menggunakan dlsym untuk mendapatkan alamat simbol dalam memori dan melampirkannya ke simbol lain. Kode lain di perpustakaan masih berfungsi karena Anda tidak mengubah alamat simbol asli. Objective-C menggunakan tabel pencarian untuk memetakan nama kelas ke alamat, dan ini merupakan pemetaan 1-1, sehingga Anda tidak dapat memiliki dua kelas dengan nama yang sama. Jadi, untuk memuat kedua kelas, salah satu dari mereka harus memiliki nama yang diubah. Namun, ketika kelas lain perlu mengakses salah satu kelas dengan nama itu,

Michael Buckley
sumber
5
Saya percaya bundle unloading tidak didukung hingga 10,5 atau lebih baru.
Quinn Taylor
93

Awalan kelas Anda dengan awalan unik pada dasarnya adalah satu-satunya pilihan tetapi ada beberapa cara untuk membuat ini kurang memberatkan dan jelek. Ada diskusi panjang opsi di sini . Favorit saya adalah @compatibility_aliasdirektif kompilasi Objective-C (dijelaskan di sini ). Anda dapat menggunakan @compatibility_aliasuntuk "mengubah nama" kelas, memungkinkan Anda untuk memberi nama kelas Anda menggunakan FQDN atau awalan semacam itu:

@interface COM_WHATEVER_ClassName : NSObject
@end

@compatibility_alias ClassName COM_WHATEVER_ClassName
// now ClassName is an alias for COM_WHATEVER_ClassName

@implementation ClassName //OK
//blah
@end

ClassName *myClass; //OK

Sebagai bagian dari strategi lengkap, Anda bisa mengawali semua kelas Anda dengan awalan unik seperti FQDN dan kemudian membuat header dengan semua @compatibility_alias(Saya akan membayangkan Anda bisa secara otomatis menghasilkan kata header).

Kelemahan dari awalan seperti ini adalah bahwa Anda harus memasukkan nama kelas yang benar (misalnya di COM_WHATEVER_ClassNameatas) dalam apa pun yang membutuhkan nama kelas dari string selain kompiler. Khususnya, @compatibility_aliasadalah arahan kompiler, bukan fungsi runtime sehingga NSClassFromString(ClassName)akan gagal (kembali nil) - Anda harus menggunakan NSClassFromString(COM_WHATERVER_ClassName). Anda dapat menggunakan ibtoolfase build untuk memodifikasi nama kelas di Interface Builder nib / xib sehingga Anda tidak perlu menulis COM_WHATEVER _... lengkap di Interface Builder.

Peringatan terakhir: karena ini adalah kompiler directive (dan yang tidak jelas pada saat itu), itu mungkin tidak portabel di kompiler. Secara khusus, saya tidak tahu apakah itu bekerja dengan frontend dentang dari proyek LLVM, meskipun itu harus bekerja dengan LLVM-GCC (LLVM menggunakan frontend GCC).

Barry Wark
sumber
4
Suara positif untuk kompatibilitas_alias, saya tidak tahu tentang itu! Terima kasih. Tapi itu tidak menyelesaikan masalah saya. Saya tidak dalam kontrol untuk mengubah awalan dari kerangka mana pun yang saya gunakan, saya hanya memiliki mereka dalam bentuk biner dan mereka bertabrakan. Apa yang harus saya lakukan?
Mecki
Di file mana (.h atau .m) haruskah baris @compatibility_alias pergi?
Alex Basson
1
@Alex_Basson @compatibility_alias mungkin harus masuk di header sehingga terlihat di mana deklarasi @interface terlihat.
Barry Wark
Saya ingin tahu apakah Anda dapat menggunakan @compatibility_alias dengan nama protokol, atau definisi typedef, atau yang lainnya?
Ali
Pikiran bagus di sini. Bisakah metode swizzling digunakan untuk mencegah NSClassFromString (ClassName) mengembalikan nil untuk kelas alias? Mungkin akan sulit untuk menemukan semua metode yang menggunakan nama kelas.
Dickey Singh
12

Beberapa orang telah membagikan beberapa kode rumit dan pintar yang mungkin membantu menyelesaikan masalah. Beberapa saran mungkin berhasil, tetapi semuanya kurang dari ideal, dan beberapa di antaranya benar-benar tidak menyenangkan untuk diterapkan. (Kadang-kadang peretasan yang jelek tidak bisa dihindari, tetapi saya mencoba menghindarinya kapan pun saya bisa.) Dari sudut pandang praktis, berikut adalah saran saya.

  1. Dalam kasus apa pun, beri tahu pengembang kedua kerangka konflik, dan jelaskan bahwa kegagalan mereka untuk menghindari dan / atau menanganinya menyebabkan Anda mengalami masalah bisnis nyata, yang dapat diterjemahkan menjadi pendapatan bisnis yang hilang jika tidak terselesaikan. Tekankan bahwa sementara menyelesaikan konflik yang ada berdasarkan per kelas adalah perbaikan yang tidak terlalu mengganggu, mengubah keseluruhan awalan mereka (atau menggunakan satu jika tidak saat ini, dan memalukan pada mereka!) Adalah cara terbaik untuk memastikan bahwa mereka tidak akan menyelesaikannya. lihat masalah yang sama lagi.
  2. Jika konflik penamaan terbatas pada sekelompok kelas yang cukup kecil, lihat apakah Anda dapat mengatasi hanya kelas-kelas itu, terutama jika salah satu kelas yang bertentangan tidak digunakan oleh kode Anda, secara langsung atau tidak langsung. Jika demikian, lihat apakah vendor akan menyediakan versi kustom kerangka kerja yang tidak termasuk kelas yang saling bertentangan. Jika tidak, jujurlah tentang fakta bahwa ketidakfleksibelan mereka mengurangi ROI Anda dari penggunaan kerangka kerja mereka. Jangan merasa bersalah karena menjadi pemalu karena alasan - pelanggan selalu benar. ;-)
  3. Jika satu kerangka kerja lebih "dapat disingkirkan", Anda dapat mempertimbangkan untuk menggantinya dengan kerangka kerja lain (atau kombinasi kode), baik pihak ketiga atau homebrew. (Yang terakhir adalah kasus terburuk yang tidak diinginkan, karena pasti akan menimbulkan biaya bisnis tambahan, baik untuk pengembangan dan pemeliharaan.) Jika Anda melakukannya, beri tahu vendor kerangka itu persis mengapa Anda memutuskan untuk tidak menggunakan kerangka kerja mereka.
  4. Jika kedua kerangka kerja dianggap sama tak terpisahkan dari aplikasi Anda, telusuri cara-cara untuk faktor penggunaan salah satu dari mereka untuk satu atau lebih proses yang terpisah, mungkin berkomunikasi melalui DO seperti yang disarankan Louis Gerbarg. Tergantung pada tingkat komunikasi, ini mungkin tidak seburuk yang Anda harapkan. Beberapa program (termasuk QuickTime, saya percaya) menggunakan pendekatan ini untuk memberikan keamanan lebih terperinci yang disediakan dengan menggunakan profil sandbox Seatbelt di Leopard , sehingga hanya subset spesifik dari kode Anda yang diizinkan untuk melakukan operasi kritis atau sensitif. Kinerja akan menjadi tradeoff, tetapi mungkin satu-satunya pilihan Anda

Saya menduga bahwa biaya lisensi, ketentuan, dan jangka waktu dapat mencegah tindakan instan pada salah satu poin ini. Semoga Anda dapat menyelesaikan konflik secepat mungkin. Semoga berhasil!

Quinn Taylor
sumber
8

Ini kotor, tetapi Anda bisa menggunakan objek terdistribusi untuk mempertahankan salah satu kelas hanya di alamat program bawahan dan RPC untuk itu. Itu akan menjadi berantakan jika Anda melewati banyak hal bolak-balik (dan mungkin tidak mungkin jika kedua kelas secara langsung memanipulasi tampilan, dll).

Ada solusi potensial lainnya, tetapi banyak dari mereka bergantung pada situasi yang tepat. Secara khusus, apakah Anda menggunakan runtime modern atau lama, apakah Anda gemuk atau arsitektur tunggal, 32 atau 64 bit, rilis OS apa yang Anda targetkan, apakah Anda menghubungkan secara dinamis, menghubungkan secara statis, atau apakah Anda punya pilihan, dan apakah berpotensi boleh melakukan sesuatu yang mungkin memerlukan pemeliharaan untuk pembaruan perangkat lunak baru.

Jika Anda benar-benar putus asa, yang bisa Anda lakukan adalah:

  1. Tidak terhubung langsung dengan salah satu perpustakaan
  2. Menerapkan versi alternatif dari rutinitas runtc objc yang mengubah nama pada waktu buka (checkout proyek objc4 , apa yang sebenarnya perlu Anda lakukan tergantung pada sejumlah pertanyaan yang saya tanyakan di atas, tetapi itu harus dimungkinkan terlepas dari apa pun jawabannya. ).
  3. Gunakan sesuatu seperti mach_override untuk menyuntikkan implementasi baru Anda
  4. Muat pustaka baru menggunakan metode normal, itu akan melalui rutin linker yang ditambal dan mengubah className-nya

Hal di atas akan menjadi sangat padat karya, dan jika Anda perlu mengimplementasikannya pada banyak lengkungan dan versi runtime yang berbeda itu akan sangat tidak menyenangkan, tetapi pasti dapat dibuat untuk bekerja.

Louis Gerbarg
sumber
4

Sudahkah Anda mempertimbangkan untuk menggunakan fungsi runtime (/usr/include/objc/runtime.h) untuk mengkloning salah satu kelas yang bertentangan ke kelas yang tidak bertabrakan, dan kemudian memuat kerangka kerja kelas bertabrakan? (Ini akan membutuhkan kerangka kerja bertabrakan yang dimuat pada waktu yang berbeda untuk bekerja.)

Anda dapat memeriksa kelas ivars, metode (dengan nama dan alamat implementasi) dan nama dengan runtime, dan membuat Anda sendiri secara dinamis untuk memiliki tata letak ivar yang sama, nama metode / alamat implementasi, dan hanya berbeda dengan nama (untuk menghindari tabrakan)

xtophyr
sumber
3

Situasi putus asa membutuhkan tindakan putus asa. Sudahkah Anda mempertimbangkan meretas kode objek (atau file perpustakaan) dari salah satu perpustakaan, mengubah simbol bertabrakan menjadi nama alternatif - dengan panjang yang sama tetapi dengan ejaan yang berbeda (tetapi, rekomendasi, panjang nama yang sama)? Sangat jahat.

Tidak jelas apakah kode Anda secara langsung memanggil dua fungsi dengan nama yang sama tetapi implementasi yang berbeda atau apakah konflik tidak langsung (juga tidak jelas apakah itu membuat perbedaan). Namun, setidaknya ada peluang di luar bahwa penggantian nama akan berhasil. Mungkin juga merupakan ide, untuk meminimalkan perbedaan dalam ejaan, sehingga jika simbol berada dalam urutan diurutkan dalam sebuah tabel, penamaan ulang tidak memindahkan hal-hal yang rusak. Hal-hal seperti pencarian biner menjadi kesal jika array yang mereka cari tidak dalam urutan seperti yang diharapkan.

Jonathan Leffler
sumber
Mengubah perpustakaan pada disk tidak perlu dipertanyakan, karena lisensi tidak akan mengizinkan ini. Mengubah simbol dalam memori mungkin dilakukan, tetapi saya tidak melihat cara bagaimana hal ini dapat dilakukan (memuat lib ke memori, memodifikasinya lalu meneruskannya ke tautan dinamis ... tidak melihat metode untuk itu).
Mecki
3
OK - waktu untuk memutuskan perpustakaan mana yang kurang penting, dan untuk mencari penggantinya. Dan jelaskan kepada orang yang Anda bayar tetapi Anda meninggalkan bahwa alasan Anda berubah adalah karena tabrakan ini dan mereka dapat mempertahankan bisnis Anda dengan memperbaiki masalah Anda.
Jonathan Leffler
2

@compatibility_alias akan dapat menyelesaikan konflik namespace kelas, mis

@compatibility_alias NewAliasClass OriginalClass;

Namun, ini tidak akan menyelesaikan salah satu enums, typedef, atau collision namespace protokol . Selain itu, itu tidak bermain dengan baik dengan @classdecl maju dari kelas asli. Karena sebagian besar kerangka kerja akan datang dengan hal-hal non-kelas ini seperti typedefs, Anda mungkin tidak akan dapat memperbaiki masalah namespace dengan hanya kompatibilitas_alias.

Saya melihat masalah yang sama dengan Anda , tetapi saya memiliki akses ke sumber dan sedang membangun kerangka kerja. Solusi terbaik yang saya temukan untuk ini adalah menggunakan @compatibility_aliaskondisional dengan #defines untuk mendukung enum / typedefs / protokol / dll. Anda dapat melakukan ini secara kondisional pada unit kompilasi untuk header yang dimaksud untuk meminimalkan risiko memperluas barang-barang di kerangka bertabrakan lainnya.

Michael Chinen
sumber
1

Tampaknya masalahnya adalah Anda tidak dapat mereferensikan file header dari kedua sistem dalam unit terjemahan yang sama (file sumber). Jika Anda membuat pembungkus objektif-c di sekitar pustaka (menjadikannya lebih bermanfaat dalam proses), dan hanya menyertakan header untuk setiap pustaka dalam implementasi kelas pembungkus, yang secara efektif akan memisahkan tabrakan nama.

Saya tidak punya cukup pengalaman dengan ini dalam tujuan-c (baru memulai), tetapi saya percaya itulah yang akan saya lakukan di C.

Chrish
sumber
1
Tetapi bukankah Anda masih akan mengalami tabrakan jika Anda mencoba memasukkan kedua header pembungkus dalam file yang sama (karena mereka masing-masing harus menyertakan header kerangka kerja masing-masing)?
Wilco
0

Membuat awalan file adalah solusi paling sederhana yang saya ketahui. Cocoadev memiliki halaman namespace yang merupakan upaya komunitas untuk menghindari tabrakan namespace. Jangan ragu untuk menambahkan milik Anda sendiri ke daftar ini, saya percaya itulah gunanya.

http://www.cocoadev.com/index.pl?CreoseYourOwnPrefix Anda

Ryan Townshend
sumber
Bagaimana awalan file membantu jika objek yang ditentukan dalam file menyebabkan masalah? Saya pasti dapat memanipulasi file h, tetapi kemudian linker tidak akan menemukan objek sama sekali ketika menyukai kerangka kerja: - /
Mecki
Saya percaya, ini lebih baik menjadi praktik terbaik daripada solusi untuk masalah awal.
Ali
Ini sama sekali tidak menjawab pertanyaan
Madbreaks
0

Jika Anda memiliki tabrakan, saya sarankan Anda berpikir keras tentang bagaimana Anda dapat memperbaiki salah satu kerangka kerja dari aplikasi Anda. Memiliki tabrakan menunjukkan bahwa keduanya melakukan hal yang sama, dan Anda mungkin bisa menggunakan kerangka tambahan hanya dengan refactoring aplikasi Anda. Ini tidak hanya akan memecahkan masalah namespace Anda, tetapi juga akan membuat kode Anda lebih kuat, lebih mudah dirawat, dan lebih efisien.

Lebih dari solusi yang lebih teknis, jika saya berada di posisi Anda ini akan menjadi pilihan saya.

Allyn
sumber
0

Jika tabrakan hanya pada level tautan statis maka Anda dapat memilih perpustakaan mana yang digunakan untuk menyelesaikan simbol:

cc foo.o -ldog bar.o -lcat

Jika foo.odan bar.okedua referensi simbol ratmaka libdogakan menyelesaikan foo.o's ratdan libcatakan menyelesaikan bar.o' s rat.

wcochran
sumber
0

Hanya sebuah pemikiran .. tidak diuji atau terbukti dan bisa menjadi tanda tetapi dalam apakah Anda mempertimbangkan untuk menulis adaptor untuk kelas yang Anda gunakan dari kerangka kerja yang lebih sederhana .. atau setidaknya antarmuka mereka?

Jika Anda menulis pembungkus di sekitar kerangka kerja yang lebih sederhana (atau yang paling sedikit antarmuka Anda aksesnya) tidak akan mungkin untuk mengkompilasi pembungkus itu ke perpustakaan. Mengingat perpustakaan dikompilasi dan hanya header -nya yang perlu didistribusikan, Anda akan secara efektif menyembunyikan kerangka kerja yang mendasarinya dan akan bebas untuk menggabungkannya dengan kerangka kerja kedua dengan bentrok.

Saya menghargai tentu saja bahwa ada saat-saat ketika Anda perlu menggunakan kelas dari kedua kerangka kerja pada saat yang sama, namun, Anda dapat menyediakan pabrik untuk adaptor kelas lebih lanjut dari kerangka kerja itu. Di belakang titik itu saya kira Anda akan perlu sedikit refactoring untuk mengekstrak antarmuka yang Anda gunakan dari kedua kerangka kerja yang seharusnya memberikan titik awal yang baik bagi Anda untuk membangun pembungkus Anda.

Anda dapat membangun di atas pustaka saat Anda dan saat Anda membutuhkan fungsionalitas lebih lanjut dari pustaka terbungkus, dan cukup kompilasi ulang saat Anda mengubahnya.

Sekali lagi, sama sekali tidak terbukti tetapi rasanya ingin menambahkan perspektif. semoga membantu :)

menandai
sumber
-1

Jika Anda memiliki dua kerangka kerja yang memiliki nama fungsi yang sama, Anda bisa mencoba memuat kerangka kerja secara dinamis. Itu akan tidak berlaku, tetapi mungkin. Bagaimana melakukannya dengan kelas Objective-C, saya tidak tahu. Saya menduga NSBundlekelas akan memiliki metode yang akan memuat kelas tertentu.

MaddTheSane
sumber