Câu trả lời từ Vladimir thực sự khá tốt, tuy nhiên, tôi muốn cung cấp thêm một số kiến thức nền tảng ở đây. Có thể một ngày nào đó ai đó tìm thấy câu trả lời của tôi và có thể thấy nó hữu ích.
Trình biên dịch biến đổi các tệp nguồn (.c, .cc, .cpp, .m) thành các tệp đối tượng (.o). Có một tệp đối tượng cho mỗi tệp nguồn. Các tệp đối tượng chứa các ký hiệu, mã và dữ liệu. Các tệp đối tượng không thể sử dụng trực tiếp bởi hệ điều hành.
Bây giờ khi xây dựng thư viện động (.dylib), khung, gói có thể tải (.bundle) hoặc tệp nhị phân có thể thực thi, các tệp đối tượng này được liên kết với nhau để tạo ra thứ gì đó mà hệ điều hành coi là "có thể sử dụng được", ví dụ như thứ gì đó có thể tải trực tiếp đến một địa chỉ bộ nhớ cụ thể.
Tuy nhiên, khi xây dựng một thư viện tĩnh, tất cả các tệp đối tượng này chỉ được thêm vào một tệp lưu trữ lớn, do đó mở rộng các thư viện tĩnh (.a để lưu trữ). Vì vậy, một tệp .a không gì khác hơn là một tệp lưu trữ các tệp đối tượng (.o). Hãy nghĩ về một kho lưu trữ TAR hoặc một kho lưu trữ ZIP mà không nén. Việc sao chép một tệp .a xung quanh dễ dàng hơn so với toàn bộ các tệp .o (tương tự như Java, nơi bạn đóng gói các tệp. Class vào kho lưu trữ .jar để phân phối dễ dàng).
Khi liên kết nhị phân với thư viện tĩnh (= archive), trình liên kết sẽ nhận được một bảng gồm tất cả các ký hiệu trong kho lưu trữ và kiểm tra xem các ký hiệu nào trong số các ký hiệu này được tham chiếu bởi các nhị phân. Chỉ các tệp đối tượng chứa các ký hiệu được tham chiếu thực sự được tải bởi trình liên kết và được xem xét bởi quá trình liên kết. Ví dụ: nếu kho lưu trữ của bạn có 50 tệp đối tượng, nhưng chỉ có 20 biểu tượng được sử dụng bởi nhị phân, chỉ 20 tệp đó được tải bởi trình liên kết, 30 tệp còn lại hoàn toàn bị bỏ qua trong quá trình liên kết.
Điều này hoạt động khá tốt đối với mã C và C ++, vì các ngôn ngữ này cố gắng làm nhiều nhất có thể vào thời gian biên dịch (mặc dù C ++ cũng có một số tính năng chỉ dành cho thời gian chạy). Obj-C, tuy nhiên, là một loại ngôn ngữ khác. Obj-C phụ thuộc rất nhiều vào các tính năng thời gian chạy và nhiều tính năng Obj-C thực sự là các tính năng chỉ chạy. Các lớp Obj-C thực sự có các ký hiệu có thể so sánh với các hàm C hoặc các biến C toàn cầu (ít nhất là trong thời gian chạy Obj-C hiện tại). Một trình liên kết có thể xem một lớp có được tham chiếu hay không, vì vậy nó có thể xác định một lớp đang được sử dụng hay không. Nếu bạn sử dụng một lớp từ một tệp đối tượng trong một thư viện tĩnh, tệp đối tượng này sẽ được trình liên kết tải lên vì trình liên kết nhìn thấy một biểu tượng đang được sử dụng. Danh mục là một tính năng chỉ dành cho thời gian chạy, các danh mục không phải là biểu tượng như các lớp hoặc hàm và điều đó cũng có nghĩa là một trình liên kết không thể xác định xem một danh mục có được sử dụng hay không.
Nếu trình liên kết tải một tệp đối tượng chứa mã Obj-C, tất cả các phần Obj-C của nó luôn là một phần của giai đoạn liên kết. Vì vậy, nếu một tệp đối tượng chứa các danh mục được tải vì bất kỳ ký hiệu nào từ nó được coi là "đang sử dụng" (có thể là một lớp, có thể là một biến toàn cục), các thể loại cũng được tải và sẽ có sẵn trong thời gian chạy . Tuy nhiên, nếu tệp đối tượng tự nó không được tải, các thể loại trong đó sẽ không có sẵn trong thời gian chạy. Một đối tượng tập tin có chứa chỉ loại được không bao giờ được nạp bởi vì nó có chứa không có biểu tượng các mối liên kết sẽ không bao giờ xem xét "sử dụng". Và đây là toàn bộ vấn đề ở đây.
Một số giải pháp đã được đề xuất và bây giờ bạn biết làm thế nào tất cả những điều này kết hợp với nhau, chúng ta hãy có cái nhìn khác về giải pháp được đề xuất:
Một giải pháp là thêm -all_load
vào cuộc gọi liên kết. Cờ liên kết đó thực sự sẽ làm gì? Trên thực tế, nó nói với trình liên kết " Tải tất cả các tệp đối tượng của tất cả các tài liệu lưu trữ bất kể bạn có thấy bất kỳ biểu tượng nào được sử dụng hay không ". Tất nhiên, điều đó sẽ hoạt động; nhưng nó cũng có thể tạo ra các nhị phân khá lớn.
Một giải pháp khác là thêm -force_load
vào lệnh gọi liên kết bao gồm đường dẫn đến kho lưu trữ. Cờ này hoạt động chính xác như thế -all_load
, nhưng chỉ cho kho lưu trữ được chỉ định. Tất nhiên điều này sẽ làm việc như là tốt.
Giải pháp phổ biến nhất là thêm -ObjC
vào cuộc gọi liên kết. Cờ liên kết đó thực sự sẽ làm gì? Cờ này cho trình liên kết " Tải tất cả các tệp đối tượng từ tất cả các tài liệu lưu trữ nếu bạn thấy rằng chúng có chứa bất kỳ mã Obj-C nào ". Và "bất kỳ mã Obj-C" bao gồm các danh mục. Điều này cũng sẽ hoạt động và nó sẽ không buộc tải các tệp đối tượng không chứa mã Obj-C (những tệp này vẫn chỉ được tải theo yêu cầu).
Một giải pháp khác là cài đặt xây dựng Xcode khá mới Perform Single-Object Prelink
. Cài đặt này sẽ làm gì? Nếu được bật, tất cả các tệp đối tượng (hãy nhớ, có một tệp cho mỗi tệp nguồn) được hợp nhất với nhau thành một tệp đối tượng (đó không phải là liên kết thực, do đó tên PreLink ) và tệp đối tượng duy nhất này (đôi khi còn được gọi là "đối tượng chính tập tin ") sau đó được thêm vào kho lưu trữ. Nếu bây giờ bất kỳ ký hiệu nào của tệp đối tượng chính được xem xét sử dụng, toàn bộ tệp đối tượng chính sẽ được xem xét sử dụng và do đó tất cả các phần Objective-C của nó luôn được tải. Và vì các lớp là các ký hiệu bình thường, nên đủ để sử dụng một lớp từ thư viện tĩnh như vậy để có được tất cả các danh mục.
Giải pháp cuối cùng là mẹo mà Vladimir thêm vào cuối câu trả lời của mình. Đặt một " biểu tượng giả " vào bất kỳ tệp nguồn nào chỉ khai báo các danh mục. Nếu bạn muốn sử dụng bất kỳ danh mục nào trong thời gian chạy, hãy đảm bảo rằng bằng cách nào đó bạn tham chiếu biểu tượng giả một khi đã đọc hoặc đã viết, điều này là đủ). Không giống như tất cả các giải pháp khác ở trên, giải pháp này chuyển điều khiển về các danh mục có sẵn trong thời gian chạy sang mã được biên dịch (nếu nó muốn được liên kết và có sẵn, nó truy cập biểu tượng, nếu không nó không truy cập vào biểu tượng và trình liên kết sẽ bỏ qua nó). khi biên dịch, vì điều này làm cho tệp đối tượng được tải bởi trình liên kết và do đó cũng có tất cả mã Obj-C trong đó. Ví dụ, nó có thể là một hàm có thân hàm rỗng (sẽ không làm gì khi được gọi) hoặc nó có thể là biến toàn cục được truy cập (ví dụ: toàn cụcint
Đó là tất cả mọi người.
Oh, đợi đã, còn một điều nữa:
Trình liên kết có một tùy chọn được đặt tên -dead_strip
. Tùy chọn này làm gì? Nếu trình liên kết quyết định tải một tệp đối tượng, tất cả các ký hiệu của tệp đối tượng trở thành một phần của nhị phân được liên kết, cho dù chúng có được sử dụng hay không. Ví dụ: một tệp đối tượng chứa 100 hàm, nhưng chỉ một trong số chúng được sử dụng bởi nhị phân, tất cả 100 hàm vẫn được thêm vào nhị phân vì các tệp đối tượng được thêm vào hoặc toàn bộ chúng không được thêm vào. Thêm một phần đối tượng thường không được hỗ trợ bởi các trình liên kết.
Tuy nhiên, nếu bạn nói với trình liên kết là "dải chết", trước tiên trình liên kết sẽ thêm tất cả các tệp đối tượng vào tệp nhị phân, giải quyết tất cả các tham chiếu và cuối cùng quét nhị phân cho các ký hiệu không được sử dụng (hoặc chỉ được sử dụng bởi các ký hiệu khác không có trong sử dụng). Tất cả các biểu tượng được tìm thấy không được sử dụng sau đó được xóa như một phần của giai đoạn tối ưu hóa. Trong ví dụ trên, 99 hàm không sử dụng được loại bỏ một lần nữa. Điều này rất hữu ích nếu bạn sử dụng các tùy chọn như -load_all
, -force_load
hoặc Perform Single-Object Prelink
bởi vì các tùy chọn này có thể dễ dàng làm tăng đáng kể kích thước nhị phân trong một số trường hợp và tước chết sẽ xóa lại mã và dữ liệu không sử dụng.
Tước chết hoạt động rất tốt cho mã C (ví dụ: các hàm không sử dụng, các biến và hằng được loại bỏ như mong đợi) và nó cũng hoạt động khá tốt cho C ++ (ví dụ: các lớp không sử dụng được loại bỏ). Nó không hoàn hảo, trong một số trường hợp, một số biểu tượng không bị xóa mặc dù có thể xóa chúng đi, nhưng trong hầu hết các trường hợp, nó hoạt động khá tốt đối với các ngôn ngữ này.
Còn Obj-C thì sao? Quên nó đi! Không có tước chết cho Obj-C. Vì Obj-C là ngôn ngữ tính năng thời gian chạy, trình biên dịch không thể nói tại thời điểm biên dịch cho dù một biểu tượng có thực sự được sử dụng hay không. Ví dụ, một lớp Obj-C không được sử dụng nếu không có mã trực tiếp tham chiếu đến nó, đúng không? Sai lầm! Bạn có thể tự động xây dựng một chuỗi chứa một tên lớp, yêu cầu một con trỏ lớp cho tên đó và tự động phân bổ lớp. Ví dụ thay vì
MyCoolClass * mcc = [[MyCoolClass alloc] init];
Tôi cũng có thể viết
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
Trong cả hai trường hợp mmc
là một tham chiếu đến một đối tượng của lớp "MyCoolClass", nhưng không có tham chiếu trực tiếp đến lớp này trong mẫu mã thứ hai (thậm chí không phải tên lớp là một chuỗi tĩnh). Mọi thứ chỉ xảy ra trong thời gian chạy. Và đó là mặc dù các lớp học là thực sự những biểu tượng thực sự. Nó thậm chí còn tồi tệ hơn cho các thể loại, vì chúng thậm chí không phải là biểu tượng thực sự.
Vì vậy, nếu bạn có một thư viện tĩnh với hàng trăm đối tượng, nhưng hầu hết các nhị phân của bạn chỉ cần một vài trong số chúng, bạn có thể không muốn sử dụng các giải pháp (1) đến (4) ở trên. Nếu không, bạn kết thúc với các nhị phân rất lớn chứa tất cả các lớp này, mặc dù hầu hết chúng không bao giờ được sử dụng. Đối với các lớp bạn thường không cần bất kỳ giải pháp đặc biệt nào vì các lớp có ký hiệu thực và miễn là bạn tham chiếu chúng trực tiếp (không phải trong mẫu mã thứ hai), trình liên kết sẽ tự xác định cách sử dụng của chúng khá tốt. Tuy nhiên, đối với các danh mục, hãy xem xét giải pháp (5), vì nó chỉ có thể bao gồm các danh mục bạn thực sự cần.
Ví dụ: nếu bạn muốn một danh mục cho NSData, ví dụ: thêm phương thức nén / giải nén vào nó, bạn sẽ tạo một tệp tiêu đề:
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
và một tập tin thực hiện
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
Bây giờ chỉ cần đảm bảo rằng bất cứ nơi nào trong mã của bạn import_NSData_Compression()
được gọi. Không quan trọng nó được gọi ở đâu hoặc tần suất được gọi như thế nào. Trên thực tế, nó thực sự không cần phải được gọi, nó là đủ nếu trình liên kết nghĩ như vậy. Ví dụ: bạn có thể đặt đoạn mã sau ở bất cứ đâu trong dự án của bạn:
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
Bạn không cần phải gọi importCategories()
mã của mình, thuộc tính sẽ làm cho trình biên dịch và trình liên kết tin rằng nó được gọi, ngay cả trong trường hợp không.
Và một mẹo cuối cùng:
Nếu bạn thêm -whyload
vào lệnh gọi liên kết cuối cùng, trình liên kết sẽ in trong nhật ký xây dựng tệp đối tượng mà thư viện đã tải từ đó vì sử dụng ký hiệu nào. Nó sẽ chỉ in biểu tượng đầu tiên được xem xét sử dụng, nhưng đó không nhất thiết là biểu tượng duy nhất được sử dụng của tệp đối tượng đó.