คำตอบจาก Vladimir นั้นค่อนข้างดีอย่างไรก็ตามฉันต้องการให้ความรู้พื้นฐานเพิ่มเติมที่นี่ บางทีวันหนึ่งอาจมีคนพบคำตอบของฉันและอาจเป็นประโยชน์
คอมไพเลอร์แปลงไฟล์ต้นฉบับ (.c, .cc, .cpp, .m) เป็นไฟล์วัตถุ (.o) มีหนึ่งอ็อบเจ็กต์ไฟล์ต่อไฟล์ต้นฉบับ ไฟล์วัตถุมีสัญลักษณ์รหัสและข้อมูล ไฟล์วัตถุไม่สามารถใช้งานได้โดยตรงจากระบบปฏิบัติการ
ตอนนี้เมื่อสร้าง dynamic library (.dylib) เฟรมเวิร์กบันเดิลที่โหลดได้ (.bundle) หรือไบนารีที่ปฏิบัติการไฟล์อ็อบเจ็กต์เหล่านี้จะเชื่อมโยงเข้าด้วยกันโดย linker เพื่อสร้างสิ่งที่ระบบปฏิบัติการพิจารณาว่า "ใช้งานได้" เช่นบางสิ่งบางอย่าง โหลดโดยตรงไปยังที่อยู่หน่วยความจำที่เฉพาะเจาะจง
อย่างไรก็ตามเมื่อสร้างไลบรารีแบบสแตติกไฟล์อ็อบเจ็กต์เหล่านี้ทั้งหมดจะถูกเพิ่มลงในไฟล์เก็บถาวรขนาดใหญ่ดังนั้นส่วนขยายของสแตติกไลบรารี (.a สำหรับไฟล์เก็บถาวร) ดังนั้นไฟล์. a จึงไม่มีอะไรมากกว่าไฟล์เก็บถาวรของไฟล์ object (.o) นึกถึงไฟล์เก็บถาวร TAR หรือไฟล์ ZIP โดยไม่บีบอัด มันง่ายกว่าที่จะคัดลอกไฟล์. a ไฟล์หนึ่งไฟล์ที่อยู่รอบ ๆ ไฟล์. o (คล้ายกับ Java ที่คุณเก็บไฟล์. class ไว้ในไฟล์. jar เพื่อการแจกจ่ายที่ง่าย)
เมื่อทำการเชื่อมโยงไบนารีไปยังไลบรารีแบบสแตติก (= ไฟล์เก็บถาวร) ตัวลิงก์จะได้รับตารางของสัญลักษณ์ทั้งหมดในไฟล์เก็บถาวรและตรวจสอบว่าสัญลักษณ์ใดที่อ้างอิงโดยไบนารี เฉพาะอ็อบเจ็กต์ไฟล์ที่มีสัญลักษณ์อ้างอิงเท่านั้นที่ถูกโหลดโดย linker และถูกพิจารณาโดยกระบวนการลิงก์ เช่นถ้าไฟล์เก็บถาวรของคุณมีไฟล์วัตถุ 50 ไฟล์ แต่มีเพียง 20 ไฟล์ที่มีสัญลักษณ์ที่ใช้โดยไบนารีเฉพาะไฟล์ 20 เท่านั้นที่โหลดโดยลิงเกอร์ไฟล์อื่น ๆ 30 ไฟล์จะถูกละเว้นในกระบวนการลิงก์
วิธีนี้ใช้งานได้ดีสำหรับรหัส C และ C ++ เนื่องจากภาษาเหล่านี้พยายามทำมากที่สุดเท่าที่จะเป็นไปได้ในเวลารวบรวม (แม้ว่า C ++ จะมีคุณสมบัติแบบรันไทม์เท่านั้น) อย่างไรก็ตาม Obj-C เป็นภาษาที่แตกต่าง Obj-C ขึ้นอยู่กับคุณสมบัติของรันไทม์เป็นอย่างมากและคุณสมบัติ Obj-C จำนวนมากนั้นเป็นคุณสมบัติของรันไทม์เท่านั้น คลาส Obj-C มีสัญลักษณ์เทียบเท่ากับฟังก์ชัน C หรือตัวแปร C ทั่วโลก (อย่างน้อยใน Obj-C runtime ปัจจุบัน) ตัวเชื่อมโยงสามารถดูได้ว่ามีการอ้างอิงคลาสหรือไม่ดังนั้นจึงสามารถพิจารณาว่ามีการใช้งานคลาสหรือไม่ หากคุณใช้คลาสจากไฟล์อ็อบเจ็กต์ในไลบรารีแบบสแตติกไฟล์อ็อบเจ็กต์นี้จะถูกโหลดโดยตัวลิงก์เนื่องจากตัวลิงก์จะเห็นสัญลักษณ์กำลังถูกใช้งาน หมวดหมู่เป็นคุณสมบัติแบบรันไทม์เท่านั้นหมวดหมู่ไม่ใช่สัญลักษณ์เช่นคลาสหรือฟังก์ชันและนั่นหมายความว่าตัวเชื่อมโยงไม่สามารถระบุได้ว่าหมวดหมู่นั้นใช้งานอยู่หรือไม่
หากลิงเกอร์โหลดไฟล์อ็อบเจ็กต์ที่มีรหัส Obj-C ชิ้นส่วน Obj-C ทั้งหมดของมันจะเป็นส่วนหนึ่งของระยะการลิงก์เสมอ ดังนั้นหากไฟล์ออบเจ็กต์ที่มีหมวดหมู่ถูกโหลดเนื่องจากสัญลักษณ์ใด ๆ จากมันถูกพิจารณาว่า "ใช้งาน" (ไม่ว่าจะเป็นคลาสไม่ว่าจะเป็นฟังก์ชั่นไม่ว่าจะเป็นตัวแปรทั่วโลก) หมวดหมู่ก็จะโหลดเช่นกัน . แต่ถ้าไม่ได้โหลดออบเจ็กต์ไฟล์เองหมวดหมู่ในนั้นจะไม่สามารถใช้งานได้ในขณะทำงาน ไฟล์อ็อบเจ็กต์ที่มีหมวดหมู่เท่านั้นไม่เคยถูกโหลดเนื่องจากไม่มีสัญลักษณ์ที่ linker เคยพิจารณาว่า "กำลังใช้" และนี่คือปัญหาทั้งหมดที่นี่
มีการเสนอแนวทางแก้ไขหลายอย่างแล้วและตอนนี้คุณก็รู้ว่าการเล่นทั้งหมดนี้เข้าด้วยกันได้อย่างไรมาดูการแก้ปัญหาที่เสนออีกครั้ง:
ทางออกหนึ่งคือการเพิ่ม-all_load
การโทรลิงเกอร์ ตัวเชื่อมโยงนั้นตั้งค่าสถานะอะไรจริง " โหลดไฟล์ออบเจ็กต์ทั้งหมดของไฟล์เก็บถาวรทั้งหมดโดยไม่คำนึงว่าคุณเห็นสัญลักษณ์ใด ๆ ที่ใช้งานอยู่หรือไม่ 'แน่นอนว่ามันใช้งานได้ แต่มันอาจสร้างไบนารีที่ค่อนข้างใหญ่
อีกวิธีคือการเพิ่ม-force_load
การเรียก linker รวมถึงเส้นทางไปยังที่เก็บถาวร แฟล็กนี้ทำงานเหมือน-all_load
กันทุกประการ แต่สำหรับไฟล์เก็บถาวรที่ระบุเท่านั้น แน่นอนว่ามันจะได้ผลเช่นกัน
ทางออกที่นิยมที่สุดคือการเพิ่ม-ObjC
การโทรลิงเกอร์ ตัวเชื่อมโยงนั้นตั้งค่าสถานะอะไรจริง แฟล็กนี้บอกตัวลิงก์ " โหลดไฟล์อ็อบเจ็กต์ทั้งหมดจากไฟล์เก็บถาวรทั้งหมดหากคุณเห็นว่ามีโค้ด Obj-C ใด ๆ " และ "รหัส Obj-C ใด ๆ " รวมถึงหมวดหมู่ สิ่งนี้จะทำงานได้ดีและจะไม่บังคับให้โหลดไฟล์ออบเจ็กต์ที่ไม่มีรหัส Obj-C (ซึ่งยังคงโหลดตามความต้องการเท่านั้น)
Perform Single-Object Prelink
วิธีการแก้ปัญหาอีกประการหนึ่งคือการสร้างการตั้งค่า Xcode ค่อนข้างใหม่ การตั้งค่านี้จะทำอะไร หากเปิดใช้งานไฟล์ออบเจ็กต์ทั้งหมด (จำไว้ว่ามีหนึ่งไฟล์ต่อไฟล์ต้นฉบับ) จะถูกรวมเข้าด้วยกันเป็นไฟล์ออบเจ็กต์เดียว (นั่นไม่ใช่การเชื่อมโยงจริงดังนั้นชื่อPreLink ) และไฟล์ออบเจ็กต์เดี่ยวนี้ ไฟล์ ") จะถูกเพิ่มลงในไฟล์เก็บถาวร หากตอนนี้มีการพิจารณาสัญลักษณ์ใด ๆ ของไฟล์วัตถุต้นแบบไฟล์วัตถุหลักทั้งหมดจะถูกนำมาใช้และทำให้ชิ้นส่วน Objective-C ทั้งหมดของมันถูกโหลดอยู่เสมอ และเนื่องจากคลาสเป็นสัญลักษณ์ปกติก็พอที่จะใช้คลาสเดียวจากไลบรารีแบบสแตติกเพื่อรับหมวดหมู่ทั้งหมดได้เช่นกัน
ทางออกสุดท้ายคือกลอุบายของ Vladimir ที่ท้ายคำตอบของเขา วาง " สัญลักษณ์ปลอม " ลงในไฟล์ต้นฉบับที่ประกาศหมวดหมู่เท่านั้น หากคุณต้องการใช้หมวดหมู่ใด ๆ ที่รันไทม์ตรวจสอบให้แน่ใจว่าคุณอ้างถึงสัญลักษณ์ปลอมในเวลารวบรวมเนื่องจากจะทำให้ไฟล์วัตถุถูกโหลดโดย linker และทำให้รหัส Obj-C ทั้งหมดอยู่ในนั้นด้วย เช่นมันอาจจะเป็นฟังก์ชั่นที่มีฟังก์ชั่นที่ว่างเปล่าของร่างกาย (ซึ่งจะไม่ทำอะไรเมื่อถูกเรียก) หรือมันอาจจะเป็นตัวแปรทั่วโลกเข้าถึงได้ (เช่นทั่วโลกint
เมื่ออ่านหรือเขียนครั้งเดียวก็เพียงพอแล้ว) แตกต่างจากโซลูชันอื่นทั้งหมดด้านบนโซลูชันนี้เลื่อนการควบคุมเกี่ยวกับหมวดหมู่ที่สามารถใช้งานได้ในขณะทำงานกับรหัสที่รวบรวม (หากต้องการให้เชื่อมโยงและพร้อมใช้งานจะเข้าถึงสัญลักษณ์มิฉะนั้นจะไม่สามารถเข้าถึงสัญลักษณ์ได้ มัน).
นั่นคือคนทั้งหมด
โอ้รอมีอีกสิ่งหนึ่งที่: ลิงเกอร์มีตัวเลือกในการตั้งชื่อ
-dead_strip
ตัวเลือกนี้ทำอะไร หาก linker ตัดสินใจโหลดไฟล์อ็อบเจ็กต์สัญลักษณ์ทั้งหมดของอ็อบเจ็กต์ไฟล์จะกลายเป็นส่วนหนึ่งของไบนารีที่เชื่อมโยงไม่ว่าจะถูกใช้หรือไม่ก็ตาม เช่นไฟล์วัตถุที่มี 100 ฟังก์ชั่น แต่มีเพียงหนึ่งในนั้นจะถูกใช้โดยไบนารีฟังก์ชั่นทั้ง 100 ยังคงถูกเพิ่มลงในไบนารีเพราะไฟล์วัตถุจะถูกเพิ่มโดยรวมหรือไม่ได้เพิ่มเลย การเพิ่มไฟล์วัตถุบางส่วนโดยปกติแล้ว linkers ไม่รองรับ
อย่างไรก็ตามหากคุณบอกลิงเกอร์ว่า "เดตสตริป" ตัวลิงก์จะเพิ่มไฟล์ออบเจ็กต์ทั้งหมดลงในไบนารีแก้ไขการอ้างอิงทั้งหมดและสุดท้ายสแกนไบนารีสำหรับสัญลักษณ์ที่ไม่ได้ใช้งาน (หรือใช้โดยสัญลักษณ์อื่นที่ไม่ใช่ ใช้). สัญลักษณ์ทั้งหมดที่พบว่าไม่ได้ใช้งานจะถูกลบออกจากนั้นเป็นส่วนหนึ่งของขั้นตอนการปรับให้เหมาะสม ในตัวอย่างข้างต้นฟังก์ชันที่ไม่ได้ใช้ 99 ฟังก์ชันจะถูกลบออกอีกครั้ง นี้จะเป็นประโยชน์มากถ้าคุณใช้ตัวเลือกเช่น-load_all
, -force_load
หรือPerform Single-Object Prelink
เพราะตัวเลือกเหล่านี้สามารถระเบิดขนาดไบนารีอย่างมากในบางกรณีและปอกตายจะลบรหัสไม่ได้ใช้และข้อมูลอีกครั้ง
การลอกแบบ Dead ทำงานได้ดีมากสำหรับรหัส C (เช่นฟังก์ชั่นที่ไม่ได้ใช้ตัวแปรและค่าคงที่จะถูกลบออกตามที่คาดไว้) และใช้งานได้ค่อนข้างดีสำหรับ C ++ (เช่นการลบคลาสที่ไม่ได้ใช้) มันไม่สมบูรณ์แบบในบางกรณีสัญลักษณ์บางอย่างไม่ได้ถูกลบแม้ว่ามันจะโอเคที่จะลบออก แต่ในกรณีส่วนใหญ่มันใช้งานได้ดีสำหรับภาษาเหล่านี้
สิ่งที่เกี่ยวกับ Obj-C ลืมมันซะ! ไม่มีการลอกแบบตายสำหรับ Obj-C เนื่องจาก Obj-C เป็นภาษาคุณลักษณะรันไทม์คอมไพเลอร์ไม่สามารถพูดได้ในเวลาคอมไพล์ว่ามีการใช้งานสัญลักษณ์หรือไม่ เช่นคลาส Obj-C ไม่ได้ใช้งานหากไม่มีรหัสอ้างอิงโดยตรงถูกต้องหรือไม่ ไม่ถูกต้อง! คุณสามารถสร้างสตริงที่มีชื่อคลาสแบบไดนามิกขอตัวชี้คลาสสำหรับชื่อนั้นและจัดสรรคลาสได้แบบไดนามิก เช่นแทน
MyCoolClass * mcc = [[MyCoolClass alloc] init];
ฉันยังสามารถเขียน
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
ในทั้งสองกรณีmmc
เป็นการอ้างอิงไปยังวัตถุของคลาส "MyCoolClass" แต่ไม่มีการอ้างอิงโดยตรงไปยังคลาสนี้ในตัวอย่างรหัสที่สอง (ไม่แม้แต่ชื่อคลาสเป็นสแตติกสตริง) ทุกอย่างเกิดขึ้นที่รันไทม์เท่านั้น และถึงแม้ว่าชั้นเรียนจะเป็นสัญลักษณ์ที่แท้จริง มันยิ่งแย่ลงสำหรับหมวดหมู่เพราะพวกเขาไม่ได้เป็นสัญลักษณ์ที่แท้จริง
ดังนั้นหากคุณมีไลบรารีแบบสแตติกที่มีวัตถุหลายร้อยรายการ แต่ไบนารีส่วนใหญ่ของคุณต้องการเพียงบางส่วนเท่านั้นคุณอาจไม่ต้องการใช้โซลูชัน (1) ถึง (4) ด้านบน มิฉะนั้นคุณจะจบลงด้วยไบนารีที่มีขนาดใหญ่มากซึ่งมีคลาสทั้งหมดเหล่านี้แม้ว่าส่วนใหญ่จะไม่เคยใช้ สำหรับคลาสคุณมักไม่ต้องการโซลูชันพิเศษเลยเนื่องจากคลาสมีสัญลักษณ์จริงและตราบใดที่คุณอ้างอิงโดยตรง (ไม่ใช่ในตัวอย่างโค้ดที่สอง) ตัวลิงก์จะระบุการใช้งานของตัวเองได้ค่อนข้างดี อย่างไรก็ตามสำหรับหมวดหมู่ให้พิจารณาวิธีแก้ปัญหา (5) เนื่องจากเป็นไปได้ที่จะรวมเฉพาะหมวดหมู่ที่คุณต้องการจริงๆ
เช่นถ้าคุณต้องการหมวดหมู่สำหรับ NSData เช่นการเพิ่มวิธีการบีบอัด / คลายการบีบอัดให้คุณสร้างไฟล์ส่วนหัว:
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
และไฟล์การใช้งาน
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
ตอนนี้ให้ตรวจสอบให้แน่ใจว่าimport_NSData_Compression()
มีการเรียกใช้รหัสใด ๆ ไม่สำคัญว่าจะถูกเรียกหรือความถี่ที่ถูกเรียก ที่จริงแล้วมันไม่จำเป็นต้องถูกเรียกเลยมันก็เพียงพอแล้วหากลิงเกอร์คิดเช่นนั้น เช่นคุณสามารถใส่รหัสต่อไปนี้ได้ทุกที่ในโครงการของคุณ:
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
คุณไม่จำเป็นต้องโทรimportCategories()
ในรหัสของคุณเลยแอตทริบิวต์นั้นจะทำให้คอมไพเลอร์และลิงเกอร์เชื่อว่ามันถูกเรียกใช้แม้ว่าในกรณีที่มันไม่ได้เป็น
และเคล็ดลับสุดท้าย:
หากคุณเพิ่ม-whyload
ไปยังการเชื่อมโยงการโทรสุดท้ายตัวเชื่อมโยงจะพิมพ์ในบิลด์บิลด์ซึ่งเป็นไฟล์ออบเจ็กต์ที่ไลบรารี่ใดที่มันโหลดเนื่องจากสัญลักษณ์ที่ใช้งานอยู่ มันจะพิมพ์สัญลักษณ์แรกที่พิจารณาว่าใช้งานอยู่เท่านั้น แต่นั่นไม่จำเป็นต้องเป็นสัญลักษณ์เดียวที่ใช้งานของไฟล์อ็อบเจ็กต์นั้น