1. 項目結(jié)構(gòu):
其中,SimpleStatic
是一個靜態(tài)庫項目,我們將頭文件Person.h
和Person+MyPerson.h
暴露出來供外部使用.
Symbol
工程是主項目.
main.m
中的代碼為:
#import <Foundation/Foundation.h>
#import <Person.h>
#import <Person+MyPerson.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
[p todoSomething];
//extern void test(void);
//test();
}
return 0;
}
Person.m
中代碼為:
#import "Person.h"
@implementation Person
- (void)aabbccdd {
}
Person+MyPerson.m
中代碼為:
#import "Person+MyPerson.h"
void test() {
NSLog(@"這是test");
}
@implementation Person (MyPerson)
- (void)todoSomething {
NSLog(@"這是person分類");
}
@end
Config.xcconfig
中內(nèi)容為:
LD_MAP_FILE_PATH = ${SRCROOT}/myfile.m
LD_GENERATE_MAP_FILE = YES
這主要是為了生成link map文件.
2. 運行
此時,build
項目Symbol
會發(fā)現(xiàn)沒有任何問題.
然而,運行時發(fā)現(xiàn)項目崩潰:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person todoSomething]: unrecognized selector sent to instance 0x1004814f0'
根據(jù)網(wǎng)上資料,可以很容易的查到,這是因為main.o
編譯成可執(zhí)行文件的時候,沒有鏈接Person+MyPerson.o
導(dǎo)致的.解決方法是在Other linker flags
中添加-ObjC
.它的含義是:鏈接靜態(tài)庫中所有包含OC類和類別的目標(biāo)文件
3. 如何驗證
可以通過兩次生成的link map文件進(jìn)行查看.
打開myfile.m
:
# Object files:
[ 0] linker synthesized
[ 1] /Users/LQ/Library/Developer/Xcode/DerivedData/Symbol-akbspkizvxjwrmamodsanattllyd/Build/Intermediates.noindex/Symbol.build/Debug/Symbol.build/Objects-normal/x86_64/main.o
[ 2] /Users/LQ/Library/Developer/Xcode/DerivedData/Symbol-akbspkizvxjwrmamodsanattllyd/Build/Products/Debug/SimpleStatic(Person.o)
...
由此可以判斷出鏈接了哪些目標(biāo)文件.
現(xiàn)在,把main.m
中調(diào)用test()
方法的地方打開注釋,重新運行.
注意:此時沒有添加-ObjC
.
結(jié)果發(fā)現(xiàn)link map文件中已經(jīng)可以鏈接到Person+MyPerson.o
了
...
[ 2] /Users/LQ/Library/Developer/Xcode/DerivedData/Symbol-akbspkizvxjwrmamodsanattllyd/Build/Products/Debug/SimpleStatic(Person.o)
[ 3] /Users/LQ/Library/Developer/Xcode/DerivedData/Symbol-akbspkizvxjwrmamodsanattllyd/Build/Products/Debug/SimpleStatic(Person+MyPerson.o)
4. 為什么明明調(diào)用了分類的方法todoSomething
,但是沒有鏈接到分類?而假如在分類.m中定義一個c函數(shù)void test(void) {}
,在項目中調(diào)用test()
卻能鏈接到SimpleStatic(Person+MyPerson.o)
?
這是因為C和OC有所區(qū)別:
當(dāng)源文件使用在另一個文件中定義的東西(比如函數(shù))時侣集,一個未定義的符號被寫入到目標(biāo)文件中飞蚓,以“替代”丟失的東西厦幅。鏈接器通過在構(gòu)建最終可執(zhí)行文件時拉入包含未定義符號定義的對象文件來解析這些符號偷霉。
例如,如果main.c使用函數(shù)foo()甚垦,其中foo在另一個文件B.c中定義蜜托,那么對象文件main.o將具有foo()的未解析符號科侈,而B.o將包含foo()的實現(xiàn)。在鏈接時辅辩,B.o將被引入到最終的可執(zhí)行文件中难礼,因此main.o中的代碼現(xiàn)在引用B.o中定義的foo()的實現(xiàn)。
UNIX靜態(tài)庫只是對象文件的集合玫锋。通常蛾茉,如果這樣做會解析一些未定義的符號,那么鏈接器僅從靜態(tài)庫中提取對象文件撩鹿。不拉入所有對象文件會減小最終可執(zhí)行文件的大小臀稚。
Objective-C的動態(tài)特性使事情稍微復(fù)雜一些。因為實現(xiàn)方法的代碼直到方法被實際調(diào)用才確定三痰,所以O(shè)bjective-C不為方法定義鏈接器符號吧寺。鏈接器符號僅為類定義。
例如散劫,如果main.m包含代碼[[FooClass alloc]initWithBar:nil]稚机;那么main.o將包含F(xiàn)ooClass的未定義符號,但是-initWithBar:方法的鏈接器符號將不在main.o中获搏。
由于類別是方法的集合赖条,因此使用類別的方法不會生成未定義的符號。這意味著鏈接器不知道加載定義類別的對象文件(如果類本身已經(jīng)定義)常熙。這會導(dǎo)致和未實現(xiàn)方法時一樣的運行時錯誤"selector not recognized" 纬乍。
參考鏈接:oc靜態(tài)庫和類別
根據(jù)以上說法實現(xiàn)方法的代碼直到方法被實際調(diào)用才確定
,猜測是因為方法調(diào)用本質(zhì)上是objc_msgSend
,因此不會生成方法名的符號.
而調(diào)用C函數(shù)test()
是因為需要鏈接Person+MyPerson.o
,此時會一并將此目標(biāo)文件中的其他符號導(dǎo)入,因此不會發(fā)生崩潰.
5.如何驗證猜想?
首先將main.m編譯成目標(biāo)文件:
clang -fmodules -c main.m -o main.o -I/Users/LQ/Desktop/Test/OC/Symbol/SimpleStatic/SimpleStatic
注意:此時注釋掉main中調(diào)用test函數(shù)的地方,并且沒有添加-ObjC
使用nm
命令查看該文件的符號表如下:
LQ-Pro:Symbol LQ$ nm ./main.o
U _NSLog
0000000000000078 s _OBJC_CLASSLIST_REFERENCES_$_
U _OBJC_CLASS_$_Person
00000000000000b8 s _OBJC_SELECTOR_REFERENCES_
U ___CFConstantStringClassReference
0000000000000000 T _main
U _objc_alloc_init
U _objc_autoreleasePoolPop
U _objc_autoreleasePoolPush
U _objc_msgSend
可以看到,此時main.o中根本沒有OC方法todoSomething
的鏈接符號,取而代之的則是_objc_msgSend
.
現(xiàn)在,打開test()
函數(shù)的注釋,重新查看main.o中的符號:
0000000000000060 s _OBJC_CLASSLIST_REFERENCES_$_
U _OBJC_CLASS_$_Person
0000000000000078 s _OBJC_SELECTOR_REFERENCES_
0000000000000000 T _main
U _objc_alloc_init
U _objc_autoreleasePoolPop
U _objc_autoreleasePoolPush
U _objc_msgSend
U _test
_test
符號是未定義的,因此在鏈接階段,就會導(dǎo)入Person+MyPerson.o
中的符號.
使用nm
查看最終的可執(zhí)行文件:
0000000100003e90 t -[Person aabbccdd]
0000000100003ec0 t -[Person(MyPerson) todoSomething]
U _NSLog
U _OBJC_CLASS_$_NSObject
0000000100008128 S _OBJC_CLASS_$_Person
U _OBJC_METACLASS_$_NSObject
0000000100008100 S _OBJC_METACLASS_$_Person
0000000100008028 s __OBJC_$_INSTANCE_METHODS_Person(MyPerson)
00000001000080a8 s __OBJC_CLASS_RO_$_Person
0000000100008060 s __OBJC_METACLASS_RO_$_Person
U ___CFConstantStringClassReference
0000000100008150 d __dyld_private
0000000100000000 T __mh_execute_header
U __objc_empty_cache
0000000100003e00 T _main
U _objc_alloc_init
U _objc_autoreleasePoolPop
U _objc_autoreleasePoolPush
U _objc_msgSend
U _objc_storeStrong
0000000100003ea0 T _test
U dyld_stub_binder
可以看到分類和類的符號都導(dǎo)入了.
6. 其他
- 對于使用OC方法的文件(即main.m引用Person的方法),不會生成該方法的鏈接符號;但是對于定義OC方法的文件(即Person.m),會生成鏈接符號.
前半句意思是在main.o中,是不能看到OC的方法符號的.
后半句的意思是,假設(shè)我們此時去查看Person.o
和Person+MyPerson.o
,是可以分別看到-[Person aabbccdd]
和-[Person(MyPerson) todoSomething]
符號的.
- Other linker flags一些選項
-
-ObjC
:強(qiáng)制靜態(tài)庫中所有實現(xiàn)了OC類和類別的.o文件被鏈接 -
-all_load
:強(qiáng)制加載所有靜態(tài)庫中的目標(biāo)文件.這是因為當(dāng)靜態(tài)庫中只有OC類別時,-ObjC
還是無法鏈接類別 -
-fore_load
:后面必須跟一個靜態(tài)庫路徑.強(qiáng)制加載單個靜態(tài)庫所有目標(biāo)文件.