背景
給客戶提供了一個(gè)sdk,客戶那邊安裝了sdk后,發(fā)現(xiàn)另一個(gè)sdk的就會(huì)異常.....最后分析來分析去,發(fā)現(xiàn)只要添加我們的sdk就會(huì)有這樣的問題,于是,問題就到了我這了.
結(jié)論
先說結(jié)論吧,結(jié)論就是,我們的sdk中定義的一個(gè)全局c方法,覆蓋了另一個(gè)sdk中的全局c方法,然后,我們兩個(gè)c方法的作用又不一樣,從而導(dǎo)致出了不同的結(jié)果,導(dǎo)致問題.
分析
通常這種詭異的問題,調(diào)試分析是最頭痛的,因?yàn)橥耆恢绬栴}在哪,直接把對(duì)方的sdk和我們sdk的源碼加到一個(gè)測(cè)試工程里來,調(diào)試,發(fā)現(xiàn),確實(shí)是這樣,由于我們這個(gè)sdk中的文件數(shù)量不多,于是,一個(gè)個(gè)的刪除,定位到是由于一個(gè)包含全局c方法的文件中的c方法被對(duì)方sdk調(diào)用了...
也就是說,我們的c方法覆蓋了對(duì)方sdk中的同名方法了.
對(duì)于同樣都是靜態(tài)庫來說(靜態(tài)framework,.a文件),在編譯的時(shí)候,是會(huì)融合到宿主app的二進(jìn)制中去的,那么按理說,編譯階段,同名c方法,會(huì)被xcode提示出來啊,為什么這里會(huì)沒有提示呢?
我們隨便新建一個(gè)sdk,再建立一個(gè)宿主app,使用它.
在demo app中使用這個(gè)sdk
有幾個(gè)比較有意思的發(fā)現(xiàn):
宿主工程不加-ObjC的時(shí)候
主工程和庫工程中有重名的c方法的時(shí)候,xcode不會(huì)提示重復(fù)符號(hào),且能夠編譯運(yùn)行,至于哪個(gè)c方法會(huì)被調(diào)用,有如下的情況:
如果使用了c方法所在的文件中的類,那么調(diào)用的就是靜態(tài)庫中定義的c方法
- (void)viewDidLoad {
[super viewDidLoad];
convertSomething(@"");
CustomAction *act = [CustomAction new];
[act test1];
// Do any additional setup after loading the view.
}
如果沒有使用,那么調(diào)用的就是宿主中定義的c方法.
宿主添加-ObjC的時(shí)候,xcode就會(huì)報(bào)錯(cuò)了,提示符號(hào)重復(fù)
這個(gè)也是我們通常認(rèn)為的結(jié)果!!
可是,使用方明明是加了 -ObjC的,按照我們的理解,有兩個(gè)重名的c方法,不是應(yīng)該報(bào)錯(cuò),提示符號(hào)重復(fù)么,為什么沒有?
然后,讓對(duì)方自己新建一個(gè)demo,添加-ObjC,看看,是不是會(huì)報(bào),結(jié)果對(duì)方告訴我,不會(huì)....
心里立馬萬馬奔騰啊,在群里和小伙伴們討論,群里有人提示可能是弱符號(hào).于是試了下弱符號(hào) attribute((weak))
再編譯一下,不報(bào)錯(cuò)了
這里插播一下弱符號(hào)是啥,有什么作用
弱符號(hào)
我們經(jīng)常在編程中碰到一種情況叫符號(hào)重復(fù)定義戒悠。多個(gè)目標(biāo)文件中含有相同名字全局符號(hào)的定義金拒,那么這些目標(biāo)文件鏈接的時(shí)候?qū)?huì)出現(xiàn)符號(hào)重復(fù)定義的錯(cuò)誤湃密。比如我們?cè)谀繕?biāo)文件A和目標(biāo)文件B都定義了一個(gè)全局函數(shù)/變量,并將它們都初始化乖坠,那么鏈接器將A和B進(jìn)行鏈接時(shí)會(huì)報(bào)錯(cuò).這種在全局中不能有重名的符號(hào),可以稱之為 strong symbol(強(qiáng)符號(hào)).
對(duì)于C/C++語言來說染突,編譯器默認(rèn)函數(shù)和初始化了的全局變量為強(qiáng)符號(hào)兼雄,未初始化的全局變量為弱符號(hào)梧税。
就如同我們上面的情況
為了解決這樣的問題,就引入了弱符號(hào)(weak symbol)的概念:用attribute((weak))修飾的全局變量/函數(shù)就是 弱符號(hào)
針對(duì)強(qiáng)弱符號(hào)的概念部默,鏈接器就會(huì)按如下規(guī)則處理與選擇被多次定義的全局符號(hào):
規(guī)則1:不允許強(qiáng)符號(hào)被多次定義(即不同的目標(biāo)文件中不能有同名的強(qiáng)符號(hào))侵蒙;如果有多個(gè)強(qiáng)符號(hào)定義,則鏈接器報(bào)符號(hào)重復(fù)定義錯(cuò)誤傅蹂。
規(guī)則2:如果一個(gè)符號(hào)在某個(gè)目標(biāo)文件中是強(qiáng)符號(hào)纷闺,在其他文件中都是弱符號(hào),那么選擇強(qiáng)符號(hào)份蝴。
規(guī)則3:如果一個(gè)符號(hào)在所有目標(biāo)文件中都是弱符號(hào)犁功,那么選擇其中占用空間最大的一個(gè)。比如目標(biāo)文件A定義全局變量global為int型婚夫,占4個(gè)字節(jié)浸卦;目標(biāo)文件B定義global為double型,占8個(gè)字節(jié)案糙,那么目標(biāo)文件A和B鏈接后限嫌,符號(hào)global占8個(gè)字節(jié)(盡量不要使用多個(gè)不同類型的弱符號(hào),否則容易導(dǎo)致很難發(fā)現(xiàn)的程序錯(cuò)誤)时捌。
更詳細(xì)的介紹可以看GCC的強(qiáng)符號(hào)和弱符號(hào)
繼續(xù)分析
以為發(fā)現(xiàn)了問題所在,興沖沖的讓對(duì)方去查下,是不是那個(gè)c方法用attribute((weak))修飾了,并且從內(nèi)心已經(jīng)認(rèn)定是這樣的了,準(zhǔn)備收工,可現(xiàn)實(shí)往往那么的出其不意..對(duì)方告訴我,全局搜索了下,沒有使用到 attribute((weak))....
我去,這,...還會(huì)有什么問題導(dǎo)致呢?
好吧,只能繼續(xù)分析了
對(duì)方也發(fā)來了他們的測(cè)試demo,說確實(shí)會(huì)出現(xiàn)可以在宿主中定義同他們的靜態(tài)庫中的c方法名字一樣的方法.
真的很奇怪,為啥我建立的demo,加了-ObjC,沒用attribute((weak))會(huì)報(bào)錯(cuò),他們建立的demo就不會(huì)呢?
一度懷疑,難道是我用的xcode和對(duì)方的有什么地方默認(rèn)的不一致?
再查看對(duì)方的demo
.....
對(duì)方的domo中,c方法所在的文件,只有c方法,沒有任何oc的類的定義.難道是這個(gè)導(dǎo)致的?
于是乎,試了下:
在demo中的sdk1中添加個(gè)只包含c方法的文件CustomeAction2
內(nèi)容是
// CustomeAction2.h
#import <Foundation/Foundation.h>
NSString *convertSomething2(NSString * oriStr);
// CustomeAction2.m
#import "CustomeAction2.h"
NSString *convertSomething2(NSString * oriStr){
NSLog(@"convertSomething2 SDK1");
return @"convertSomething2 SDK1 ";
}
然后在宿主中定義一個(gè)同名的
NSString *convertSomething2(NSString * oriStr){
NSLog(@"convertSomething2 app");
return @"convertSomething2 app ";
}
編譯,運(yùn)行,果然不報(bào)錯(cuò),
看來就是這個(gè)原因?qū)е碌牧?/p>
再添加 -all_load,不出意外,報(bào)錯(cuò)了
問題原因解讀
再度回憶 -ObjC的作用
-all_load Loads all members of static archive libraries.
-ObjC Loads all members of static archive libraries that implement an Objective-C class or category.
因?yàn)槎x的c方法所在的文件并沒有定義Objective-C的class或者category,所以 -ObjC并不會(huì)在符號(hào)表中導(dǎo)入他們,也即是這個(gè)-ObjC失效了,所以在宿主app中,就可以定義同名的方法了
當(dāng)然了解決方式很簡單
1 要求所有的宿主app,也就是接入方,在other linker flags中添加-all_load,這個(gè)來加載靜態(tài)庫中所有的方法,當(dāng)然了,這個(gè)解決方式不太好.
更好的解決方式是
2 在僅僅包含c方法的文件中添加一個(gè)類的定義
后記
最后,我還對(duì)比了下,在一個(gè)方法中添加不添加attribute((weak))最后的可執(zhí)行文件有什么不同
當(dāng)把一個(gè)方法定義為弱符號(hào)后
attribute((weak)) NSData * pasa_cipherOperation(NSData *contentData, NSData *keyData, CCOperation operation)
在最后的 靜態(tài)庫中出現(xiàn)的不同是:
在符號(hào)表 Symbol Table中對(duì)應(yīng)的方法
拿一個(gè)app的可執(zhí)行文件來試試
在other linker flags中 添加 -ObjC 和不添加,對(duì)于最后的二進(jìn)制的區(qū)別
當(dāng)然,由于xcode編譯兩次后,codesign部分會(huì)不一致,可以用
codesign --remove-signature 可執(zhí)行文件名 來移除簽名
然后用 beyond compare來進(jìn)行對(duì)比二進(jìn)制
再在machoview中查看對(duì)應(yīng)的地址
看來,這兩個(gè),改變的都是最后的mach-o文件中符號(hào)表Symbol Table所在的內(nèi)容.