天天看點

module & Swift庫

一個

module

是機器代碼和資料的最小機關,可以獨立于其他代碼機關進行連結

通常,

module

是通過編譯單個源檔案生成的目标檔案。例如:目前的

test.m

被編譯成目标檔案

test.o

時,目前的目标檔案就代表了一個

module

這裡有一個問題,

module

在調用的時候會産生開銷,當使用一個靜态庫的時:@import TestStaticFramework;

如果靜态庫中包含許多

.o

檔案。這豈不是會導入很多

module

當然不會。在靜态連結的時候,也就是靜态庫連結到主項目或者動态庫,最終生成可執行檔案或者動态庫時,靜态連結器可以把多個

module

連結優化成一個,來減少本來多個

module

直接調用的問題
module原理
未開啟

module

時,當

B

檔案導入

A.h

C

檔案又導入了

A.h

B.h

*

#include

A.h

會跟随

B

檔案和

C

檔案編譯多次。使用

#include

造成

C

檔案重複包含

A.h

,是以當

C

檔案編譯時,

A.h

又會被編譯多次,相當于編譯了`NM

次 *

#import

A.h

依然會跟随

B

檔案和

C

檔案編譯多次。但使用

#import

可以避免

C

檔案重複包含

A.h

,此時

C

檔案編譯,

A.h

隻編譯一次,相當于編譯了

NM`次

開啟

module

時,頭檔案會被預先編譯成二進制檔案,并且每個頭檔案隻會被編譯一次。此時無論有多少檔案導入頭檔案,都不會被重複編譯,隻需要執行

N

次即可

Cat

目錄中,有

A.h

B.h

兩個頭檔案,還有一個

use.c

代碼和一個

module.modulemap

檔案。和

Cat

目錄平級,建立

prebuilt

目錄,用來存儲編譯後的

module

緩存

打開A.h檔案,寫入以下代碼:

#ifdef ENABLE_A
void a() {}
#endif           

複制

打開

B.h

檔案,寫入以下代碼:
#import "A.h"           

複制

打開

use.c

檔案,寫入以下代碼:
#import "B.h"
void use() {
#ifdef ENABLE_A
 a();
#endif
}           

複制

use.c

檔案中,使用了

B.h

,同時

B.h

使用了

A.h

打開

module.modulemap

檔案,寫入以下代碼:
module A {
 header "A.h"
}

module B {
 header "B.h"
 export A
}           

複制

module.modulemap

檔案的作用,它是用來描述

頭檔案

module

之間映射的關系

定義了名稱為

A

B

的兩個

module

module A

中,定義了

header A.h

,表示

module A

A.h

的映射關系

module B

中,定義了

header B.h

,和

A

同理。

export A

表示将

B.h

導入的

A.h

頭檔案重新導出

通過

clang

指令,開啟

module

并将

use.c

編譯成目标檔案clang -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o

-fmodules

:允許使用

module

語言來表示頭檔案

-fmodule-map-file

module map

的路徑。此參數缺失,預設找

module.modulemap

檔案。如果檔案不存在,執行會報錯

-fmodules-cache-path

:編譯後的

module

緩存路徑

打開

prebuilt

目錄,兩個

.pcm

檔案,分别對應

A.h

B.h

,它們就是預編譯頭檔案後的産物

module

Xcode

中是預設開啟的

如果在

Build Settings

中,将

Enable Modules

設定為

NO

,導入頭檔案将不能使用

@import

方式

開啟

module

後,項目中導入頭檔案,無論使用

#include

#import

@import

中何種方式,最終都會映射為

@import

方式
module解讀
檢視實際開發中使用的

.modulemap

檔案,例如:

AFNetworking

打開

AFNetworking.framework

中的

module.modulemap

檔案framework module AFNetworking
{
 umbrella header "AFNetworking-umbrella.h"

 export *
 module * { export * }
}           

複制

定義

module

名稱為

AFNetworking

,子產品是

framework

umbrella

:可以了解為

傘柄

。一把雨傘的

傘柄

下有很多

傘骨

umbrella

的作用是指定一個目錄,這個目錄即為

傘柄

,目錄下所有

.h

頭檔案即為

傘骨umbrella header AFNetworking-umbrella.h

:指定

module AFNetworking

映射

AFNetworking-umbrella.h

檔案中所有

.h

頭檔案

export *

*

表示通配符。将

AFNetworking-umbrella.h

檔案中,所有

.h

頭檔案重新導出

module * { export * }

:建立子

module

,使用

*

通配符,将

AFNetworking- umbrella.h

中導入的頭檔案,按照頭檔案名稱命名為子

module

名稱。再使用

export *

将子

module

中導入的頭檔案重新導出

打開

AFNetworking-umbrella.h

檔案
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

#import "AFNetworking.h"
#import "AFHTTPSessionManager.h"
#import "AFURLSessionManager.h"
#import "AFCompatibilityMacros.h"
#import "AFNetworkReachabilityManager.h"
#import "AFSecurityPolicy.h"
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
#import "AFAutoPurgingImageCache.h"
#import "AFImageDownloader.h"
#import "AFNetworkActivityIndicatorManager.h"
#import "UIActivityIndicatorView+AFNetworking.h"
#import "UIButton+AFNetworking.h"
#import "UIImageView+AFNetworking.h"
#import "UIKit+AFNetworking.h"
#import "UIProgressView+AFNetworking.h"
#import "UIRefreshControl+AFNetworking.h"
#import "WKWebView+AFNetworking.h"

FOUNDATION_EXPORT double AFNetworkingVersionNumber;
FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];           

複制

AFNetworking-umbrella.h

檔案,相當于

傘柄

AFNetworking-umbrella.h

檔案中,導入的所有

.h

頭檔案,相當于

傘骨

項目中,使用

@import AFNetworking

,可以

.

出一個子

module

清單,它對應的也是

傘柄

下的

傘骨

清單

檢視開源項目

AsyncDisplayKit

中的

module.modulemap

打開

module.modulemap

檔案framework module AsyncDisplayKit
{
 umbrella header "AsyncDisplayKit.h"

 export *
 module * {
   export *
 }

 explicit module ASControlNode_Subclasses {
   header "ASControlNode+Subclasses.h"
   export *
 }

 explicit module ASDisplayNode_Subclasses {
   header "ASDisplayNode+Subclasses.h"
   export *
 }

}           

複制

定義

module

名稱為

AsyncDisplayKit

,子產品是

framework

定義傘柄

AsyncDisplayKit.h

AsyncDisplayKit.h

檔案中,所有

.h

頭檔案重新導出

建立子

module

,使用

*

通配符,将

AsyncDisplayKit.h

中導入的頭檔案,按照頭檔案名稱命名為子

module

名稱。将子

module

中導入的頭檔案重新導出

explicit

:顯示指明子

module

名稱

官方文檔

更多

API

可檢視

官方文檔

自定義module
搭建

LGOCFramework

項目

LGOCFramework

是一個動态庫項目,建立項目後,系統預設并不提供

.modulemap

檔案

項目編譯後,在

LGOCFramework.framework

中的

Modules

目錄下,會自動生成

module.modulemap

檔案

打開

module.modulemap

檔案,裡面存儲了基本的頭檔案與

module

之間映射的關系
/* module.modulemap */

framework module LGOCFramework {
   // umbrella<目錄>
   umbrella header "LGOCFramework.h"

   explicit module LGTeacher {
       header "LGTeacher.h"
       export *
   }
   explicit module LGStudent {
       header "LGStudent.h"
       export *
   }
}           

複制

如果想對

module

進行配置,例如:定義子

module

,此時需要自己建立

modulemap

檔案

在項目

LGOCFramework

目錄下,建立

LGOCFramework.modulemap

檔案

LGOCFramework.modulemap

檔案加入到項目中

BuildSetting

中,修改

Module Map File

配置項:

Module Map File

:設定

.modulemap

檔案路徑,填寫

${SRCROOT}

之後的路徑即可

打開

LGOCFramework.modulemap

檔案,寫入以下代碼:
framework module LGOCFramework {
   umbrella header "LGOCFramework.h"

   explicit module LGTeacher {
       header "LGTeacher.h"
       export *
   }
   explicit module LGStudent {
       header "LGStudent.h"
       export *
   }
}           

複制

定義

module

名稱為

LGOCFramework

,子產品是

framework

定義傘柄

LGOCFramework.h

顯示指明子

module

名稱為

LGTeacher

,映射

LGTeacher.h

,将

LGTeacher.h

中導入的頭檔案重新導出

顯示指明子

module

名稱為

LGStudent

,映射

LGStudent.h

,将

LGStudent.h

中導入的頭檔案重新導出

項目編譯後,在

LGOCFramework.framework

中的

Modules

目錄下,生成的依然是名稱為

module.modulemap

的檔案

由于系統預設識别

.modulemap

檔案的名稱是

module.modulemap

,是以自定義的

LGOCFramework.modulemap

檔案在編譯後,名稱依然是

module.modulemap

,但裡面的内容已經生效

搭建

LGApp

項目

LGApp

是一個

App

項目

建立

MulitProject.xcworkspace

,加入

LGOCFramework

動态庫項目。

LGApp

連結

LGOCFramework

動态庫

打開

ViewController.m

檔案,導入

LGOCFramework

動态庫的頭檔案,和

module

中的配置完全一緻

至此自定義

module

成功
Swift庫使用OC代碼

module映射

搭建

LGSwiftFramework

項目

LGSwiftFramework

是一個

Swift

動态庫項目

打開

LGOCStudent.h

檔案,寫入以下代碼:
#import <Foundation/Foundation.h>

@interface LGOCStudent : NSObject

- (void)speek;

@end           

複制

打開

LGOCStudent.m

檔案,寫入以下代碼:
#import "LGOCStudent.h"

@implementation LGOCStudent

- (void)speek {
   NSLog(@"LGOCStudent--speek");
}

@end           

複制

打開

LGSwiftTeacher.swift

檔案,寫入以下代碼:
import Foundation

@objc open class LGSwiftTeacher: NSObject {

   public func speek() {
       let s = LGOCStudent()
       s.speek()
       print("speek!")
   }

   @objc public func walk() {
       print("walk!")
   }
}           

複制

LGSwiftTeacher.swift

檔案中,調用了

OC

代碼。在日常項目中,使用橋接檔案即可。但在

Framework

項目中,沒有橋接檔案的概念,此時編譯報錯

解決辦法:

建立

LGSwiftFramework.modulemap

檔案,寫入以下代碼:
framework module LGSwiftFramework {
   umbrella "Headers"
   export *
}           

複制

定義

module

名稱為

LGSwiftFramework

,子產品是

framework

定義傘柄

Headers

目錄

Headers

目錄下所有

.h

頭檔案重新導出

BuildSetting

中,修改

Module Map File

配置項:

Headers

目錄下的

.h

頭檔案

此時

LGSwiftTeacher.swift

檔案中,使用的

OC

代碼不再報錯,項目編譯成功

App使用Swift庫

承接

自定義module

的案例

打開

MulitProject.xcworkspace

檔案,加入

LGSwiftFramework

動态庫項目。

LGApp

連結

LGSwiftFramework

動态庫

LGApp

中,打開

ViewController.m

檔案,使用

@import LGSwiftFramework

導入頭檔案,隻能找到一個

.Swift

LGSwiftFramework

項目在編譯時,系統在

.framework

中生成的

module.modulemap

檔案,會自動生成以下代碼:
framework module LGSwiftFramework {
   umbrella "Headers"
   export *
}

module LGSwiftFramework.Swift {
   header "LGSwiftFramework-Swift.h"
   requires objc
}           

複制

但這種導入方式,無法使用

LGOCStudent

解決辦法:

使用

#import

方式,也無法找到

LGOCStudent.h

頭檔案

LGSwiftFramework

中的

.modulemap

檔案,将

Headers

目錄下所有

.h

檔案全部重新導出。是以可以強行導入

<LGSwiftFramework/LGOCStudent.h>

,導入後

LGOCStudent

類可以正常使用
#import "ViewController.h"
#import <LGSwiftFramework/LGOCStudent.h>

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   LGOCStudent *student=[LGOCStudent new];
}

@end           

複制

另一種解決辦法,通過

.modulemap

檔案,暴露出

LGOCStudent

打開

LGSwiftFramework.modulemap

檔案,改為以下代碼:
framework module LGSwiftFramework {
   umbrella "Headers"
   export *
}

module LGSwiftFramework.LGOCStudent {
   header "LGOCStudent.h"
   requires objc
}           

複制

再次編譯項目,使用

@import

方式,此時可以找到

LGOCStudent

導入

LGSwiftFramework.LGOCStudent

後,

LGOCStudent

類可以正常使用
#import "ViewController.h"
@import LGSwiftFramework.LGOCStudent;

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   LGOCStudent *student=[LGOCStudent new];
}

@end           

複制

私有module映射

在某些情況下,是否使用特定頭檔案用于區分指定庫的

公共API

私有API

例如:一個庫可能包含分别提供

公共API

私有API

的頭檔案

LGOCStudent.h

LGOCStudent_Private.h

。此外,

LGOCStudent_Private.h

可能僅在某些版本的庫中可用,而在其他版本庫中不可用。使用統一的

module.modulemap

檔案無法表達這一點

LGSwiftFramework

項目

建立

LGOCStudent_Private.h

檔案,寫入以下代碼:
#import <Foundation/Foundation.h>

@interface LGOCStudent_Private : NSObject

- (void)speek;

@end           

複制

建立

LGOCStudent_Private.m

檔案,寫入以下代碼:
#import "LGOCStudent_Private.h"

@implementation LGOCStudent_Private

- (void)speek {
   NSLog(@"LGOCStudent_Private--speek");
}

@end           

複制

建立

LGSwiftFramework.private.modulemap

檔案,寫入以下代碼:
framework module LGSwiftFramework_Private {
   module LGOCStudent {
       header "LGOCStudent_Private.h"
       export *
   }
}           

複制

私有

.modulemap

檔案的名稱,中間的

.private

一定要加,這個是命名規則

定義

module

名稱為

LGSwiftFramework_Private

,子產品是

framework

定義私有

module

名稱,後面一定要加

Private

字尾,并且首字母大寫

定義

module

名稱為

LGOCStudent

,映射

LGOCStudent_Private.h

LGOCStudent_Private.h

中導入的頭檔案重新導出

BuildSetting

中,修改

Private Module Map File

配置項:

LGApp

項目
打開ViewController.m檔案,導入LGOCStudent.h和LGOCStudent_Private.h頭檔案,此時它們被徹底分開了#import "ViewController.h"
@import LGSwiftFramework.LGOCStudent;
@import LGSwiftFramework_Private.LGOCStudent;

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];

   LGOCStudent *student=[LGOCStudent new];
   LGOCStudent_Private *sp=[LGOCStudent_Private new];
}

@end           

複制

Swift靜态庫

Xcode 9

之後,

Swift

開始⽀持靜态庫

Swift

沒有頭⽂件的概念,外界如何使⽤

Swift

public

修飾的類和函數?

Swift

庫中引⼊了⼀個全新的⽂件

.swiftmodule

.swiftmodule

包含序列化過的

AST

(抽象文法樹,

Abstract Syntax Tree

),也包含

SIL

Swift

中間語⾔,

Swift Intermediate Language

Swift靜态庫合并

搭建

LGSwiftA

項目

LGSwiftA

是一個

Swift

靜态庫項目

打開

LGSwiftTeacher.swift

檔案,寫入以下代碼:
import Foundation

@objc open class LGSwiftTeacher: NSObject {

   public func speek() {
       print("speek!")
   }

   @objc public func walk() {
       print("walk!")
   }
}           

複制

搭建

LGSwiftB

項目

LGSwiftB

是一個

Swift

靜态庫項目

打開

LGSwiftTeacher.swift

檔案,寫入以下代碼:
import Foundation

@objc open class LGSwiftTeacher: NSObject {

   public func speek() {
       print("speek!")
   }

   @objc public func walk() {
       print("walk!")
   }
}           

複制

建立

MulitProject.xcworkspace

,加入

LGSwiftA

LGSwiftB

兩個靜态庫項目

建立

Products

目錄,和

MuiltProject.xcworkspace

平級

LGSwiftA

LGSwiftB

項目中,選擇

Build Phases

,建立

Run Script

,寫入以下代碼:cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"

使用

cp

指令,将編譯後的

.framework

檔案拷貝到

Products

目錄

編譯

LGSwiftA

LGSwiftB

項目,打開

Products

目錄,

.framework

檔案已成功拷貝

使用

libtool

指令,合并

LGSwiftA

LGSwiftB

兩個靜态庫libtool -static \

-o \

libLGSwiftC.a \

LGSwiftA.framework/LGSwiftA \

LGSwiftB.framework/LGSwiftB

由于

LGSwiftA

LGSwiftB

項目中,都存在了相同的

LGSwiftTeacher.swift

檔案,使用

libtool

指令合并後提示警告libtool: warning same member name (LGSwiftTeacher.o) in output file usedfor input files: LGSwiftA.framework/LGSwiftA(LGSwiftTeacher.o) and:

LGSwiftB.framework/LGSwiftB(LGSwiftTeacher.o) due to use of basename,

truncation and blank padding

使用

ar -t libLGSwiftC.a

指令,檢視

libLGSwiftC.a

的檔案清單__.SYMDEF

LGSwiftA_vers.o

LGSwiftTeacher.o

LGSwiftB_vers.o

LGSwiftTeacher.o

如果是

OC

動态庫,

.framework

中可以舍棄

Modules

目錄,将兩個靜态庫的頭檔案拷貝到一起即可

Swift

動态庫,包含了

x.swiftmodule

目錄,裡面的

.swiftmodule

檔案不能舍棄,此時應該如何處理?

解決辦法:

Products

目錄下,建立

LGSwiftC

目錄,将庫檔案

libLGSwiftC.a

拷貝到

LGSwiftC

目錄下

仿照

Cocoapods

生成三方庫的目錄結構,在

LGSwiftC

目錄下,建立

Public

目錄,将

LGSwiftA.framework

LGSwiftB.framework

拷貝到

Public

目錄下

打開

LGSwiftA.framework

LGSwiftB.framework

檔案,将裡面的庫檔案、

.plist

檔案、簽名等資訊全部删除,最終隻保留

Headers

Modules

兩個目錄

雖然生成

.framework

時,自動建立了

Modules

目錄。但編譯時,

.modulemap

檔案和

x.swiftmodule

目錄,應該和

Headers

目錄平級

.modulemap

檔案和

x.swiftmodule

目錄,從

Modules

目錄移動到

.framework

檔案下,和

Headers

目錄平級。然後删除

Modules

目錄

此時靜态庫合并完成

App使用合并後的靜态庫

搭建

LGApp

項目

LGApp

是一個

App

項目

LGSwiftC

目錄,拷貝到

LGApp

項目的根目錄下

libLGSwiftC.a

庫檔案,拖動到項目中的

Frameworks

目錄

勾選

Copy items if needed

,點選

Finish

建立

xcconfig

檔案,并配置到

Tatget

上,寫入以下代碼:
HEADER_SEARCH_PATHS = $(inherited)'${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers'
    HEADER_SEARCH_PATHS = $(inherited)
'${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers'           

複制

指定頭檔案路徑

Header Search Paths

ViewController.m

中,使用

module

方式導入

LGSwiftA

,編譯報錯

使用

module

方式,還需要加載

modulemap

檔案的路徑

打開

xcconfig

檔案,改為以下代碼:
HEADER_SEARCH_PATHS = $(inherited)'${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers'
    HEADER_SEARCH_PATHS = $(inherited)
'${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers'OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap'
    OTHER_CFLAGS = $(inherited) '-fmodule-map-
file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap'           

複制

OTHER_CFLAGS

:傳遞給用來編譯

C

或者

OC

的編譯器,目前就是

clang

加載

modulemap

檔案的路徑

對應

Build Setting

中的配置項

打開

ViewController.m

,寫入以下代碼:
#import "ViewController.h"
@import LGSwiftA;

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];

   LGSwiftTeacher *teacher = [LGSwiftTeacher new];
}

@end           

複制

編譯成功,

Swift

靜态庫中的

LGSwiftTeacher

類,可以在

OC

下正常使用

但此時還有另一個問題:

LGSwiftTest.swift

中,使用

import

導入

LGSwiftA

,還是編譯報錯

Swift

中,還需要加載

swiftmodule

檔案的路徑

打開

xcconfig

檔案,改為以下代碼:
HEADER_SEARCH_PATHS = $(inherited)'${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers'
    HEADER_SEARCH_PATHS = $(inherited)
'${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers'OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap'
    OTHER_CFLAGS = $(inherited) '-fmodule-map-
file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap'SWIFT_INCLUDE_PATHS = $(inherited)'${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework'
    SWIFT_INCLUDE_PATHS = $(inherited)
'${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework'           

複制

SWIFT_INCLUDE_PATHS

:傳遞給

SwiftC

編譯器

在指定路徑下查找

swiftmodule

檔案

對應

Build Setting

中的配置項

打開

LGSwiftTest.swift

檔案,寫入以下代碼:
import Foundation
import LGSwiftA

@objc open class LGSwiftTest: NSObject {

   public override init() {
       super.init()

       let t = LGSwiftTeacher()
       t.speek()
   }
}           

複制

編譯成功,

Swift

靜态庫中的

LGSwiftTeacher

類,可以在

Swift

下正常使用

LGSwiftA.framework

LGSwiftB.framework

兩個靜态庫中,都存在

LGSwiftTeacher

,有時甚至會存在頭檔案相同的情況。是以在案例中,手動建構的目錄結構,可以有效避免相同頭檔案的沖突。并且在使用的時候,導入的頭檔案是誰的,使用的

LGSwiftTeacher

對應就是誰的

連結靜态庫,隻要沒指定

-all_load

-ObjC

參數,預設會使用

-noall_load

參數。是以在同一個檔案内,即使導入兩個頭檔案,當連結一個檔案找到代碼後,就不會連結另一個,是以也不會沖突
OC映射到Swift方式
搭建

OCFramework

項目

OCFramework

是一個

OC

動态庫項目

打開

LGToSwift.h

檔案,寫入以下代碼:
#import <Foundation/Foundation.h>

typedef NS_ENUM(NSUInteger, LGTeacherName) {
   LGTeacherNameHank,
   LGTeacherNameCat,
};

typedef NSString * LGTeacherNameString;

extern NSString *getTeacherName(void);
extern NSString * const LGTeacherCat;
extern LGTeacherNameString const LGTeacherHank;

@interface LGToSwift : NSObject

- (nullable NSString *)teacherNameForIndex:(NSUInteger)index;

- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id>*)options;@end           

複制

打開

LGToSwift.m

檔案,寫入以下代碼:
#import "LGToSwift.h"

NSString *getTeacherName(void) {
   return nil;
}

NSString * const LGTeacherCat = @"Cat";
LGTeacherNameString const LGTeacherHank = @"Hank";

@implementation LGToSwift

- (nullable NSString *)teacherNameForIndex:(NSUInteger)pageIndex {
   return nil;
}

- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id>*)options {
       return NO;
    }@end           

複制

搭建

SwiftProject

項目

SwiftProject

是一個

App

項目

建立

MulitProject.xcworkspace

,加入

OCFramework

動态庫項目。

SwiftProject

連結

OCFramework

動态庫

ViewController.swift

中,使用

OCFramework

動态庫的方法,出現以下問題:

無法對

LGTeacherNameString

類型的屬性指派枚舉值

teacherName

方法的命名,被改為

teacherName(for:)

,但我們預期的是

teacherName(forIndex:)changeTeacherName

方法,我們希望它作為私有方法,并以雙下劃線字元

__

開頭

解決辦法:

可以使用特定宏,改變映射規則

OCFramework

中,打開

LGToSwift.h

檔案,改為以下代碼:
#import <Foundation/Foundation.h>

typedef NS_ENUM(NSUInteger, LGTeacherName) {
   LGTeacherNameHank,
   LGTeacherNameCat,
};

typedef NSString * LGTeacherNameString NS_TYPED_EXTENSIBLE_ENUM;

extern NSString *getTeacherName(void);
extern NSString * const LGTeacherCat;
extern LGTeacherNameString const LGTeacherHank;

@interface LGToSwift : NSObject

- (nullable NSString *)teacherNameForIndex:(NSUInteger)indexNS_SWIFT_NAME(teacherName(forIndex:));- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id>*)options NS_REFINED_FOR_SWIFT;@end
  *
NS_TYPED_EXTENSIBLE_ENUM:屬性訓示編譯器,使用struct(swift_wrapper(struct)屬性),通過指定NS_TYPED_ENUM宏,編譯器被訓示使用enum(swift_wrapper(enum)屬性)NS_SWIFT_NAME:通過指定NS_SWIFT_NAME宏,可以添加一些詳細資訊以使函數清晰可見
NS_REFINED_FOR_SWIFT:通過指定NS_REFINED_FOR_SWIFT宏,Swift的Clang
Importer将做一些額外的工作,将該方法導入為私有方法,并以雙下劃線字元__開頭           

複制

SwiftProject

中,打開

ViewController.swift

檔案,寫入以下代碼:
import UIKit
import OCFramework

class ViewController: UIViewController {

   override func viewDidLoad() {
       super.viewDidLoad()

       let Hank: LGTeacherNameString = .hank

       let teacher: LGToSwift = LGToSwift()
       teacher.teacherName(forIndex: 1)
   }
}

extension LGToSwift {
   func change() -> Bool {
       return __changeTeacherName(nil)
   }
}           

複制

問題解決,

OC

中的方法和屬性,在

Swift

中使用符合預期

但另一個問題又出現了:

通過指定宏的方式,需要修改原有代碼。如果一個使用

OC

開發的

SDK

需要适配

Swift

,需要為每一個方法或屬性指定宏,這将是工程浩大且費時費力的事情

解決辦法:

使用

.apinotes

檔案,代替宏的方式

OCFramework

目錄下,建立

OCFramework.apinotes

檔案

OCFramework

中,将

OCFramework.apinotes

檔案加入到項目

.apinotes

檔案必須要放在

SDK

的目錄中,采用

yaml

格式書寫,類似

JSON

格式
打開OCFramework.apinotes檔案,寫入以下代碼:---
Name: OCFramework
Classes:
- Name: LGToSwift
#  SwiftName: ToSwift
 Methods:
 - Selector: "changeTeacherName:"
   Parameters:
   - Position: 0
     Nullability: O
   MethodKind: Instance
   SwiftPrivate: true
   Availability: nonswift
   AvailabilityMsg: "prefer 'deinit'"           

複制

changeTeacherName:

方法,在

Swift

中設定為不可用

編譯項目,顯示自定義錯誤提示:

prefer 'deinit'

.apinotes

檔案最終會被放入編譯後的

.framework

官方文檔

更多

API

可檢視

官方文檔

總結

module

(子產品):最小的代碼單元,表示頭檔案與目标檔案的關系undefined

modulemap

:最小的代碼單元,表示頭檔案與目标檔案的映射

定義一個

module

export

:導出目前代表的頭檔案使用的頭檔案

export *

:比對目錄下所有的頭檔案

module *

:目錄下所有的頭檔案都當作一個子

module

explicit *

:顯式聲明一個

module

的名稱

Swift

庫使用

OC

代碼:

不能使用橋接檔案

OC

的頭檔案放到

modulemap

使用私有

modulemap

更好的表達

公共API

私有API

Swift

靜态庫合并

必須保留

.swiftmodule

檔案(

Swift

的頭檔案)

使用

libtool

指令,合并靜态庫本身

用到的頭檔案、

Swift

頭檔案以及

modulemap

檔案,通過目錄的形式放到一起

OC

要用合并的靜态庫:

clang: other c flags

-fmodule-map-file <modulemap path>

Swift

要用合并的靜态庫:

SwiftC :other swift flags

顯式告訴

SwiftC <modulemap dir>

OC

映射到

Swift

方式

使用

.apinotes

檔案:

<工程名稱>.apinotes