Vladimir의 답변은 실제로 꽤 좋지만 여기서 더 많은 배경 지식을주고 싶습니다. 언젠가 누군가가 내 답장을 찾아서 도움이 될 수도 있습니다.
컴파일러는 소스 파일 (.c, .cc, .cpp, .m)을 객체 파일 (.o)로 변환합니다. 소스 파일 당 하나의 오브젝트 파일이 있습니다. 객체 파일에는 심볼, 코드 및 데이터가 포함됩니다. 운영 체제에서 오브젝트 파일을 직접 사용할 수 없습니다.
이제 동적 라이브러리 (.dylib), 프레임 워크,로드 가능한 번들 (.bundle) 또는 실행 가능한 바이너리를 빌드 할 때 이러한 오브젝트 파일은 링커에 의해 서로 연결되어 운영 체제가 "사용 가능한"것으로 간주하는 것을 생성합니다. 특정 메모리 주소로 직접로드합니다.
그러나 정적 라이브러리를 빌드 할 때 이러한 모든 오브젝트 파일은 단순히 큰 아카이브 파일에 추가되므로 정적 라이브러리의 확장자 (아카이브의 경우 .a)입니다. 따라서 .a 파일은 객체 (.o) 파일의 아카이브 일뿐입니다. 압축하지 않은 TAR 아카이브 또는 ZIP 아카이브를 생각하십시오. 단일 .a 파일을 전체 .o 파일보다 쉽게 복사 할 수 있습니다 (자바와 유사하게 배포하기 위해 .class 파일을 .jar 아카이브로 압축하는 Java와 유사).
바이너리를 정적 라이브러리 (= 아카이브)에 링크 할 때 링커는 아카이브에있는 모든 심볼의 테이블을 가져와 바이너리가 참조하는 심볼을 확인합니다. 링커가 참조 심볼을 포함하는 오브젝트 파일 만 실제로로드하며 링크 프로세스에서 고려합니다. 예를 들어, 아카이브에 50 개의 객체 파일이 있지만 바이너리에만 사용되는 심볼이 20 개만 포함 된 링커는 20 개만로드하고 나머지 30 개는 링크 프로세스에서 완전히 무시합니다.
이 언어는 컴파일 타임에 가능한 한 많은 작업을 시도하기 때문에 C 및 C ++ 코드에 매우 효과적입니다 (C ++에는 런타임 전용 기능이 있지만). 그러나 Obj-C는 다른 종류의 언어입니다. Obj-C는 런타임 기능에 크게 의존하며 많은 Obj-C 기능은 실제로 런타임 전용 기능입니다. Obj-C 클래스는 실제로 C 함수 또는 전역 C 변수 (적어도 현재 Obj-C 런타임에서)와 비교할 수있는 기호를 가지고 있습니다. 링커는 클래스의 참조 여부를 확인할 수 있으므로 사용중인 클래스를 결정할 수 있습니다. 정적 라이브러리에있는 객체 파일의 클래스를 사용하는 경우 링커에서 심볼이 사용중인 것으로 보이기 때문에이 객체 파일은 링커에 의해로드됩니다. 범주는 런타임 전용 기능이며 범주는 클래스 또는 함수와 같은 기호가 아니며 링커에서 범주의 사용 여부를 확인할 수 없습니다.
링커에서 Obj-C 코드가 포함 된 객체 파일을로드하면 모든 Obj-C 부분은 항상 연결 단계의 일부입니다. 따라서 범주를 포함하는 객체 파일이로드되면 해당 기호가 "사용 중"(클래스, 함수, 전역 변수 일 수 있음)으로 간주되어 범주가로드되어 런타임에 사용할 수있게됩니다. . 그러나 오브젝트 파일 자체가로드되지 않으면 런타임에 오브젝트 파일의 카테고리를 사용할 수 없습니다. 카테고리 만 포함 된 객체 파일 은 결코 그것을 포함하지 않기 때문에로드되지 에는 문자 링커는 것이 지금까지 "사용"생각을. 그리고 이것은 전체 문제입니다.
몇 가지 솔루션이 제안되었으며 이제이 모든 기능이 어떻게 작동하는지 알았으므로 제안 된 솔루션에 대해 다시 살펴 보겠습니다.
한 가지 해결책은 -all_load
링커 호출 에 추가 하는 것입니다. 링커 플래그는 실제로 무엇을합니까? 실제로는 링커에 다음과 같이 알려줍니다. " 사용중인 심볼이 있는지 여부에 관계없이 모든 아카이브의 모든 객체 파일을로드합니다 . '물론 작동하지만 큰 바이너리를 생성 할 수도 있습니다.
다른 해결책은 -force_load
아카이브 경로를 포함하여 링커 호출 에 추가 하는 것입니다. 이 플래그는와 정확히 동일 -all_load
하지만 지정된 아카이브에 대해서만 작동합니다 . 물론 이것은 잘 작동합니다.
가장 인기있는 솔루션은 -ObjC
링커 호출 하는 것입니다. 링커 플래그는 실제로 무엇을합니까? 이 플래그는 링커에 " Obj-C 코드가 포함되어있는 경우 모든 아카이브에서 모든 오브젝트 파일로드 "를 지시합니다 . "모든 Obj-C 코드"에는 범주가 포함됩니다. 이것은 잘 작동하며 Obj-C 코드가 포함되지 않은 객체 파일을 강제로로드하지 않습니다 (이것은 여전히 요청시로드됩니다).
또 다른 해결책은 다소 새로운 Xcode 빌드 설정 Perform Single-Object Prelink
입니다. 이 설정은 무엇을합니까? 사용 가능한 경우 모든 오브젝트 파일 (소스 파일 당 하나 있음)이 단일 오브젝트 파일로 병합됩니다 (실제 링크되지 않으므로 이름 PreLink )과이 단일 오브젝트 파일 (때로는 "마스터 오브젝트"라고도 함 파일 ")이 보관함에 추가됩니다. 이제 마스터 오브젝트 파일의 기호가 사용중인 것으로 간주되면 전체 마스터 오브젝트 파일이 사용중인 것으로 간주되므로 모든 Objective-C 부분이 항상로드됩니다. 클래스는 일반적인 기호이므로 이러한 정적 라이브러리의 단일 클래스를 사용하여 모든 범주를 가져 오는 것으로 충분합니다.
마지막 해결책은 블라디미르가 그의 대답의 끝에 추가 한 트릭입니다. 카테고리 만 선언하는 소스 파일에 " 가짜 기호 "를 넣으십시오 . 런타임에 범주 중 하나를 사용하려면 컴파일 타임에 가짜 기호 를 참조해야합니다. 이렇게하면 링커에 의해 오브젝트 파일이로드되므로 모든 Obj-C 코드가로드됩니다. 예를 들어 빈 함수 본문이있는 함수이거나 (호출 될 때 아무것도하지 않을 것입니다) 전역 변수에 액세스 할 수 있습니다 (예 : 전역 변수)int
한번 읽거나 쓴 후에는 이것으로 충분합니다). 위의 다른 모든 솔루션과 달리이 솔루션은 런타임에 사용 가능한 카테고리에 대한 제어를 컴파일 된 코드로 전환합니다 (링크 및 사용 가능하게하려면 심볼에 액세스합니다. 그렇지 않으면 심볼에 액세스하지 않고 링커는 무시 함). 그것).
그게 다야
아, 잠깐, 한 가지 더 있습니다 :
링커에는이라는 옵션이 -dead_strip
있습니다. 이 옵션은 무엇을합니까? 링커에서 객체 파일을로드하기로 결정한 경우, 객체 파일의 모든 심볼은 사용 여부에 관계없이 연결된 바이너리의 일부가됩니다. 예를 들어 객체 파일에는 100 개의 함수가 포함되어 있지만 이진은이 중 하나만 사용합니다. 객체 파일은 전체적으로 추가되거나 전혀 추가되지 않기 때문에 100 개의 모든 기능이 바이너리에 계속 추가됩니다. 링커에서는 일반적으로 오브젝트 파일을 부분적으로 추가 할 수 없습니다.
그러나 링커에 "데드 스트립"을 지시하면 링커는 먼저 모든 객체 파일을 이진 파일에 추가하고 모든 참조를 확인한 다음 마지막으로 이진 파일에서 사용하지 않는 심볼을 검색합니다. 사용하다). 사용하지 않는 것으로 확인 된 모든 기호는 최적화 단계의 일부로 제거됩니다. 위의 예에서 99 개의 사용하지 않는 기능이 다시 제거되었습니다. 이 같은 옵션을 사용하는 경우 매우 유용합니다 -load_all
, -force_load
또는 Perform Single-Object Prelink
이러한 옵션은 쉽게 어떤 경우에 극적 이진 크기를 날려 버릴 수 있기 때문에 죽은 스트립은 다시 사용하지 않는 코드와 데이터를 제거합니다.
데드 스트리핑은 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
하면 링커는 사용중인 심볼로 인해 라이브러리에서로드 한 오브젝트 파일을 빌드 로그에 인쇄합니다. 사용중인 것으로 간주되는 첫 번째 심볼 만 인쇄하지만 반드시 해당 오브젝트 파일을 사용하는 유일한 심볼은 아닙니다.