iOS靜態庫開發中引入的第三方庫可能與宿主APP中衝突的解決方案

来源:https://www.cnblogs.com/simplepp/archive/2020/04/21/12745716.html
-Advertisement-
Play Games

SDK開發中我們可能希望使用已有的第三方開源庫,比如在發送請求的功能上我們更希望用AFNetworking而非直接使用NSURLSession,又如在實現socket連接時我們更希望用SocketRocket而非自己從零實現。但如果我們直接把AFNetworking的源文件拖到靜態庫SDK里,而宿主 ...


SDK開發中我們可能希望使用已有的第三方開源庫,比如在發送請求的功能上我們更希望用AFNetworking而非直接使用NSURLSession,又如在實現socket連接時我們更希望用SocketRocket而非自己從零實現。但如果我們直接把AFNetworking的源文件拖到靜態庫SDK里,而宿主APP也引入了AFNetworking,這時運行代碼就會報符號衝突(duplicate symbols)的錯誤。

  符號衝突報錯

這時大部分人的解決方案都是手動修改引入到SDK里的開源庫代碼,包括類名、分類名、全局常量名、協議名等會導致衝突的符號。其實對於像AFNetworking(v3.2.1)這種源碼量較少的第三方庫來說,需要修改的地方都要多達47個,可想而知這是一項多麼低效和易錯的解決方案,而且如果下次需要升級SDK中的該第三方庫,你需要再重新手動改一遍……下邊我們來一步步深入解決這件麻煩事。
首先我們考慮下怎樣避免每次都要修改第三方庫源碼,如果有一個單獨的文件來存原符號和重命名符號的對應關係就好了,我們自然而然地會想到用巨集定義。創建一個頭文件,比如叫XNGNamespace.h

// XNGNamespace.h

#define AFURLSessionManager XNGURLSessionManager
#define AFNetworkingReachabilityDidChangeNotification XNGNetworkingReachabilityDidChangeNotification
#define AFImageResponseSerializer XNGImageResponseSerializer
...

然後在你的SDK工程中,如果你已經有一個預編譯頭文件(一般為xxx.pch),在最上一行引入XNGNamespace.h,否則在Build Settings -> Prefix Header配置該文件的路徑(即把這個文件作為預編譯頭文件)。這時你可以在Xcode中看到原本的比如AFURLSessionManager類名顏色變成和巨集一樣的顏色,準確地說,這個類現在其實叫XNGURLSessionManager了。

  類名顏色

現在有了這個文件後,即使要升級SDK中的第三方庫,我們也只需要在這個文件里做少量增刪了。
但到目前為止最麻煩的這部分事還沒解決,畢竟現在還是要靠肉眼找出那些符號,手動編寫巨集定義。有沒有什麼命令或腳本幫我們分析出這些符號呢,這正好可以藉助nm命令了。nm是Linux下用於查看指定文件(對象文件、可執行文件或對象文件庫)中符號列表的命令,所以為了用這個命令,我們需要先做點準備工作。

一、準備工作

如上所述,我們需要得到一個可供nm命令分析的文件。新建一個庫工程,Framework類型或Static Library類型都可以,將第三方庫的源碼拖入其中,運行產出靜態庫文件。因為後邊分析也是直接跑在MacOS上,所以這裡直接產出當前架構的debug模式庫即可。如果是Static Library類型,我們需要的直接就是.a文件,如果是Framework類型,我們需要的是.framework中的那個同名文件。這兩種文件分析起來無差異,下文統一用.a的情況來說明。

二、分析

不瞭解nm命令的同學可以先看下這個Tutorial,也建議看下完整的man page。下麵以分析AFNetworking庫為例,假如我們的庫名叫libMyAFNetworking,cd到所在目錄執行nm libMyAFNetworking,即可得到每個.o文件中的符號。下圖截取了AFURLRequestSerialization.o中的部分符號。

  AFURLRequestSerialization中的符號

通過nm命令的文檔,我們瞭解到.o文件中頻繁出現的幾種符號是如下定義:

對於每一個符號來說,其類型如果是小寫的,則表明該符號是local的;大寫則表明該符號是global(external)的。

  • B 該符號的值出現在非初始化數據段(bss)中。例如,在一個文件中定義全局static int test。則該符號test的類型為b,位於bss section中。其值表示該符號在bss段中的偏移。一般而言,bss段分配於RAM中。
  • D 該符號位於初始化數據段中。一般來說,分配到data section中。
    例如:定義全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},會分配到初始化數據段中。
  • S 符號位於非初始化數據區,用於small object。
  • T 該符號位於代碼區text section。
  • U 該符號在當前文件中是未定義的,即該符號的定義在別的文件中。
    例如,當前文件調用另一個文件中定義的函數,在這個被調用的函數在當前就是未定義的;但是在定義它的文件中類型是T。但是對於全局變數來說,在定義它的文件中,其符號類型為C,在使用它的文件中,其類型為U。

一般OC文件

現在我們先不考慮category屬性的getter和setter這種私有方法(下文會單獨說明),所以只關註類型是大寫字母的符號。我們可以很容易的歸納出

  • 類型是S,且以_OBJC_CLASS_$_開頭的是類名,以__OBJC_LABEL_PROTOCOL_$_開頭的是協議名,只以下劃線_開頭的是全局常量名
  • 類型是T,且只以下劃線_開頭的是全局函數名
  • 類型是D,且以__OBJC_PROTOCOL_$_開頭的是協議名,不過我們直接用S的規則就可以了。D類型其實也存在以_OBJC_CLASS_$_開頭的類名和以下劃線_開頭的全局常量名,上邊樣例文件中未給出。

有了目標後,我們就可以對於每行符號,用正則[0-9a-f]{16} [STD] (_OBJC_CLASS_\$|__OBJC_LABEL_PROTOCOL_\$)?_([_A-Za-z][^_]\w+)\n來匹配得到目標符號了。但這裡還有個比較坑的問題,對於D類型的符號,可以看到蘋果官方SDK中的協議名也會被列出來,考慮到知名第三方庫一般不會和蘋果官方首碼相同,所以我會過濾掉以官方首碼(如NS、UI、WK等等)開頭的協議名。

C++文件

有些第三方庫包含C++代碼,由於編譯器的name mangling機制,直接用nm命令只能看到更改後的函數名。我們可以用Linux的另一個命令c++filt顯示原本的函數名。

// 同樣是PLCrashAsyncDwarfEncoding.o
// nm libCrashReporter-iOS.a
T __ZN7plcrash3PL_5async18dwarf_frame_reader4initEP21plcrash_async_mobjectPK23plcrash_async_byteorderbb

// nm libCrashReporter-iOS.a | c++filt
T plcrash::PL_::async::dwarf_frame_reader::init(plcrash_async_mobject*, plcrash_async_byteorder const*, bool, bool)

不過要註意下,如果加了c++filt的pipe,得到的符號列表中,t類型會變為"unsigned short",下邊我們分析category屬性時要註意這點。

OC category文件

我們先考慮下對於category,哪些符號會衝突。首先分類名肯定是要改的,但是只保證分類名不同就萬事大吉了嗎?不同分類中的方法和屬性都是往主類的方法列表和屬性列表中插入的,如果我們SDK中使用的第三方庫版本和宿主APP使用的版本不一致,就可能存在分類方法名相同但方法實現不同,分類屬性名相同但關聯對象存取策略不同,導致代碼邏輯錯誤。所以除了分類名,我們還有必要修改分類的屬性名和方法名。
下麵的例子是SDWebImage的UIImage+ForceDecode.o文件中的符號

  UIImage+ForceDecode中的符號

這樣我們可以用另一個正則[0-9a-f]{16} unsigned short [+-]\[\w+\((\w+)\) ([\w:]+)\]\n匹配分類中所需要的符號了。這裡要註意我們只根據屬性的getter方法得到屬性名,而不要把setter方法也加入到需要修改的符號行列。

三、產出巨集定義

我們已經通過第二步的分析獲取到了所有需要重定義的符號,除category屬性外,遍歷一遍,將加了首碼的符號巨集定義為原始符號。對於category屬性則需要點額外操作,可以想象下屬性名為foo,如果要加首碼XN,那麼它的getter方法是直接加首碼為-XNfoo,但setter方法不是直接加首碼變為-XNsetFoo:,而應該是-setXNfoo:了。


  完整流程

分析和產出的過程我已經寫了個Python腳本來做,代碼放在這裡https://github.com/xuning0/RedefineSymbols
。用法的話,比如你要分析的是libMySDWebImage.a,要加的命名空間首碼是ABC,那麼執行python3 redefine_symbols.py --ns ABC libMySDWebImage.a,即可在當前目錄產出ABCNamespace.h文件。如上文所說將其拖入你的SDK工程,設置為預編譯頭文件或在已存在的預編譯頭文件第一行import。以下截圖就是針對SDWebImage產出的ABCNamespace.h部分樣例。

  image

這個腳本可以覆蓋絕大部分情況,但由於OC屬性命名的特殊性,在拿到產出文件後最好人工核查category getter和setter這部分的正確性。

推薦

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • NVL(exp1,exp2)函數時判斷判斷某個值是否為null。若為null,則返回exp2,否則返回exp1 格式1 select nvl(exp1,exp2) from db_table 例子 select monthid,decode(nvl(sale,6000),6000,'NG','OK' ...
  • 前言: 最近也在玩資料庫,感覺普通機子搞資料庫,還是差了點,全文查找,慢的要查一分鐘更久。 但是搞cmd5庫很不錯,億級資料庫,毫秒級。 qq 944520563好吧,下麵開始,首先你得需要一個mysql資料庫,推薦 環境 : apmserv5.2.6 php+mysql Navicat for M ...
  • # Android Sugar ORM (2) ### Android Sugar ORM 實體 #### 1. 創建一個實體類 `Sugar ORM`在創建一個實體的時候, 僅需要使這個實體類繼承於`SugarRecord`即可 ```java public class Book extends ... ...
  • 很簡單,只需要新建一個 drawable 文件 效果概念拓展stroke 是用來處理邊框的,可以修改邊框粗細 和 顏色等等; corners 可以設置邊框的圓角顯示,也可以只是設置某一個圓角; android:bottomLeftRadius -> 設置左下圓角; android:bottomRig... ...
  • 引言 目前經濟增速的放緩,到處都在鼓吹互聯網發展進入下半場。今年跳槽季的遭遇想必大家也是感受到了一絲寒意。筆者有一個朋友在阿裡工作,今年3月底開始請他幫忙內推,也許是阿裡的大前端戰略,也許真的是互聯網的寒意。對於一個三年的iOSer上海這邊一直沒有合適的崗位可推,即便是有也是招P7級別,統統被拒的命 ...
  • iOS 企業賬號配置InHouse類型證書、配置文件流程 一、關於企業賬號 蘋果的開發者賬號分為三種:個人開發者賬號、公司開發者賬號、企業開發者賬號 個人開發者賬號:以個人的名義申請的開發者賬號,有AppleID即可申請;可以在AppStore上發佈應用;可以打包內測版安裝包,需要提供安裝設備的UD ...
  • 解決:UIBarButtonItem size issue.尺寸 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...