programing

정적 라이브러리의 목표 C 카테고리

newsource 2023. 4. 20. 21:31

정적 라이브러리의 목표 C 카테고리

스태틱 라이브러리와 아이폰 프로젝트를 적절히 연계하는 방법을 안내해주실 수 있나요?앱 프로젝트에 추가된 정적 라이브러리 프로젝트를 직접 종속성(타깃 -> 일반 -> 직접 종속성)으로 사용하고 있으며, 모든 작업은 정상이지만 카테고리는 정상입니다.정적 라이브러리에 정의된 범주가 앱에서 작동하지 않습니다.

그래서 어떻게 다른 프로젝트에 카테고리가 있는 정적 라이브러리를 추가할 것인가 하는 것입니다.

그리고 일반적으로 다른 프로젝트의 앱 프로젝트 코드에서 사용하는 베스트 프랙티스는 무엇입니까?

솔루션:Xcode 4.2에서는 라이브러리에 링크되어 있는 애플리케이션(라이브러리 자체가 아님)으로 이동하여 Project Navigator에서 프로젝트를 클릭하고, 앱의 타깃을 클릭한 후 설정을 빌드한 다음, "Other Linker Flags"를 검색하여 "-ObJC"를 추가하고, "all_load"와 "force"는 더 이상 필요하지 않습니다.

상세:나는 다양한 포럼, 블로그, 애플 문서에서 몇 가지 답을 찾았습니다.이제 검색과 실험을 간략하게 요약해 보겠습니다.

이 문제는 (Apple Technical Q&A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html)에서 확인)에 의해 발생하였습니다.

Objective-C는 각 함수(또는 Objective-C의 메서드)에 대한 링커 기호를 정의하지 않습니다. 대신 링커 기호는 각 클래스에 대해서만 생성됩니다.기존 클래스를 카테고리와 확장하면 링커는 코어 클래스 구현과 카테고리 구현의 오브젝트 코드를 관련짓는 것을 인식하지 않습니다.이로 인해 결과 응용 프로그램에서 생성된 개체가 카테고리에 정의된 선택기에 응답하지 않습니다.

그리고 그 솔루션은 다음과 같습니다.

이 문제를 해결하려면 정적 라이브러리가 -ObjC 옵션을 링커에 전달해야 합니다.이 플래그를 사용하면 링커는 Objective-C 클래스 또는 카테고리를 정의하는 라이브러리의 모든 객체파일을 로드합니다.이 옵션은 일반적으로 (어플리케이션에 추가 오브젝트코드가 로드되어 있기 때문에) 실행 가능한 파일이 커지지만 기존 클래스의 카테고리를 포함하는 효과적인 Objective-C 정적 라이브러리를 성공적으로 만들 수 있습니다.

또, iPhone 개발에 관한 FAQ에도 추천이 있습니다.

정적 라이브러리에서 Objective-C 클래스를 모두 링크하려면 어떻게 해야 합니까?Other Linker Flags 빌드 설정을 -ObjC로 설정합니다.

및 플래그 설명:

-all_load 정적 아카이브 라이브러리의 모든 구성원을 로드합니다.

-ObjC Objective-C 클래스 또는 카테고리를 구현하는 정적 아카이브 라이브러리의 모든 구성원을 로드합니다.

-force_load(path_to_archive) 지정된 정적 아카이브 라이브러리의 모든 멤버를 로드합니다.주의: -all_load는 모든 아카이브의 모든 멤버를 강제로 로드합니다.이 옵션을 사용하면 특정 아카이브 대상을 지정할 수 있습니다.

* force_load를 사용하여 앱 바이너리 크기를 줄이고 경우에 따라 all_load로 인해 발생할 수 있는 충돌을 방지할 수 있습니다.

예, 프로젝트에 *.a 파일이 추가된 상태에서 작동합니다.그러나 직접 의존관계로 추가된 lib프로젝트에 문제가 있었습니다.그러나 나중에 나는 그것이 내 잘못이라는 것을 알게 되었다. 직접 의존 프로젝트가 적절하게 추가되지 않았을 수 있다.삭제한 후 단계를 따라 다시 추가할 때:

  1. 앱 프로젝트에서 lib 프로젝트 파일을 드래그 앤 드롭합니다(또는 프로젝트 -> 프로젝트에 추가...).
  2. lib project 아이콘 - mylib.a 파일명을 클릭하여 이 mylib.a 파일을 드래그하여 [Target]-> [ Link Binary With Library ]그룹에 드롭 합니다.
  3. 첫 페이지(일반)에서 대상 정보를 열고 종속성 목록에 my lib 추가

'-ObjC' 플래그는 제 경우 충분했습니다.

저도 http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html 블로그 아이디어에 관심이 많았습니다.작성자는 -all_load 또는 -ObjC 플래그를 설정하지 않고 lib에서 카테고리를 사용할 수 있다고 합니다.그는 단지 빈 더미 클래스 인터페이스/실장을 카테고리 h/m 파일에 추가하여 링커가 이 파일을 사용하도록 강제합니다.그래, 이 속임수가 효과가 있어

하지만 저자는 더미 오브젝트도 인스턴스화하지 않았다고 말했다.음... 제가 알아낸 바로는 카테고리 파일에서 "실제" 코드를 명시적으로 호출해야 합니다.따라서 적어도 클래스 함수는 호출되어야 합니다.그리고 우리는 심지어 더미 수업이 필요하지도 않다.단일 c 함수도 마찬가지입니다.

lib 파일을 다음과 같이 쓸 경우:

// mylib.h
void useMyLib();

@interface NSObject (Logger)
-(void)logSelf;
@end


// mylib.m
void useMyLib(){
    NSLog(@"do nothing, just for make mylib linked");
}


@implementation NSObject (Logger)
-(void)logSelf{
    NSLog(@"self is:%@", [self description]);
}
@end

useMyLib()를 호출하면 App 프로젝트 내 임의의 클래스에서 logSelf 카테고리 메서드를 사용할 수 있습니다.

[self logSelf];

그리고 더 많은 주제에 대한 블로그:

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

Vladimir의 답변은 사실 꽤 괜찮지만, 여기서 배경지식을 좀 더 알려드리고 싶습니다.언젠가 누군가 내 답장을 발견하고 도움이 될지도 몰라.

컴파일러는 소스 파일(.c, .cc, .cpp, .m)을 오브젝트 파일(.o)로 변환합니다.소스 파일당 하나의 객체파일이 있습니다.객체 파일에는 기호, 코드 및 데이터가 포함됩니다.운영 체제에서 개체 파일을 직접 사용할 수 없습니다.

이제 동적 라이브러리(.dylib), 프레임워크, 로드 가능한 번들(.bundle) 또는 실행 가능한 바이너리를 구축할 때 이들 오브젝트 파일은 링커에 의해 링크되어 운영체계가 "사용 가능한" 것으로 간주하는 것(예를 들어 특정 메모리 주소로 직접 로드할 수 있는 것)을 생성합니다.

그러나 정적 라이브러리를 구축할 때는 이러한 모든 객체 파일이 큰 아카이브 파일에 추가되기 때문에 정적 라이브러리(.a)의 확장자가 됩니다.따라서 .a 파일은 객체(.o) 파일의 아카이브에 지나지 않습니다.압축이 없는 TAR 아카이브 또는 ZIP 아카이브를 생각해 보십시오..o 파일 전체보다 하나의 .a 파일을 복사하는 것이 더 쉽습니다(배포하기 쉽도록 .jar 아카이브에 .class 파일을 패키지하는 Java와 유사합니다).

바이너리를 정적 라이브러리(= 아카이브)에 링크하면 링커는 아카이브에 있는 모든 기호의 테이블을 가져와 바이너리에서 참조하는 기호를 확인합니다.참조 기호를 포함하는 객체 파일만 링커에 의해 실제로 로드되며 링크 프로세스에 의해 고려됩니다.예를 들어 아카이브에 50개의 객체 파일이 있지만 20개만 이진에서 사용되는 기호를 포함하고 20개만 링커에 의해 로드되고 나머지 30개는 링크 프로세스에서 완전히 무시됩니다.

이 방법은 C 및 C++ 코드에 매우 적합합니다.이러한 언어들은 컴파일 시에 가능한 한 많은 처리를 시도합니다(단, C++는 일부 런타임 전용 기능을 갖추고 있습니다).하지만 Obj-C는 다른 종류의 언어이다.Obj-C는 런타임 기능에 크게 의존하며 많은 Obj-C 기능은 실제로 런타임 전용 기능입니다.Obj-C 클래스는 실제로 C 함수 또는 전역 C 변수와 유사한 기호를 가집니다(최소한 현재의 Obj-C 런타임).링커는 클래스가 참조되고 있는지 여부를 확인할 수 있으므로 사용 중인 클래스를 확인할 수 있습니다.정적 라이브러리에서 오브젝트 파일의 클래스를 사용하는 경우 링커에 사용 중인 기호가 표시되므로 이 오브젝트 파일은 링커에 의해 로드됩니다.카테고리는 런타임 전용 기능이며, 카테고리는 클래스나 함수와 같은 기호가 아닙니다.따라서 링커는 카테고리의 사용 여부를 판단할 수 없습니다.

링커가 Obj-C 코드를 포함한 오브젝트파일을 로드하는 경우, 그 모든 Obj-C 부분은 항상 링크스테이지의 일부가 됩니다.따라서 범주가 포함된 객체 파일이 "사용 중"으로 간주되어 로드되는 경우(클래스, 함수, 전역 변수) 범주도 로드되어 런타임에 사용할 수 있습니다.그러나 오브젝트 파일 자체가 로드되지 않으면 오브젝트 파일 내의 카테고리는 런타임에 사용할 수 없습니다.카테고리만 포함하는 오브젝트 파일은 링커가 "사용 중"이라고 간주하는 기호가 포함되어 있지 않기 때문에 로드되지 않습니다.그리고 이게 이곳의 모든 문제입니다.

몇 가지 솔루션이 제안되었습니다.이러한 솔루션이 어떻게 조합되는지 알게 되었습니다.이제 제안된 솔루션에 대해 다시 한 번 살펴보겠습니다.

  1. 한 가지 해결책은 다음과 같습니다.-all_load링커 콜에 접속합니다.링커 플래그는 실제로 무엇을 합니까?실제로 링커에 "사용 중인 기호가 있는지 여부에 관계없이 모든 아카이브의 모든 객체 파일 로드"를 알려줍니다.물론, 그것은 효과가 있을 것이다; 하지만 그것은 또한 큰 이진수를 만들어 낼 수도 있다.

  2. 다른 은 '먹다'를 입니다.-force_load링커 콜(아카이브(archive)로의 패스를 포함한다)에 접속합니다.이 플래그는 다음과 같이 작동합니다.-all_load, archive.에 만 사용할 수 있습니다.을 사용하다

  3. 가장 일반적인 솔루션은 다음과 같습니다.-ObjC링커 콜에 접속합니다.링커 플래그는 실제로 무엇을 합니까?이 플래그는 링커에게 "Obj-C 코드가 포함된 경우 모든 아카이브에서 모든 객체 파일 로드"를 알려줍니다.또, 「임의의 Obj-C 코드」에는 카테고리가 포함됩니다.이 경우에도 동작하며 Obj-C 코드가 없는 오브젝트 파일을 강제로 로드하지 않습니다(이러한 파일은 요구 시에만 로드됩니다).

  4. 다른 은 다소 설정이다.Perform Single-Object Prelink이 설정으로 무엇을 할 수 있습니까?활성화 되어 있는 경우 모든 오브젝트 파일(소스 파일마다 1개씩 있음)이 단일 오브젝트 파일(실제 링크가 아님, 따라서 PreLink라는 이름)로 병합되고 이 단일 오브젝트 파일('마스터 오브젝트 파일'이라고도 함)이 아카이브에 추가됩니다.마스터 오브젝트 파일의 기호가 사용 중인 것으로 간주될 경우 마스터 오브젝트 파일 전체가 사용 중인 것으로 간주되므로 그 모든 Objective-C 부분이 항상 로드됩니다.클래스는 일반적인 기호이기 때문에 모든 카테고리를 취득하기 위해 정적 라이브러리에서 하나의 클래스를 사용하는 것으로 충분합니다.

  5. 마지막 해결책은 블라디미르가 대답의 맨 마지막에 추가한 속임수이다.카테고리만 선언하는 소스 파일에 "짝퉁 기호"를 넣습니다.런타임에 카테고리를 사용하고 싶은 경우는, 컴파일시에 가짜 심볼을 참조해 주세요.이렇게 하면 오브젝트 파일이 링커에 의해 로드되어 그 안에 있는 모든 Obj-C 코드가 로딩됩니다.빈 함수 본문이 있는 함수(호출 시 아무것도 하지 않음)이거나 액세스되는 전역 변수(예: 글로벌)일 수 있습니다.int한 번 읽었거나 한 번 읽거나 한 번 쓰면 됩니다.위의 다른 모든 솔루션과 달리 이 솔루션은 실행 시 사용할 수 있는 카테고리를 컴파일된 코드로 제어합니다(링크하여 사용할 수 있도록 하려면 심볼에 액세스하고 그렇지 않으면 링커는 심볼에 액세스하지 않고 링커는 이를 무시합니다).

그게 다예요.

만요, 하나더
에는 '다 하다'라는 옵션이 .-dead_strip이옵 어떤 ?떤 능? ???에는 100개만, 를 들어 오브젝트 파일에는 100개의 함수가 포함되어 .예를 들어 오브젝트 파일에 100개의 함수가 포함되어 있지만 그 중 하나만 바이너리에서 사용됩니다.오브젝트 파일은 전체적으로 추가되거나 전혀 추가되지 않기 때문에 100개의 함수는 모두 바이너리에 추가됩니다.일반적으로 링커에서는 오브젝트파일을 부분적으로 추가할 수 없습니다.

그러나 링커에 "데드 스트립"을 지정하면 링커는 먼저 모든 객체 파일을 바이너리에 추가하고 모든 참조를 해결한 다음 마지막으로 바이너리에서 사용되지 않는 기호(또는 사용하지 않는 다른 기호에서만 사용 중인 기호)를 검색합니다.사용되지 않는 것으로 판명된 모든 심볼은 최적화 단계의 일부로 제거됩니다.위의 예에서는 99개의 미사용 함수가 다시 삭제됩니다. 하다, 하다, 하다, 하다, 하다 등의 옵션을 합니다.-load_all,-force_load ★★★★★★★★★★★★★★★★★」Perform Single-Object Prelink이러한 옵션은 경우에 따라 바이너리 크기를 크게 늘릴 수 있으며 데드스트립을 통해 사용되지 않는 코드와 데이터가 다시 제거되기 때문입니다.

데드 스트리핑은 C 코드(예: 사용하지 않는 함수, 변수 및 상수가 예상대로 제거됨)에 매우 잘 작동하고 C++(예: 사용하지 않는 클래스가 제거됨)에도 매우 적합합니다.완벽하지는 않습니다. 일부 기호는 제거해도 괜찮지만 제거되지 않는 경우도 있습니다. 그러나 대부분의 경우 이러한 언어에서는 상당히 잘 작동합니다.

Obj-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)을 검토해 주세요.솔루션(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링커는 사용 중인 기호로 인해 로드된 라이브러리의 객체 파일을 빌드 로그에 출력합니다.사용 중인 것으로 간주되는 첫 번째 기호만 인쇄되지만 이 기호만 해당 개체 파일을 사용하는 것은 아닙니다.

문제는 LLVM에서 수정되었습니다.이 수정은 LLVM 2.9의 일부로 출하됩니다.이 수정이 포함된 최초의 Xcode 버전은 LLVM 3.0과 함께 출하되는 Xcode 4.2입니다.XCode 4.2를 사용하는 경우 또는 를 사용할 필요가 없습니다. -ObjC여전히 필요합니다.

정적 라이브러리를 컴파일할 때 이 문제를 완전히 해결하려면 다음과 같이 하십시오.

[ ]으로하여 [를 []또는 [Xcode Build Settings]로 합니다.[ Perform Single Object Prelink ]는 [YES]를 선택합니다.GENERATE_MASTER_OBJECT_FILE = YES빌드 컨피규레이션파일에 격납합니다.

기본적으로 링커는 각 .m 파일에 대해 .o 파일을 생성합니다.따라서 카테고리에 따라 .o 파일이 다릅니다.링커는 정적 라이브러리 .o 파일을 볼 때 클래스별로 모든 심볼의 인덱스를 만들지 않습니다(실행 시간은 중요하지 않습니다).

이 디렉티브는 링커에게 모든 오브젝트를 하나의 큰 .o 파일로 정리하도록 요구하며, 이를 통해 스태틱라이브러리를 처리하는 링커가 모든 클래스 카테고리를 강제로 가져옵니다.

그게 명확해졌으면 좋겠어요.

스태틱 라이브러리 링크에 대한 논의가 시작될 때마다 거의 언급되지 않는 한 가지 요인은 빌드 단계 -> 파일 복사 스태틱 라이브러리 자체의 소스 컴파일에도 카테고리 자체를 포함해야 한다는 점입니다.

애플은 또한 최근 발행된 iOS에서의 정적 라이브러리 사용에서도 이 사실을 강조하지 않는다.

하루 종일 -objC, -all_load 등 다양한 종류를 시도했습니다.하지만 아무것도 나오지 않았어요 질문이 나에게 그 문제를 일깨워 주었다.(오해하지 마세요.그래도 -objC 같은 거 해야 돼그것뿐만이 아닙니다.)

또한 항상 저를 도와준 또 다른 행동은 항상 포함된 정적 라이브러리를 스스로 먼저 구축한다는 것입니다.그 후, 동봉된 애플리케이션을 작성합니다.

스태틱 라이브러리의 "public" 헤더에 #import "MyStaticLib" 카테고리가 있어야 합니다.h"

언급URL : https://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library