📜 ⬆️ ⬇️

Objective-C, static libraries, categories, -ObjC, pain…

Not everyone was lucky to write applications completely on Swift, and even under ios 8+ onli. A lot of legacy in Objective-C, a lot of dependencies comes through static, neither cocoapods, nor carthage, all handles. We are cool developers, so we strictly follow DRY and all reusable snacks are brought out either in separate projects or in library statics. Now consider the case when we made a cool static library with a no less cool api, and would like to share with colleagues in the shop within the company - put the archiver with liba, headers and, of course, the readme where the whole api is described on the wiki resource / game use it.

For example, consider one class + its category.


')


In the screenshot we have the structure of the project, where the class + class category, everything is simple. We collect in the usual way, we write readme.md with the description api and we archive the library. Everything is cool, poured on the wiki, the boys tweeted in slack / skype / etc and went for another coffee. You just crouched back with freshly brewed coffee and the mouse cursor almost reached the tab for Habr, some logs fell in chat rooms, and everyone requires your immediate response, since the problem is in freshly liberated. You were thrown into the pot, because you have a test coverage of 146%, everything was rechecked a hundred times. At the same time, the same error log is written again in the chat:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Deadpool guns]: unrecognized selector sent to instance 0x7ffecbc12df0' 


After reading the log, the reason is clear and painfully familiar when you often work with statics libs. Having understood the problem, you confidently wipe the sweat from your forehead, open the previously sent readme.md and append:

Don't forget to add the '-ObjC' flag to the 'Other Linker Flags' in Build Settins of Xcode's scheme.

After, they updated the wiki, again they notified everyone and everything seemed to calm down, the messengers were silent, the coffee did not even have time to cool down. “Well, now for sure nobody will stop me” - your inner voice whispers and the mouse cursor again reaches for the cherished bookmark (Neneen, only Habr!). From the desired you are separated only by clicking on the left mouse button, but the thought does not leave you: “Was it possible to avoid this error or how to prevent it in the future ?!”. “Fuck it all!” Exclaimed the inner voice and the cursor reached for Terminal.app.

 otool -tV -arch x86_64 libDeadpool.a 


gives:

 Archive : libDeadpool.a libDeadpool.a(Deadpool+Guns.o): (__TEXT,__text) section -[Deadpool(Guns) guns]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %rsp 0000000000000008 leaq 0x71(%rip), %rax ## Objc cfstring ref: @"sword 2" 000000000000000f movd %rax, %xmm0 0000000000000014 leaq 0x45(%rip), %rax ## Objc cfstring ref: @"sword 1" 000000000000001b movd %rax, %xmm1 0000000000000020 punpcklqdq %xmm0, %xmm1 ## xmm1 = xmm1[0],xmm0[0] 0000000000000024 movdqa %xmm1, -0x10(%rbp) 0000000000000029 movq 0x70(%rip), %rdi ## Objc class ref: NSArray 0000000000000030 movq 0x91(%rip), %rsi ## Objc selector ref: arrayWithObjects:count: 0000000000000037 leaq -0x10(%rbp), %rdx 000000000000003b movl $0x2, %ecx 0000000000000040 callq *_objc_msgSend(%rip) 0000000000000046 addq $0x10, %rsp 000000000000004a popq %rbp 000000000000004b retq libDeadpool.a(Deadpool.o): (__TEXT,__text) section -[Deadpool name]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 movq 0x1d(%rip), %rsi ## Objc selector ref: class 000000000000000b callq *_objc_msgSend(%rip) 0000000000000011 movq %rax, %rdi 0000000000000014 popq %rbp 0000000000000015 jmp _NSStringFromClass 


hmm, in lib itself all the methods are in place, now let's see the source code of the application:

 otool -tV -arch x86_64 DemoApp.app/DemoApp | grep Deadpool 


gives:

 0000000100001ae8 movq 0x21f1(%rip), %rdi ## Objc class ref: Deadpool 0000000100001af6 movq 0x1513(%rip), %r12 ## Objc message: +[Deadpool new] -[Deadpool name]: 


WTF! Okay Google, where is the method from the category?

Google skillfully slips us a link to the epl documentation on this problem https://developer.apple.com/library/mac/qa/qa1490/_index.html , where a quick translation says the following:

The linker

When the C program is compiled, each file (.c) is compiled into a so-called “object file” (.o), which contains implementations of functions and other static information. After the linker collects all these files into one final file - executable. And this executable file just gets inside our .app by means of Xcode.

But when the source file (.c) uses something, for example, a function that is defined in another file (another .c file), then the “undefined symbol” is written to the .o file for this piece of code. And at the assembly stage, the linker has enough information to understand where it is necessary to pull out the missing thing from the “undefined symbol” in order to collect the final executable. This description is for building a UNIX static library.


Objective c

Due to the dynamic nature of the language, this process in Objective-C is a bit complicated, since the search for the implementation of a method occurs only after the fact that the method is accessed. Objective-C does not define auxiliary symbols for linker methods, but only defines symbols for classes. For example, in the class / main.o file there is a code:

[[FooClass alloc] initWithBar: nil]

that is, FooClass is a separate class, in a separate FooClass.o file, so main.o will only contain an “undefined symbol” for FooClass itself, but no additional symbols for the -initWithBar method: in this class.

Since a category is just a separate file with methods, the linker has absolutely no information that this file needs to be linked, since no methods are created for the linker “undefined symbol”.


So, sort of sorted out, once again look at the byte code of the lib:

 Archive : libDeadpool.a libDeadpool.a(Deadpool+Guns.o): (__TEXT,__text) section -[Deadpool(Guns) guns]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %rsp 0000000000000008 leaq 0x71(%rip), %rax ## Objc cfstring ref: @"sword 2" 000000000000000f movd %rax, %xmm0 0000000000000014 leaq 0x45(%rip), %rax ## Objc cfstring ref: @"sword 1" 000000000000001b movd %rax, %xmm1 0000000000000020 punpcklqdq %xmm0, %xmm1 ## xmm1 = xmm1[0],xmm0[0] 0000000000000024 movdqa %xmm1, -0x10(%rbp) 0000000000000029 movq 0x70(%rip), %rdi ## Objc class ref: NSArray 0000000000000030 movq 0x91(%rip), %rsi ## Objc selector ref: arrayWithObjects:count: 0000000000000037 leaq -0x10(%rbp), %rdx 000000000000003b movl $0x2, %ecx 0000000000000040 callq *_objc_msgSend(%rip) 0000000000000046 addq $0x10, %rsp 000000000000004a popq %rbp 000000000000004b retq libDeadpool.a(Deadpool.o): (__TEXT,__text) section -[Deadpool name]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 movq 0x1d(%rip), %rsi ## Objc selector ref: class 000000000000000b callq *_objc_msgSend(%rip) 0000000000000011 movq %rax, %rdi 0000000000000014 popq %rbp 0000000000000015 jmp _NSStringFromClass 


Indeed, we have compiled two files Deadpool.o and Deadpool + Guns.o , since the second file is just a set of methods for the first, the linker knows nothing about it and therefore we get this error only in runtime.

Immediately the first solution is to transfer the category to the main class file. Yes, it will work :) but for us it is not very convenient, as we used to keep all categories in separate daddies for order.

Another solution. Those who use our lib should specify the -ObjC flag in the “Other Linker Flags”, this flag tells the linker to load everything everything from the static lib. Well, this decision suits us that we don't need to rule anything on our side. But if you think about it, if a developer connects a bunch of libs and only because of ours he has to add this flag, then he can get a sickly weight gain for his application (I guess so).

Is it possible to somehow tell the linker to assemble the class and its categories into one file? It turns out there is such a name for it "Perform Single-Object Prelink" or "GENERATE_MASTER_OBJECT_FILE" in the pbxproj file. The truth is, it is not just the merging of a class and its category into a single file, but all project files will be like a single “object file”. If this value is set to true, then we need to get the behavior we want. Check it out.

Expose:



 otool -tV -arch x86_64 libDeadpool.a 


we get:

 Archive : libDeadpool.a libDeadpool.a(libDeadpool.a-x86_64-master.o): (__TEXT,__text) section -[Deadpool(Guns) guns]: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %rsp 0000000000000008 leaq 0x149(%rip), %rax ## Objc cfstring ref: @"sword 2" 000000000000000f movd %rax, %xmm0 0000000000000014 leaq 0x11d(%rip), %rax ## Objc cfstring ref: @"sword 1" 000000000000001b movd %rax, %xmm1 0000000000000020 punpcklqdq %xmm0, %xmm1 ## xmm1 = xmm1[0],xmm0[0] 0000000000000024 movdqa %xmm1, -0x10(%rbp) 0000000000000029 movq 0x270(%rip), %rdi ## Objc class ref: NSArray 0000000000000030 movq 0x259(%rip), %rsi ## Objc selector ref: arrayWithObjects:count: 0000000000000037 leaq -0x10(%rbp), %rdx 000000000000003b movl $0x2, %ecx 0000000000000040 callq *_objc_msgSend(%rip) 0000000000000046 addq $0x10, %rsp 000000000000004a popq %rbp 000000000000004b retq -[Deadpool name]: 000000000000004c pushq %rbp 000000000000004d movq %rsp, %rbp 0000000000000050 movq 0x241(%rip), %rsi ## Objc selector ref: class 0000000000000057 callq *_objc_msgSend(%rip) 000000000000005d movq %rax, %rdi 0000000000000060 popq %rbp 0000000000000061 jmp _NSStringFromClass 


What they wanted, now everything is in one file libDeadpool.a-x86_64-master.o . We remove from the application -ObjC and reassemble with the new version of our library and see:

 otool -tV -arch x86_64 DemoApp.app/DemoApp | grep Deadpool 


conclusion:

 0000000100001a70 movq 0x22c9(%rip), %rdi ## Objc class ref: Deadpool 0000000100001a7e movq 0x158b(%rip), %r12 ## Objc message: +[Deadpool new] -[Deadpool(Guns) guns]: -[Deadpool name]: 


Fine. Now it is possible to delete information about -ObjC flag from readme.md, boldly open Habr and finish, unfortunately, already cooled coffee)

ps

The problem is old, it has long been decided, now I’m here to write my hands and understand it in more detail)
I'm not sure about the ideal solution, but it helped me with this problem, maybe someone will be interested.

Useful links:

https://developer.apple.com/library/mac/qa/qa1490/_index.html
http://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library

Source: https://habr.com/ru/post/283570/


All Articles