静态库中的类别符号(-ObjC\-all_load\-force_load)

1. 项目结构:

image.png

其中,SimpleStatic是一个静态库项目,我们将头文件Person.hPerson+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中内容为:

LD_MAP_FILE_PATH = ${SRCROOT}/myfile.m
LD_GENERATE_MAP_FILE = YES

这主要是为了生成link map文件.

2. 运行

此时,build项目Symbol会发现没有任何问题.
然而,运行时发现项目崩溃:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person todoSomething]: unrecognized selector sent to instance 0x1004814f0'

根据网上资料,可以很容易的查到,这是因为main.o编译成可执行文件的时候,没有链接Person+MyPerson.o导致的.解决方法是在Other linker flags中添加-ObjC.它的含义是:链接静态库中所有包含OC类和类别的目标文件

3. 如何验证

可以通过两次生成的link map文件进行查看.
打开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)
    ...

由此可以判断出链接了哪些目标文件.

现在,把main.m中调用test()方法的地方打开注释,重新运行.

注意:此时没有添加-ObjC.

结果发现link map文件中已经可以链接到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. 为什么明明调用了分类的方法todoSomething,但是没有链接到分类?而假如在分类.m中定义一个c函数void test(void) {},在项目中调用test()却能链接到SimpleStatic(Person+MyPerson.o)?

这是因为C和OC有所区别:

当源文件使用在另一个文件中定义的东西(比如函数)时,一个未定义的符号被写入到目标文件中,以“替代”丢失的东西。链接器通过在构建最终可执行文件时拉入包含未定义符号定义的对象文件来解析这些符号。

例如,如果main.c使用函数foo(),其中foo在另一个文件B.c中定义,那么对象文件main.o将具有foo()的未解析符号,而B.o将包含foo()的实现。在链接时,B.o将被引入到最终的可执行文件中,因此main.o中的代码现在引用B.o中定义的foo()的实现。

UNIX静态库只是对象文件的集合。通常,如果这样做会解析一些未定义的符号,那么链接器仅从静态库中提取对象文件。不拉入所有对象文件会减小最终可执行文件的大小。

Objective-C的动态特性使事情稍微复杂一些。因为实现方法的代码直到方法被实际调用才确定,所以Objective-C不为方法定义链接器符号。链接器符号仅为类定义。

例如,如果main.m包含代码[[FooClass alloc]initWithBar:nil];那么main.o将包含FooClass的未定义符号,但是-initWithBar:方法的链接器符号将不在main.o中。

由于类别是方法的集合,因此使用类别的方法不会生成未定义的符号。这意味着链接器不知道加载定义类别的对象文件(如果类本身已经定义)。这会导致和未实现方法时一样的运行时错误"selector not recognized" 。

参考链接:oc静态库和类别

根据以上说法实现方法的代码直到方法被实际调用才确定,猜测是因为方法调用本质上是objc_msgSend,因此不会生成方法名的符号.

而调用C函数test()是因为需要链接Person+MyPerson.o,此时会一并将此目标文件中的其他符号导入,因此不会发生崩溃.

5.如何验证猜想?

首先将main.m编译成目标文件:

clang -fmodules -c main.m -o main.o -I/Users/LQ/Desktop/Test/OC/Symbol/SimpleStatic/SimpleStatic

注意:此时注释掉main中调用test函数的地方,并且没有添加-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.

现在,打开test()函数的注释,重新查看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符号是未定义的,因此在链接阶段,就会导入Person+MyPerson.o中的符号.

使用nm查看最终的可执行文件:

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

可以看到分类和类的符号都导入了.

6. 其他

  1. 对于使用OC方法的文件(即main.m引用Person的方法),不会生成该方法的链接符号;但是对于定义OC方法的文件(即Person.m),会生成链接符号.

前半句意思是在main.o中,是不能看到OC的方法符号的.

后半句的意思是,假设我们此时去查看Person.oPerson+MyPerson.o,是可以分别看到-[Person aabbccdd]-[Person(MyPerson) todoSomething]符号的.

  1. Other linker flags一些选项
  • -ObjC:强制静态库中所有实现了OC类和类别的.o文件被链接
  • -all_load:强制加载所有静态库中的目标文件.这是因为当静态库中只有OC类别时,-ObjC还是无法链接类别
  • -fore_load:后面必须跟一个静态库路径.强制加载单个静态库所有目标文件.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容