Dapatkah Anda membimbing saya cara menautkan pustaka statis ke proyek iPhone dengan benar. Saya menggunakan proyek perpustakaan statis ditambahkan ke proyek aplikasi sebagai ketergantungan langsung (target -> umum -> ketergantungan langsung) dan semua berfungsi OK, tetapi kategori. Kategori yang ditentukan di perpustakaan statis tidak berfungsi di aplikasi.
Jadi pertanyaan saya adalah bagaimana cara menambahkan perpustakaan statis dengan beberapa kategori ke proyek lain?
Dan secara umum, apa praktik terbaik untuk digunakan dalam kode proyek aplikasi dari proyek lain?
iphone
objective-c
static-libraries
categories
Vladimir
sumber
sumber
Jawaban:
Solusi: Pada Xcode 4.2, Anda hanya perlu pergi ke aplikasi yang menautkan ke perpustakaan (bukan perpustakaan itu sendiri) dan klik proyek di Project Navigator, klik target aplikasi Anda, lalu buat pengaturan aplikasi, lalu cari "Lainnya Tautan Bendera ", klik tombol +, dan tambahkan '-ObjC'. '-all_load' dan '-force_load' tidak lagi diperlukan.
Detail: Saya menemukan beberapa jawaban di berbagai forum, blog, dan dokumen Apple. Sekarang saya mencoba membuat ringkasan pendek dari pencarian dan eksperimen saya.
Masalah disebabkan oleh (kutipan dari apel. T&J Teknis QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html ):
Dan solusi mereka:
dan ada juga rekomendasi di FAQ Pengembangan iPhone:
dan deskripsi bendera:
* kita dapat menggunakan force_load untuk mengurangi ukuran biner aplikasi dan untuk menghindari konflik yang dapat menyebabkan all_load dalam beberapa kasus.
Ya, ini berfungsi dengan * .a file yang ditambahkan ke proyek. Namun saya punya masalah dengan proyek Lib ditambahkan sebagai ketergantungan langsung. Tetapi kemudian saya menemukan bahwa itu adalah kesalahan saya - proyek ketergantungan langsung mungkin tidak ditambahkan dengan benar. Ketika saya menghapusnya dan menambahkan lagi dengan langkah-langkah:
setelah itu semua berfungsi OK. Bendera "-ObjC" sudah cukup dalam kasus saya.
Saya juga tertarik dengan ide dari http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html blog. Penulis mengatakan ia dapat menggunakan kategori dari lib tanpa mengatur -all_load atau -ObjC flag. Dia hanya menambah kategori file h / m kosong antarmuka kelas dummy / implementasi untuk memaksa linker menggunakan file ini. Dan ya, trik ini berhasil.
Tetapi penulis juga mengatakan ia bahkan tidak membuat objek boneka. Mm ... Seperti yang saya temukan, kita harus secara eksplisit memanggil beberapa kode "nyata" dari file kategori. Jadi setidaknya fungsi kelas harus dipanggil. Dan kita bahkan tidak perlu kelas dummy. Fungsi c tunggal melakukan hal yang sama.
Jadi jika kita menulis file lib sebagai:
dan jika kita memanggil useMyLib (); di mana saja di proyek App maka di kelas apa pun kita dapat menggunakan metode kategori logSelf;
Dan lebih banyak blog bertema:
http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/
http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html
sumber
Jawaban dari Vladimir sebenarnya cukup bagus, namun, saya ingin memberikan sedikit latar belakang pengetahuan di sini. Mungkin suatu hari seseorang menemukan balasan saya dan mungkin merasa terbantu.
Kompiler mengubah file sumber (.c, .cc, .cpp, .m) menjadi file objek (.o). Ada satu file objek per file sumber. File objek berisi simbol, kode, dan data. File objek tidak dapat langsung digunakan oleh sistem operasi.
Sekarang ketika membangun perpustakaan dinamis (.dylib), kerangka kerja, bundel yang dapat dimuat (.bundle) atau biner yang dapat dieksekusi, file objek ini dihubungkan bersama oleh linker untuk menghasilkan sesuatu yang sistem operasi anggap "dapat digunakan", misalnya sesuatu yang dapat langsung memuat ke alamat memori tertentu.
Namun ketika membangun perpustakaan statis, semua file objek ini hanya ditambahkan ke file arsip besar, maka ekstensi perpustakaan statis (.a untuk arsip). Jadi file .a tidak lebih dari arsip file objek (.o). Pikirkan arsip TAR atau arsip ZIP tanpa kompresi. Ini hanya lebih mudah untuk menyalin satu file .a sekitar dari sejumlah file .o (mirip dengan Jawa, di mana Anda mengemas file .class ke dalam arsip .jar untuk distribusi mudah).
Saat menautkan biner ke pustaka statis (= arsip), tautan akan mendapatkan tabel semua simbol dalam arsip dan memeriksa simbol mana yang dirujuk oleh binari. Hanya file objek yang mengandung simbol yang direferensikan yang sebenarnya dimuat oleh linker dan dianggap oleh proses penautan. Misalnya jika arsip Anda memiliki 50 file objek, tetapi hanya 20 yang mengandung simbol yang digunakan oleh biner, hanya 20 yang dimuat oleh linker, 30 lainnya sepenuhnya diabaikan dalam proses penautan.
Ini berfungsi cukup baik untuk kode C dan C ++, karena bahasa-bahasa ini mencoba melakukan sebanyak mungkin pada waktu kompilasi (meskipun C ++ juga memiliki beberapa fitur runtime-only). Obj-C, bagaimanapun, adalah jenis bahasa yang berbeda. Obj-C sangat tergantung pada fitur runtime dan banyak fitur Obj-C sebenarnya hanya fitur runtime. Kelas Obj-C sebenarnya memiliki simbol yang sebanding dengan fungsi C atau variabel global C (setidaknya dalam runtime Obj-C saat ini). Linker dapat melihat apakah suatu kelas direferensikan atau tidak, sehingga dapat menentukan kelas yang digunakan atau tidak. Jika Anda menggunakan kelas dari file objek di perpustakaan statis, file objek ini akan dimuat oleh linker karena linker melihat simbol sedang digunakan. Kategori adalah fitur runtime-only, kategori bukan simbol seperti kelas atau fungsi dan itu juga berarti penghubung tidak dapat menentukan apakah suatu kategori sedang digunakan atau tidak.
Jika tautan memuat file objek yang berisi kode Obj-C, semua bagian Obj-C selalu menjadi bagian dari tahap penautan. Jadi jika file objek yang berisi kategori dimuat karena simbol apa pun dari itu dianggap "sedang digunakan" (baik itu kelas, baik itu fungsi, baik itu variabel global), kategori tersebut akan dimuat juga dan akan tersedia saat runtime . Namun jika file objek itu sendiri tidak dimuat, kategori di dalamnya tidak akan tersedia saat runtime. File objek yang berisi hanya kategori yang tidak pernah dimuat karena mengandung tidak ada simbol linker akan pernah mempertimbangkan "digunakan". Dan inilah keseluruhan masalahnya di sini.
Beberapa solusi telah diajukan dan sekarang setelah Anda tahu bagaimana semua ini bekerja bersama, mari kita lihat lagi solusi yang diusulkan:
Salah satu solusinya adalah menambahkan
-all_load
panggilan tautan. Apa yang sebenarnya akan dilakukan oleh bendera tautan? Sebenarnya itu memberitahu linker berikut ini " Muat semua file objek dari semua arsip terlepas dari apakah Anda melihat simbol yang digunakan atau tidak '. Tentu saja, itu akan berhasil; tetapi juga dapat menghasilkan biner yang agak besar.Solusi lain adalah menambahkan
-force_load
panggilan tautan termasuk jalur ke arsip. Bendera ini berfungsi persis seperti-all_load
, tetapi hanya untuk arsip yang ditentukan. Tentu saja ini akan berhasil juga.Solusi paling populer adalah menambahkan
-ObjC
panggilan tautan. Apa yang sebenarnya akan dilakukan oleh bendera tautan? Bendera ini memberi tahu penghubung " Muat semua file objek dari semua arsip jika Anda melihat bahwa mereka mengandung kode Obj-C ". Dan "kode Obj-C" termasuk kategori. Ini akan bekerja juga dan tidak akan memaksa memuat file objek yang tidak mengandung kode Obj-C (ini masih hanya dimuat sesuai permintaan).Solusi lain adalah pengaturan build Xcode yang agak baru
Perform Single-Object Prelink
. Apa yang akan dilakukan pengaturan ini? Jika diaktifkan, semua file objek (ingat, ada satu file per sumber) digabung bersama menjadi file objek tunggal (yang bukan penautan nyata, maka nama PreLink ) dan file objek tunggal ini (kadang-kadang juga disebut "objek master" file ") kemudian ditambahkan ke arsip. Jika sekarang simbol dari file objek master dianggap sedang digunakan, seluruh file objek master dianggap sedang digunakan dan dengan demikian semua bagian Objective-C selalu dimuat. Dan karena kelas adalah simbol normal, cukup menggunakan satu kelas dari pustaka statis untuk mendapatkan semua kategori.Solusi terakhir adalah trik yang ditambahkan Vladimir di akhir jawabannya. Tempatkan " simbol palsu " ke file sumber apa pun yang hanya menyatakan kategori. Jika Anda ingin menggunakan salah satu kategori saat runtime, pastikan Anda entah bagaimana mereferensikan simbol palsu pada waktu kompilasi, karena ini menyebabkan file objek dimuat oleh linker dan dengan demikian juga semua kode Obj-C di dalamnya. Misalnya itu bisa fungsi dengan badan fungsi kosong (yang tidak akan melakukan apa-apa ketika dipanggil) atau itu bisa menjadi variabel global yang diakses (misalnya global
int
setelah membaca atau sekali ditulis, ini sudah cukup). Tidak seperti semua solusi lain di atas, solusi ini menggeser kontrol tentang kategori mana yang tersedia saat runtime ke kode yang dikompilasi (jika ia ingin mereka ditautkan dan tersedia, ia mengakses simbol, jika tidak mengakses simbol dan linker akan mengabaikan Itu).Itu semua orang.
Oh, tunggu, ada satu hal lagi:
Linker memiliki opsi bernama
-dead_strip
. Apa yang dilakukan opsi ini? Jika linker memutuskan untuk memuat file objek, semua simbol file objek menjadi bagian dari biner yang ditautkan, apakah mereka digunakan atau tidak. Misalnya file objek berisi 100 fungsi, tetapi hanya satu dari mereka yang digunakan oleh biner, semua 100 fungsi masih ditambahkan ke biner karena file objek baik ditambahkan secara keseluruhan atau tidak ditambahkan sama sekali. Menambahkan file objek sebagian biasanya tidak didukung oleh tautan.Namun, jika Anda memberi tahu tautan ke "dead strip", tautan pertama akan menambahkan semua file objek ke biner, menyelesaikan semua referensi dan akhirnya memindai biner untuk simbol yang tidak digunakan (atau hanya digunakan oleh simbol lain yang tidak di menggunakan). Semua simbol yang ditemukan tidak digunakan kemudian dihapus sebagai bagian dari tahap optimasi. Dalam contoh di atas, 99 fungsi yang tidak digunakan dihapus lagi. Ini sangat berguna jika Anda menggunakan opsi seperti
-load_all
,-force_load
atauPerform Single-Object Prelink
karena opsi ini dapat dengan mudah meledakkan ukuran biner secara dramatis dalam beberapa kasus dan pengupasan mati akan menghapus kode dan data yang tidak digunakan lagi.Dead stripping berfungsi sangat baik untuk kode C (mis. Fungsi yang tidak digunakan, variabel dan konstanta dihapus seperti yang diharapkan) dan juga bekerja cukup baik untuk C ++ (mis. Kelas yang tidak digunakan dihapus). Itu tidak sempurna, dalam beberapa kasus beberapa simbol tidak dihapus meskipun akan baik-baik saja untuk menghapusnya, tetapi dalam kebanyakan kasus itu berfungsi dengan cukup baik untuk bahasa-bahasa ini.
Bagaimana dengan Obj-C? Lupakan saja! Tidak ada stripping mati untuk Obj-C. Karena Obj-C adalah bahasa fitur runtime, kompiler tidak dapat mengatakan pada waktu kompilasi apakah simbol benar-benar digunakan atau tidak. Misalnya kelas Obj-C tidak digunakan jika tidak ada kode yang langsung merujuknya, benar? Salah! Anda dapat secara dinamis membuat string yang berisi nama kelas, meminta pointer kelas untuk nama itu dan mengalokasikan kelas secara dinamis. Misalnya bukannya
Saya juga bisa menulis
Dalam kedua kasus
mmc
adalah referensi ke objek dari kelas "MyCoolClass", tetapi tidak ada referensi langsung ke kelas ini dalam sampel kode kedua (bahkan tidak nama kelas sebagai string statis). Semuanya terjadi hanya saat runtime. Dan itu meskipun kelas sebenarnya adalah simbol nyata. Bahkan lebih buruk untuk kategori, karena mereka bahkan bukan simbol nyata.Jadi jika Anda memiliki pustaka statis dengan ratusan objek, namun sebagian besar binari Anda hanya memerlukan beberapa di antaranya, Anda dapat memilih untuk tidak menggunakan solusi (1) hingga (4) di atas. Kalau tidak, Anda berakhir dengan binari yang sangat besar yang berisi semua kelas ini, meskipun sebagian besar dari mereka tidak pernah digunakan. Untuk kelas Anda biasanya tidak memerlukan solusi khusus sama sekali karena kelas memiliki simbol nyata dan selama Anda mereferensikannya secara langsung (tidak seperti dalam contoh kode kedua), tautan akan mengidentifikasi penggunaannya dengan cukup baik. Namun untuk kategori, pertimbangkan solusi (5), karena memungkinkan untuk hanya memasukkan kategori yang benar-benar Anda butuhkan.
Misalnya, jika Anda menginginkan kategori untuk NSData, misalnya menambahkan metode kompresi / dekompresi, Anda akan membuat file header:
dan file implementasi
Sekarang pastikan bahwa di mana saja dalam kode Anda
import_NSData_Compression()
dipanggil. Tidak masalah di mana namanya atau seberapa sering disebut. Sebenarnya itu tidak benar-benar harus dipanggil sama sekali, itu sudah cukup jika linker berpikir demikian. Misalnya Anda dapat meletakkan kode berikut di mana saja di proyek Anda:Anda tidak perlu memanggil
importCategories()
kode Anda, atribut akan membuat kompiler dan linker percaya bahwa itu disebut, bahkan jika tidak.Dan tip terakhir:
Jika Anda menambah
-whyload
panggilan tautan terakhir, penghubung akan mencetak dalam log build file objek mana dari perpustakaan mana ia memuat karena simbol yang digunakan. Ini hanya akan mencetak simbol pertama yang dianggap digunakan, tetapi itu tidak selalu merupakan satu-satunya simbol yang digunakan dalam file objek itu.sumber
-whyload
, mencoba men-debug mengapa linker melakukan sesuatu bisa sangat sulit!Dead Code Stripping
diBuild Settings>Linking
. Apakah sama dengan-dead_strip
menambahkanOther Linker Flags
?-ObjC
, jadi saya mencoba peretasan Anda tetapi komplain"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
. Saya memasukkanimport_NSString_jsonObject
Framework tertanam saya bernamaUtility
, dan menambahkan#import <Utility/Utility.h>
dengan__attribute__
pernyataan di akhir sayaAppDelegate.h
.Masalah ini telah diperbaiki di LLVM . Perbaikan dikirimkan sebagai bagian dari LLVM 2.9 Versi Xcode pertama yang berisi perbaikan adalah Xcode 4.2 pengiriman dengan LLVM 3.0. Penggunaan
-all_load
atau-force_load
tidak lagi diperlukan ketika bekerja dengan XCode 4.2-ObjC
masih diperlukan.sumber
-ObjC
flag masih diperlukan dan akan selalu. Solusinya adalah penggunaan-all_load
atau-force_load
. Dan itu tidak diperlukan lagi. Saya memperbaiki jawaban saya di atas.Inilah yang perlu Anda lakukan untuk menyelesaikan masalah ini sepenuhnya saat kompilasi pustaka statis Anda:
Pergi ke Pengaturan Xcode Build dan atur Perform Single-Object Prelink ke YES atau
GENERATE_MASTER_OBJECT_FILE = YES
dalam file konfigurasi build Anda.Secara default, linker menghasilkan file .o untuk setiap file .m. Jadi kategori mendapat file .o yang berbeda. Ketika tautan melihat file .o perpustakaan statis, itu tidak membuat indeks semua simbol per kelas (Runtime akan, tidak peduli apa).
Arahan ini akan meminta linker untuk mengemas semua objek menjadi satu file .o besar dan dengan ini memaksa linker yang memproses perpustakaan statis untuk mendapatkan indeks semua kategori kelas.
Harapan yang mengklarifikasi itu.
sumber
Salah satu faktor yang jarang disebutkan setiap kali diskusi penghubung pustaka statis muncul adalah kenyataan bahwa Anda juga harus memasukkan kategori itu sendiri dalam fase pembuatan-> menyalin file dan mengkompilasi sumber pustaka statis itu sendiri .
Apple juga tidak menekankan fakta ini dalam Menggunakan Perpustakaan Statis mereka yang baru-baru ini diterbitkan di iOS .
Saya menghabiskan sepanjang hari mencoba segala macam variasi -objC dan -all_load dll. Tapi tidak ada yang keluar dari itu .. pertanyaan ini membawa masalah itu menjadi perhatian saya. (jangan salah paham .. Anda masih harus melakukan hal -objC .. tapi ini lebih dari itu).
juga tindakan lain yang selalu membantu saya adalah bahwa saya selalu membangun perpustakaan statis yang disertakan terlebih dahulu sendiri .. maka saya membangun aplikasi terlampir ..
sumber
Anda mungkin perlu memiliki kategori di tajuk "publik" pustaka statis: #import "MyStaticLib.h"
sumber