ios技巧 -- 靜態庫和動態庫

常用庫文件格式

  • .a:靜態庫
  • .dylib:動態庫
  • .framework:動靜結合的庫
  • .xcframework:特定架構下庫

庫(Library)說?了就是?段編譯好的?進制代碼,加上頭?件就可以供別
?使?。

什么時候會?到庫(Library)?

  1. 某些代碼需要給別?使?,但是我們不希望別?看到源碼,就需要以
    庫的形式進?封裝,只暴露出頭?件。
  2. 對于某些不會進??的改動的代碼,我們想減少編譯的時間,就可以
    把它打包成庫,因為庫是已經編譯好的?進制了,編譯的時候只需
    要 Link ?下,不會浪費編譯時間。

什么是靜態庫?

靜態庫即靜態鏈接庫:可以簡單的看成?組?標?件的集合。即很多?
標?件經過壓縮打包后形成的?件。Windows 下的 .lib,Linux 和 Mac
下的 .a。Mac獨有的.framework。
缺點:
浪費內存和磁盤空間,模塊更新困難

什么是動態庫?

與靜態庫相反,動態庫在編譯時并不會被拷?到?標程序中,?標程序
中只會存儲指向動態庫的引?。等到程序運?時,動態庫才會被真正加
載進來。格式有:.framework、.dylib、.tdb。
缺點:
會導致?些性能損失。但是可以優化,?如延遲綁定(Lazy Binding)技術

什么是tdb格式?

tbd全稱是text-based stub libraries,本質上就是?個YAML描述的?本?
件。
他的作?是?于記錄動態庫的?些信息,包括導出的符號、動態庫的架構信息、動
態庫的依賴信息
?于避免在真機開發過程中直接使?傳統的dylib。
對于真機來說,由于動態庫都是在設備上,在Xcode上使?基于tbd格式的偽
framework可以??減少Xcode的??。

Framework

Mac OS/iOS 平臺還可以使? Framework。Framework 實際上是?種打包
?式,將庫的?進制?件,頭?件和有關的資源?件打包到?起,?便管
理和分發。
Framework 和系統的 UIKit.Framework 還是有很?區別。系統的
Framework 不需要拷?到?標程序中,我們??做出來的 Framework 哪怕
是動態的,最后也還是要拷?到 App 中(App 和 Extension 的 Bundle 是
共享的),因此蘋果?把這種 Framework 稱為 Embedded Framework。

Embedded Framework

開發中使?的動態庫會被放?到ipa下的framework?錄下,基于沙盒運?。
不同的App使?相同的動態庫,并不會只在系統中存在?份。?是會在多
個App中各?打包、簽名、加載?份。

Mach-o File Format

?個Mach-o?件有兩部分組成:header 和data。
header:代表了?件的映射,描述了?件的內容以及?件所有內容所在的位置。
data:緊跟header之后,由多個?進制組成,one by one。

Load Commands

進制?件加載進內存要執?的?些指令。
這?的指令主要在負責我們 APP 對應進程的創建和基本設置(分配虛擬內
存,創建主線程,處理代碼簽名/加密的?作),然后對動態鏈接庫(.dylib
系統庫和我們??創建的動態庫)進?庫加載和符號解析的?作。

Mach-o File Format

?個Mach-o?件有兩部分組成:header 和data。
header:包含三種類型。Mach header,segment,sections
header內的section描述了對應的?進制信息。
注意?:Mach header屬于header的?部分,它包含了整個?件的信息和segment信息。

Embedded Framework

開發中使?的Embedded Framework會被放?到ipa下的Frameworks?錄下,基
于沙盒運?。
不同的App使?相同的動態庫,并不會只在系統中存在?份。?是會在多
個App中各?打包、簽名、加載?份。

這里我們通過file查看AFN的.a文件,打開終端
file libAFNetworking.a:查看文件的格式

libAFNetworking.a: current ar archive

ar -t libAFNetworking.a查看.a文件的合集

__.SYMDEF
AFAutoPurgingImageCache.o
AFHTTPSessionManager.o
AFImageDownloader.o
AFNetworkActivityIndicatorManager.o
AFNetworking-dummy.o
AFNetworkReachabilityManager.o
AFSecurityPolicy.o
AFURLRequestSerialization.o
AFURLResponseSerialization.o
AFURLSessionManager.o
UIActivityIndicatorView+AFNetworking.o
UIButton+AFNetworking.o
UIImageView+AFNetworking.o
UIProgressView+AFNetworking.o
UIRefreshControl+AFNetworking.o
WKWebView+AFNetworking.o

clang

NAME
       clang - the Clang C, C++, and Objective-C compiler

SYNOPSIS
       clang [options] filename ...

DESCRIPTION
       clang  is  a C, C++, and Objective-C compiler which encompasses prepro-
       cessing, parsing, optimization, code generation, assembly, and linking.
       Depending  on  which high-level mode setting is passed, Clang will stop
       before doing a full link.  While Clang  is  highly  integrated,  it  is
       important to understand the stages of compilation, to understand how to
       invoke it.  These stages are:

clang命令參數:
-x: 指定編譯文件語言類型
-g: 生成調試信息
-c: 生成目標文件,只運行preprocess,compile,assemble,不鏈接
-o: 輸出文件
-isysroot: 使用的SDK路徑
1. -I<directory> 在指定目錄尋找頭文件 header search path
2. -L<dir> 指定庫文件路徑(.a.dylib庫文件) library search path
3. -l<library_name> 指定鏈接的庫文件名稱(.a.dylib庫文件)other link flags -lAFNetworking
-F<directory> 在指定目錄尋找framework framework search path
-framework <framework_name> 指定鏈接的framework名稱 other link flags -framework AFNetworking

將.m編譯成.o文件

//-x 指定編譯語言
clang -x objective-c \
//-target 指定編譯平臺
-target x86_64-apple-macos10.15 \
//編譯成arc環境,10.6一下用-fno-objc-arc
-fobjc-arc \
//指定需要的sdk路徑 并找到自己xcode目錄下的sdk路徑,在這使用的是MacOSX,也可以換成對應的手機或者模擬器
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
//鏈接庫
-I./AFNetworking \
//將.m編譯成.o
-c test.m -o test.o

目標文件就可以通過重定位符號表,鏈接重定位。所以我們只需要導入頭文件即可
將.o編譯成可執行文件

clang -target x86_64-apple-macos10.5 \
//編譯成arc環境,10.6一下用-fno-objc-arc
-fobjc-arc \
//指定需要的sdk路徑 并找到自己xcode目錄下的sdk路徑,在這使用的是MacOSX,也可以換成對應的手機或者模擬器
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
//鏈接庫
-L./AFNetworking \
-lAFNetworking
//將.m編譯成.o
test.o -o test

靜態庫其實就是一個.o文件的合集

合并靜態庫

libtool

添加或更新靜態庫的目錄表

NAME
       libtool - create libraries
       ranlib - add or update the table of contents of archive libraries
libtool \
//代表要合并的是一個靜態庫
-static \
//輸出靜態庫的名稱
-o libCat.a \
//需要合并的靜態庫
libAFNetworking.a \
libSDWebImage.a

ar

ar壓縮目標文件,并對其進行編號和索引,形成靜態庫。同時也可以解壓縮靜態庫,查看有哪些目標文件:
ar -rc a.a a.o
-r: 像a.a添加or替換文件
-c: 不輸出任何信息
-t: 列出包含的目標文件

生成workspace

![addFile.png](https://upload-images.jianshu.io/upload_images/2414707-87ed3515a891192e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

添加文件之前需要xcode中所有的窗口

動態庫

//支持的架構
ld -dylib -arch x86_64 \
//支持最小的系統版本
-macosx_version_min 10.15 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
//system:dyld相關的鏈接器
-lsystem -framework Foundation \
//符號全部導出
-all_load \
libTestExample.a -o libTestExample.dylib

動態庫是.o文件鏈接過后的產物

什么是tdb格式?

tbd全稱是text-based stub libraries,本質上就是?個YAML描述的?本?
件。
他的作?是?于記錄動態庫的?些信息,包括導出的符號、動態庫的架構信息、動
態庫的依賴信息
?于避免在真機開發過程中直接使?傳統的dylib。
對于真機來說,由于動態庫都是在設備上,在Xcode上使?基于tbd格式的偽
framework可以??減少Xcode的??。

在向著上面的方式鏈接后,我們在鏈接動態庫運行時一直會image not found

12.png

我們通過otool -l test | grep 'DYLIB' -A 5遇到DYLIB向下查找5行(-B是向上)

          cmd LC_LOAD_DYLIB
      cmdsize 72
         name TestExample (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 0.0.0
compatibility version 0.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 96
         name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1675.129.0
compatibility version 300.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libobjc.A.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 228.0.0
compatibility version 1.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libSystem.B.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1281.100.1
compatibility version 1.0.0
--
          cmd LC_LOAD_DYLIB
      cmdsize 104
         name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 1675.129.0
compatibility version 150.0.0

我們可以看到其他的動態庫的name都是一個路徑TestExample只有一個名字
動態庫的路徑是保存在自己的mach-o中的

install_name_tool

NAME
       install_name_tool - change dynamic shared library install names

SYNOPSIS
       install_name_tool  [-change  old  new  ]  ...  [-rpath  old  new  ] ...
       [-add_rpath new ] ... [-delete_rpath new ] ... [-id name] file

DESCRIPTION
       Install_name_tool changes the dynamic shared library install names  and
       or  adds,  changes  or  deletes the rpaths recorded in a Mach-O binary.
       For this tool to work when the install names or rpaths are  larger  the
       binary  should  be  built  with  the ld(1) -headerpad_max_install_names
       option.

       -change old new
              Changes the dependent shared library install name old to new  in
              the specified Mach-O binary.  More than one of these options can
              be specified.  If the Mach-O binary does  not  contain  the  old
              install  name  in  a  specified  -change  option  the  option is
              ignored.
       -id name
              Changes the shared library  identification  name  of  a  dynamic
              shared  library  to name.  If the Mach-O binary is not a dynamic
              shared library and the -id option is specified it is ignored.

       -rpath old new
              Changes the rpath path name old to new in the  specified  Mach-O
              binary.   More  than  one of these options can be specified.  If
              the Mach-O binary does not contain the old rpath path name in  a
              specified -rpath it is an error.

       -add_rpath new
              Adds  the  rpath  path  name new in the specified Mach-O binary.
              More than one of these options can be specified.  If the  Mach-O
              binary  already  contains  the  new rpath path name specified in
              -add_rpath it is an error.

       -delete_rpath old
              deletes the rpath path name old in the specified Mach-O  binary.
              More  than one of these options can be specified.  If the Mach-O
              binary does not contains the old rpath path  name  specified  in
              -delete_rpath it is an error.

執行Install_name_tool -id 路徑 目標文件

@rpath

Install_name_tool命令中提供的路徑是絕對路徑,有什么可以比較方便的解決路徑問題呢, @rpath
@rpath代表的誰鏈接我,誰提供路徑,這個是因為@rpath存在鏈接文件的mach-o中,鏈接時,傳遞給被鏈接的Mach-o中的@rpath
我們上面的命令可以改為Install_name_tool -id @rpath/Frameworks/TestExample.framework/TestExample TestExample然后在需要鏈接的文件中Install_name_tool -add_rpath 路徑 目標文件
@executable_path:表示可執?程序所在的?錄,解析為可執??件的絕對路徑。
@loader_path:表示被加載的Mach-O 所在的?錄,每次加載時,都可能
被設置為不同的路徑,由上層指定。

XCFramework

XCFramework:是蘋果官?推薦的、?持的,可以更?便的表示?個多
個平臺和架構的分發?進制庫的格式。
需要Xcode11以上?持。
是為更好的?持Mac Catalyst和ARM芯?的macOS。
專?在2019年提出的framework的另?種先進格式。

iOS/iPad:arm64
iOS/iPad Simulator: x86_64 arm64
Mac Catalyst: x86_64 arm64
Mac: x86_64 arm64

和傳統的framework相?:

  1. 可以?單個.xcframework?件提供多個平臺的分發?進制?件;
  2. 與Fat Header相?,可以按照平臺劃分,可以包含相同架構的不同平
    臺的?件;
  3. 在使?時,不需要再通過腳本去剝離不需要的架構體系。
  • xcodebuild
    將文件進行編譯打包
xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
//指定編譯平臺
-destination 'generic/platform=iOS Simulator' \
//輸出路徑
-archivePath '../archives/SYTimer.framework-iphonesimulator.xcarchive' \
//將編譯的產物拷貝到目錄中
SKIP_INSTALL=NO

指定他的編譯環境進行打包

//編譯為真機環境下
xcodebuild archive -project 'SYTimer.xcodeproj' \
-scheme 'SYTimer' \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath '../archives/SYTimer.framework-iphoneos.xcarchive' \
SKIP_INSTALL=NO

平常我們打包時都是多架構打包在一塊的

  • lipo:創建或操作通用文件
  • 胖二進制 :多個架構打包到一起

動態庫合并只能合并不同架構的

lipo -output 
//指定輸出名稱
SYTimer 
-create 
//真機環境下的
../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer 
//模擬器環境下的
../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

提取出特定架構下的動態庫

lipo -output SYTimer-x86_64 -extract x86_64 ../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework/SYTimer

但是還需要前面,dSYM之類信息

xcframework目前只能通過命令行生成

xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-output 'SYTimer.xcframework'

就會根據生成不同平臺的framework
還需要把調試符添加進去

xcodebuild -create-xcframework \
-framework '../archives/SYTimer.framework-iphoneos.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/xxx/xxx/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/6142AB7C-A46D-33C9-A32B-405B93A1D680.bcsymbolmap' \
-debug-symbols '/Users/xxx/xxx/archives/SYTimer.framework-iphoneos.xcarchive/BCSymbolMaps/314A0F21-A400-3D1F-ACBE-258D8A3C919F.bcsymbolmap' \
-framework '../archives/SYTimer.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SYTimer.framework' \
-debug-symbols '/Users/xxx/xxx/archives/SYTimer.framork-iphonesimulator.xcarchive/dSYMs/SYTimer.framework.dSYM' \
-output 'SYTimer.xcframework'

這里-debug-symbols需要的是絕對路徑。

生成的xcframework.png

  • weak_framework

我們在 xcconfig中配置

// 2. -F: frmaework 所在的目錄
FRAMEWORK_SEARCH_PATHS = $(inherited) ${SRCROOT}
// 1. -I :頭文件
HEADER_SEARCH_PATHS = $(inherited) ${SRCROOT}/SYTimer.framework/Headers
// 路徑
LD_RUNPATH_SEARCH_PATHS = $(inherited)
// 3. 名稱
// null -》 runtime -〉 nil
// weak_import
// library
OTHER_LDFLAGS = $(inherited) -Xlinker -weak_framework -Xlinker "SYTimer"

設置了weak_framework當沒有找到定義的符號是就會顯示為null。

鏈接兩個相同的靜態庫

OTHER_LDFLAGS = $(inherited) -l"AFNetworking" -l"AFNetworking2"
鏈接兩個相同的靜態庫,不同的名字,不會發生沖突,因為引入的時候默認是-noall_load,會做個代碼剝離,如果找到了就不會進行鏈接。

動態庫鏈接動態庫

我們通過cocoapods導入時只是生成鏈接器的參數,并不會真正導入。

  • 導入動態庫的時候通過他給我們的腳本動態的拷貝到ipa包中。
    我們在Podfile中使用use_frameworks!
if [[ "$CONFIGURATION" == "Debug" ]]; then
  install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
  install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
  • 在我們項目的target下再導入一次

如果動態庫想使用APP中的符號,我們可以OTHER_LDFLAGS添加
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -Xlinker -undefined -Xlinker dynamic_lookup
使用-undefined會有風險,如果沒有定義的符號也會通過,我們也可以用
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -Xlinker -U -Xlinker _OBJC_CLASS_$_LGAppObject來指定特定的符號

動態庫鏈接靜態庫

Podfile中不使用use_frameworks!
可正常運行
如果想隱藏靜態庫的符號可使用-hidden-l
OTHER_LDFLAGS = $(inherited) -ObjC -Xlinker -hidden-l"AFNetworking"

靜態庫鏈接靜態庫

我們需要手動配置鏈接的靜態庫信息
LIBRARY_SEARCH_PATHSOTHER_LDFLAGS

靜態庫鏈接動態庫

app = app + 靜態庫 所以和動態鏈接動態差不多

cocoapods 即導入靜態庫又導入動態庫

platform :ios, '14.1'
target :'LGNetworkManager' do
  use_frameworks!
  # 靜態庫、動態庫
  # 指定需要被編譯成static_framework的庫
  $static_framework = ['AFNetworking']
  
  pre_install do |installer|
  installer.pod_targets.each do |pod|
        if $static_framework.include?(pod.name)
            def pod.build_type;
              Pod::Target::BuildType.static_framework
            end
        end
    end
  end
  pod 'SDWebImage'
end

Module

Module(模塊)-最小的代碼單元
一個Module是機器代碼和數據的最小單元,可以獨立于其他代碼單元進行鏈接。
通常,Module是通過編譯單個原文件生成的目標文件。例如,當前的test.m被編譯成目標文件test.o時,當前的目標文件就代表了一個Module。

AFN中的module

//framework module 名稱 AFNetworking
framework module AFNetworking {
  umbrella header "AFNetworking-umbrella.h"
//將所有的頭文件重新導出
  export *
//將上面子module全部導出
  module * { export * }
}

umbrella代表一個目錄代表傘柄, 這個目錄下所有.h代表傘骨

AFNetworking-umbrella.png

這一個文件里包含所有的頭文件
explicit顯示指明子module名稱
在文件中通過@import就可以直接導入模塊,例@import AFNetworking.AFHTTPSessionManager;
其實只要開啟了module#include#import最后都會轉換成@import
更多用法可參考官網

一般生成 framework后會自動生成一個module,如果要手動創建一個,在創建完.modulemap, 還需要在build settings中配置module file map

framework module LGOCFramework {
    umbrella "Headers"
    export *
    module * {
        export *
    }
}

展開就是下面

framework module LGOCFramework {
    // umbrella<目錄>
    umbrella header "LGOCFramework.h"
  
    explicit module LGTeacher {
        header "LGTeacher.h"
        export *
    }
    explicit module LGStudent {
        header "LGStudent.h"
        export *
    }
}

swiftframework是沒有橋接文件的,我們就需要module,如果我們只想在framework中使用,需要將其設置為私有的。
文件命名為xxx.private.modulemap

framework module LGSwiftFramework_Private {
    module LGOCStudent {
        header "LGOCStudent.h"
        export *
    }
}

最后在build settings中配置Private Module file map

swift通過 libtool合并靜態庫后在xcconfig中配置

// OTHER_CFLAGS:傳遞給用來編譯C或者OC的編譯器,當前就是clang
OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap"

// SWIFT_INCLUDE_PATHS: 傳遞給SwiftC編譯器,告訴他去下面的路徑中查找module.file
SWIFT_INCLUDE_PATHS="${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework"  "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework"

apinotes

我們在通過OC轉swift類時通過宏定義一個個轉

// 通過指定NS_SWIFT_NAME宏,我們可以添加一些詳細信息以使函數清晰可見,從而使其變得如下所示:
// 規范
- (nullable NSString *)teacherNameForIndex:(NSUInteger)index NS_SWIFT_NAME(teacherName(forIndex:));

// NS_REFINED_FOR_SWIFT從現在開始,Swift的Clang Importer將做一些額外的工作并將該方法導入為私有方法,并以雙下劃線字符開頭__,例如:
//- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options;
- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options NS_REFINED_FOR_SWIFT;

如果量太多我們就可以使用apinotes,文件為SDK名稱.apinotes文件放在SDK目錄下。
這個格式是yaml格式

#yaml
---
Name: OCFramework
Classes:
- Name: LGToSwift
//Swift名
  SwiftName: ToSwift
  Methods:
  - Selector: "changeTeacherName:"
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
//設置不可以使用
    Availability: nonswift
    AvailabilityMsg: "這個不能用"
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true

具體信息可以通過官網查詢

符號與庫總結.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容