Programing

정적 라이브러리의 Objective-C 범주

lottogame 2020. 6. 12. 22:09
반응형

정적 라이브러리의 Objective-C 범주


정적 라이브러리를 iPhone 프로젝트에 올바르게 연결하는 방법을 알려주십시오. 앱 프로젝트에 추가 된 정적 라이브러리 프로젝트를 직접 종속성 (대상-> 일반-> 직접 종속성)으로 사용하고 모두 정상적으로 작동하지만 카테고리는 작동합니다. 정적 라이브러리에 정의 된 카테고리가 앱에서 작동하지 않습니다.

그래서 내 질문은 일부 프로젝트의 정적 라이브러리를 다른 프로젝트에 추가하는 방법입니다.

그리고 일반적으로 다른 프로젝트의 앱 프로젝트 코드에서 사용하는 가장 좋은 방법은 무엇입니까?


해결 방법 : Xcode 4.2부터는 라이브러리 자체가 아닌 라이브러리와 연결되는 응용 프로그램으로 이동하여 프로젝트 네비게이터에서 프로젝트를 클릭하고 앱의 대상을 클릭 한 다음 설정을 빌드하고 "기타"를 검색하면됩니다. 링커 플래그 "에서 + 버튼을 클릭하고 '-ObjC'를 추가하십시오. '-all_load'및 '-force_load'는 더 이상 필요하지 않습니다.

세부 정보 : 다양한 포럼, 블로그 및 Apple 문서에서 일부 답변을 찾았습니다. 이제 검색 및 실험에 대한 간단한 요약을 시도합니다.

(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 로드에게 오브젝티브 C 클래스 또는 카테고리를 구현하는 정적 아카이브 라이브러리의 모든 구성원.

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

* force_load를 사용하여 앱 이진 크기를 줄이고 all_load가 어떤 경우에 발생할 수있는 충돌을 피할 수 있습니다.

예, 프로젝트에 추가 된 * .a 파일과 함께 작동합니다. 그러나 직접 의존으로 추가 된 lib 프로젝트에 문제가있었습니다. 그러나 나중에 내 잘못이라는 것을 알았습니다. 직접 종속성 프로젝트가 제대로 추가되지 않았을 수 있습니다. 제거하고 단계를 다시 추가하면 :

  1. 앱 프로젝트에서 lib 프로젝트 파일을 드래그 앤 드롭하거나 Project-> Add to project…로 추가하십시오.
  2. lib project icon-mylib.a 파일 이름에서 화살표를 클릭하고이 mylib.a 파일을 끌어서 Target-> Link Binary With Library 그룹에 놓으십시오.
  3. 첫 페이지에서 대상 정보를 열고 (일반) 종속성 목록에 내 라이브러리를 추가하십시오.

그 후 모든 것이 정상적으로 작동합니다. 내 경우에는 "-ObjC"플래그로 충분했습니다.

또한 http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html 블로그의 아이디어에 관심이있었습니다 . 저자는 -all_load 또는 -ObjC 플래그를 설정하지 않고 lib의 카테고리를 사용할 수 있다고 말합니다. 그는 링커가이 파일을 사용하도록하기 위해 빈 hummy 클래스 인터페이스 / 구현을 카테고리 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


블라디미르의 대답은 실제로 꽤 좋지만 여기에 더 많은 배경 지식을주고 싶습니다. 언젠가 누군가가 내 답장을 찾아서 도움이 될 수도 있습니다.

The compiler transforms source files (.c, .cc, .cpp, .m) into object files (.o). There is one object file per source file. Object files contain symbols, code and data. Object files are not usable directly by the operating system.

Now when building a dynamic library (.dylib), a framework, a loadable bundle (.bundle) or an executable binary, these object files are linked together by the linker to produce something the operating system considers "usable", e.g. something it can directly load to a specific memory address.

However when building a static library, all these object files are simply added to a big archive file, hence the extension of static libraries (.a for archive). So an .a file is nothing than an archive of object (.o) files. Think of a TAR archive or a ZIP archive without compression. It's just easier to copy a single .a file around than a whole bunch of .o files (similar to Java, where you pack .class files into a .jar archive for easy distribution).

When linking a binary to a static library (= archive), the linker will get a table of all symbols in the archive and check which of these symbols are referenced by the binaries. Only the object files containing referenced symbols are actually loaded by the linker and are considered by the linking process. E.g. if your archive has 50 object files, but only 20 contain symbols used by the binary, only those 20 are loaded by the linker, the other 30 are entirely ignored in the linking process.

This works quite well for C and C++ code, as these languages try to do as much as possible at compile time (though C++ also has some runtime-only features). Obj-C, however, is a different kind of language. Obj-C heavily depends on runtime features and many Obj-C features are actually runtime-only features. Obj-C classes actually have symbols comparable to C functions or global C variables (at least in current Obj-C runtime). A linker can see if a class is referenced or not, so it can determine a class being in use or not. If you use a class from an object file in a static library, this object file will be loaded by the linker because the linker sees a symbol being in use. Categories are a runtime-only feature, categories aren't symbols like classes or functions and that also means a linker cannot determine if a category is in use or not.

If the linker loads an object file containing Obj-C code, all Obj-C parts of it are always part of the linking stage. So if an object file containing categories is loaded because any symbol from it is considered "in use" (be it a class, be it a function, be it a global variable), the categories are loaded as well and will be available at runtime. Yet if the object file itself is not loaded, the categories in it will not be available at runtime. An object file containing only categories is never loaded because it contains no symbols the linker would ever consider "in use". And this is the whole problem here.

Several solutions have been proposed and now that you know how all this plays together, let's have another look on the proposed solution:

  1. One solution is to add -all_load to the linker call. What will that linker flag actually do? Actually it tells the linker the following "Load all object files of all archives regardless if you see any symbol in use or not'. Of course, that will work; but it may also produce rather big binaries.

  2. Another solution is to add -force_load to the linker call including the path to the archive. This flag works exactly like -all_load, but only for the specified archive. Of course this will work as well.

  3. The most popular solution is to add -ObjC to the linker call. What will that linker flag actually do? This flag tells the linker "Load all object files from all archives if you see that they contain any Obj-C code". And "any Obj-C code" includes categories. This will work as well and it will not force loading of object files containing no Obj-C code (these are still only loaded on demand).

  4. Another solution is the rather new Xcode build setting Perform Single-Object Prelink. What will this setting do? If enabled, all the object files (remember, there is one per source file) are merged together into a single object file (that is not real linking, hence the name PreLink) and this single object file (sometimes also called a "master object file") is then added to the archive. If now any symbol of the master object file is considered in use, the whole master object file is considered in use and thus all Objective-C parts of it are always loaded. And since classes are normal symbols, it's enough to use a single class from such a static library to also get all the categories.

  5. The final solution is the trick Vladimir added at the very end of his answer. Place a "fake symbol" into any source file declaring only categories. If you want to use any of the categories at runtime, make sure you somehow reference the fake symbol at compile time, as this causes the object file to be loaded by the linker and thus also all Obj-C code in it. E.g. it could be a function with an empty function body (which will do nothing when being called) or it could be a global variable accessed (e.g. a global int once read or once written, this is sufficient). Unlike all other solutions above, this solution shifts control about which categories are available at runtime to the compiled code (if it wants them to be linked and available, it accesses the symbol, otherwise it doesn't access the symbol and the linker will ignore it).

That's all folks.

Oh, wait, there's one more thing:
The linker has an option named -dead_strip. What does this option do? If the linker decided to load an object file, all symbols of the object file become part of the linked binary, whether they are used or not. E.g. an object file contains 100 functions, but only one of them is used by the binary, all 100 functions are still added to the binary because object files are either added as a whole or they are not added at all. Adding an object file partially is usually not supported by linkers.

However, if you tell the linker to "dead strip", the linker will first add all the object files to the binary, resolve all the references and finally scan the binary for symbols not in use (or only in use by other symbols not in use). All the symbols found to be not in use are then removed as part of the optimization stage. In the example above, the 99 unused functions are removed again. This is very useful if you use options like -load_all, -force_load or Perform Single-Object Prelink because these options can easily blow up binary sizes dramatically in some cases and the dead stripping will remove unused code and data again.

Dead stripping works very well for C code (e.g. unused functions, variables and constants are removed as expected) and it also works quite good for C++ (e.g. unused classes are removed). It is not perfect, in some cases some symbols are not removed even though it would be okay to remove them, but in most cases it works quite well for these languages.

What about Obj-C? Forget about it! There is no dead stripping for Obj-C. As Obj-C is a runtime-feature language, the compiler cannot say at compile time whether a symbol is really in use or not. E.g. an Obj-C class is not in use if there is no code directly referencing it, correct? Wrong! You can dynamically build a string containing a class name, request a class pointer for that name and dynamically allocate the class. E.g. instead of

MyCoolClass * mcc = [[MyCoolClass alloc] init];

I would also write

NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];

In both cases mmc is a reference to an object of the class "MyCoolClass", but there is no direct reference to this class in the second code sample (not even the class name as a static string). Everything happens only at runtime. And that's even though classes are actually real symbols. It's even worse for categories, as they are not even real symbols.

So if you have a static library with hundreds of objects, yet most of your binaries only need a few of them, you may prefer not to use the solutions (1) to (4) above. Otherwise you end up with very big binaries containing all these classes, even though most of them are never used. For classes you usually don't need any special solution at all since classes have real symbols and as long as you reference them directly (not as in the second code sample), the linker will identify their usage pretty well on its own. For categories, though, consider solution (5), as it makes it possible to only include the categories you really need.

E.g. if you want a category for NSData, e.g. adding a compression/decompression method to it, you'd create a header file:

// NSData+Compress.h
@interface NSData (Compression)
    - (NSData *)compressedData;
    - (NSData *)decompressedData;
@end

void import_NSData_Compression ( );

and an implementation file

// NSData+Compress
@implementation NSData (Compression)
    - (NSData *)compressedData 
    {
        // ... magic ...
    }

    - (NSData *)decompressedData
    {
        // ... magic ...
    }
@end

void import_NSData_Compression ( ) { }

Now just make sure that anywhere in your code import_NSData_Compression() is called. It doesn't matter where it is called or how often it is called. Actually it doesn't really have to be called at all, it's enough if the linker thinks so. E.g. you could put the following code anywhere in your project:

__attribute__((used)) static void importCategories ()
{
    import_NSData_Compression();
    // add more import calls here
}

You don't have to ever call importCategories() in your code, the attribute will make the compiler and linker believe that it is called, even in case it is not.

And a final tip:
If you add -whyload to the final link call, the linker will print in the build log which object file from which library it did load because of which symbol in use. It will only print the first symbol considered in use, but that is not necessarily the only symbol in use of that object file.


This issue has been fixed in LLVM. The fix ships as part of LLVM 2.9 The first Xcode version to contain the fix is Xcode 4.2 shipping with LLVM 3.0. The usage of -all_load or -force_load is no longer needed when working with XCode 4.2 -ObjC is still needed.


Here's what you need to do to resolve this problem completely when compiling your static library:

Either go to Xcode Build Settings and set Perform Single-Object Prelink to YES or GENERATE_MASTER_OBJECT_FILE = YES in your build configuration file.

By default,the linker generates an .o file for each .m file. So categories gets different .o files. When the linker looks at a static library .o files, it doesn't create an index of all symbols per class (Runtime will, doesn't matter what).

This directive will ask the linker to pack all objects together into one big .o file and by this it forces the linker that process the static library to get index all class categories.

Hope that clarifies it.


One factor that is rarely mentioned whenever the static library linking discussion comes up is the fact that you must also include the categories themselves in the build phases->copy files and compile sources of the static library itself.

Apple also doesn't emphasize this fact in their recently published Using Static Libraries in iOS either.

I spent a whole day trying all sorts of variations of -objC and -all_load etc.. but nothing came out of it.. this question brought that issue to my attention. (don't get me wrong.. you still have to do the -objC stuff.. but it's more than just that).

also another action that has always helped me is that I always build the included static library first on its own.. then i build the enclosing application..


You probably need to have the category in you're static library's "public" header: #import "MyStaticLib.h"

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

반응형