一.Swift中的framework
我們知道,在Swift項(xiàng)目中要想使用OC代碼,必須要使用<projectName>-bridge-header.h。將OC橋接給Swift。
但是在framework中并沒有xxx-bridge-header.h。那么framework是怎么管理的?
創(chuàng)建一個(gè)framework(Swift),此時(shí)里面會(huì)有一個(gè)SwiftFramework.h文件。
SwiftFramework.h就相當(dāng)于之前我們將的umbrella傘頭文件。來管理我們的OC文件來暴露給Swift代碼使用。
這也就是為什么framework(Swift)中沒有xxx-bridge-header.h,那是因?yàn)槲覀冊(cè)趧?chuàng)建framework的時(shí)候默認(rèn)創(chuàng)建了以framework名稱的.h文件來暴露OC代碼
編譯framework查看modulemap
framework module SwiftFramework {
umbrella header "SwiftFramework.h"
export *
module * { export * }
}
module SwiftFramework.Swift {
header "SwiftFramework-Swift.h"
requires objc
}
- 可以看到
SwiftFramework.h就是一個(gè)傘頭文件
1.案例理解Headers
這里我們創(chuàng)建了一個(gè)Swift的framework,添加了LGCat(OC文件),創(chuàng)建了一個(gè)LGTeacher(swift)

LGCat
//.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGCat : NSObject
- (void)speak;
@end
NS_ASSUME_NONNULL_END
//.m
#import "LGCat.h"
@implementation LGCat
-(void)speak {
NSLog(@"LGCat speak");
}
@end
SwiftFramework.h
#import "LGCat.h"
那么此時(shí),我們?cè)?code>LGTeacher內(nèi)能否訪問LGCat?
編譯運(yùn)行后會(huì)報(bào)錯(cuò)Include of non-modular header inside framework module 'SwiftFramework': '/Users/zt/Desktop/Swift混編/SwiftFramework/SwiftFramework/OBJC/LGCat.h'
這就是一個(gè)frameork開發(fā)的經(jīng)典問題。此時(shí)的header權(quán)限有3種

-
Public暴露給外部,存在Headers目錄下 -
Private暴露給外部,存在PrivateHeaders目錄下。當(dāng)我們把LGCat.h移動(dòng)到這里后

-
Project不暴露給外部。代表在編譯的時(shí)候把頭文件編譯進(jìn)去了。達(dá)到整個(gè)頭文件隱藏的效果
那么我們繼續(xù)分析,SwiftFramework.h中的umbrella header "SwiftFramework.h"中的header代碼表的是什么?
代表的是framework中的Headers目錄,因此這里將LGCat.h移動(dòng)到Public,就能使用了。
此時(shí)又發(fā)現(xiàn)一個(gè)問題:為什么modulemap里設(shè)置的東西在Swift文件里能訪問到?
2.重寫modulemap
當(dāng)我們?cè)?code>framework中重寫modulemap時(shí),默認(rèn)生成的module文件就會(huì)被替換成重寫的。
新建module.modulemap文件,新建Config.xcconfig文件,并配置到工程中
Config.xcconfig
MODULEMAP_FILE = ${SRCROOT}/SwiftFramework/module.modulemap
module.modulemap
framework module SwiftFramework {
umbrella header "SwiftFramework.h"
export *
explicit module LGCat {
header "LGCat.h"
export *
}
module * {
export *
}
}
可以通過import SwiftFramework.LGCat來訪問到LGCat
3.設(shè)置PrivateModule
需求:不想把LGCat暴露出去
創(chuàng)建一個(gè)private.module.modulemap,并且使它生效。對(duì)于的是MODULEMAP_PRIVATE_FILE
MODULEMAP_PRIVATE_FILE = ${SRCROOT}/SwiftFramework/module.private.modulemap
// 標(biāo)準(zhǔn)寫法,xxx_Private
framework module SwiftFramework_Private {
header "LGCat.h"
export *
}
PrivateModule只針對(duì)外部工程,使用framework的工程。framework訪問不到PrivateModule的。
外界可使用@import SwiftFramework_Private;來訪問SwiftFramework_Private。
此時(shí)還是沒有滿足需求,接下來繼續(xù)分析
問題:OC使用Swift代碼,通過<projectName-Swift.h>將Swift代碼翻譯成OC代碼,但是為什么只有聲明,沒有實(shí)現(xiàn)?
SWIFT_CLASS("_TtC14SwiftFramework9LGTeacher")
@interface LGTeacher : NSObject
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
@end
通過@objc聲明并且繼承NSObject的類,在Swift編譯的時(shí)候就會(huì)把它看成OC代碼,編譯的時(shí)候就會(huì)按照OC的方式進(jìn)行編譯,放到Mach-O(專門的segement和section)中。以便在運(yùn)行的時(shí)候把class加載到內(nèi)存中。
編譯的時(shí)候就變成了OC符號(hào),編譯生成的.o的時(shí)候只需要頭文件,link的過程中才需要符號(hào)的具有信息。所以說,Swift在和OC混編的時(shí)候,OC只需要Swift暴露出來的OC頭文件就可以了。
此時(shí)我們是否可以通過這種思想來隱藏我們的LGCat
二.Swift&OC協(xié)議通訊
創(chuàng)建中間類
import Foundation
@objc(AnimalProtocol)
protocol Animal {
init()
func walk(withStep: Int)
}
//中間類
@objc(Zoo)
class Zoo : NSObject {
private static var animal: Animal.Type!
// OC把自己的類注冊(cè)進(jìn)來
@objc static func registerAnimalType(type: Animal.Type) {
animal = type
}
// Swift調(diào)用
func creatAnimal() -> Animal {
return Zoo.animal.init()
}
}
/*
SWIFT_PROTOCOL_NAMED("Animal")
@protocol AnimalProtocol
- (nonnull instancetype)init;
- (void)walkWithStep:(NSInteger)withStep;
@end
SWIFT_CLASS_NAMED("Zoo")
@interface Zoo : NSObject
+ (void)registerAnimalTypeWithType:(Class <AnimalProtocol> _Nonnull)type;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
*/
創(chuàng)建SwiftToObjectiveC.h頭文件來保存協(xié)議和Zoo對(duì)外暴露的接口(注意:查看暴露的內(nèi)容需要加上public)。并且權(quán)限聲明為project
在Swift類LGTeacher中,執(zhí)行Animal協(xié)議的creatAnimal()及walk()
import Foundation
@objc
public class LGTeacher: NSObject {
private let cat: Animal
public override init() {
cat = Zoo().creatAnimal()
}
@objc
public func walk() {
cat.walk(withStep: 10)
}
}
在LGCat.m文件中注冊(cè)類
//.h
#import <Foundation/Foundation.h>
#import "SwiftToObjectiveC.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGCat : NSObject<AnimalProtocol>
@end
NS_ASSUME_NONNULL_END
//.m
#import "LGCat.h"
@implementation LGCat
+ (void)load {
//OC的類注冊(cè)進(jìn)去
[Zoo registerAnimalTypeWithType:[LGCat class]];
}
- (void)walkWithStep:(NSInteger)withStep {
NSLog(@"Cat walk %ld", withStep);
}
@end
外界使用代碼
#import <SwiftFramework/SwiftFramework-Swift.h>
LGTeacher *t = [LGTeacher new];
[t walk];
至此就完成了對(duì)LGCat的隱藏。個(gè)人感覺有點(diǎn)像類型擦除的概念,使用中間類隱藏了具體類的信息。這里就是相當(dāng)于使用Zoo隱藏了Cat類型。
這樣的好處:
-
SwiftToObjectiveC.h和LGCat.h沒有被暴露出去 - 中間類
Zoo對(duì)OC代碼屏蔽了它的實(shí)現(xiàn)
總結(jié)3種方式:
- 如果不在乎頭文件暴露的話,可以放到
umbrella header里面或者modulemap里面。 - 如果想要提示一下用戶,這些頭文件不要使用,但是使用也沒有關(guān)系的話,可以放到
module.private.module里面。 - 如果一點(diǎn)也不想暴露的話,可以使用通過協(xié)議的方式把該屏蔽的屏蔽該暴露的暴露。
簡(jiǎn)單分析:OC頭文件編譯的過程?
.swift/.m代碼通過.h文件生成.o文件。頭文件都是一些聲明,生成.o的過程中,它不知道這些聲明代表的函數(shù)體到底是什么。所以在生成.o的時(shí)候有一個(gè)符號(hào)表保存著不知道函數(shù)體,只有符號(hào)名稱的符號(hào),叫做重定位符號(hào)表。等link的時(shí)候通過重定位符號(hào)表去找這個(gè)符號(hào)所代表的含義(函數(shù)體、具體內(nèi)容是什么)。
這里的LGCat.h是在LGCat.m編譯成.o的時(shí)候使用。ObjectiveCToSwift.swift也會(huì)通過SwiftToObjectiveC.h編譯成.o。在link的時(shí)候2個(gè)正好合在一起了,LGCat的.o保存著ObjectiveCToSwift的頭文件,ObjectiveCToSwift的.o保存著頭文件具體的實(shí)現(xiàn)。
三.多SwiftModule引入
1.多個(gè)靜態(tài)庫合并
創(chuàng)建2個(gè)framework(Static),保存至Muti.workspace。添加腳本
//將framework保存到Products目錄下
cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"
設(shè)置編譯A時(shí)同時(shí)編譯B,編譯后,得到2個(gè)framework

? cd /Users/zt/Desktop/Swift混編/多module合并/Products
? libtool -static -o libAB.a /Users/zt/Desktop/Swift混編/多module合并/Products/SwiftA.framework/SwiftA /Users/zt/Desktop/Swift混編/多module合并/Products/SwiftB.framework/SwiftB
此時(shí)得到了合并后的靜態(tài)庫libAB.a,此時(shí)的靜態(tài)沒有了modulemap、Headers,暫時(shí)也不能使用
2.配置合并后的靜態(tài)庫
創(chuàng)建一個(gè)xcconfig,并與項(xiàng)目產(chǎn)生關(guān)聯(lián)
//導(dǎo)入原有2個(gè)framework的modulemap文件
OTHER_CFLAGS = "-fmodule-map-file=${SRCROOT}/SwiftA.framework/Modules/module.modulemap" "-fmodule-map-file=${SRCROOT}/SwiftB.framework/Modules/module.modulemap"
//設(shè)置framework搜索路徑
FRAMEWORK_SEARCH_PATHS = ${SRCROOT}
使用原有的module.modulemap
3.ViewController中驗(yàn)證是否能夠使用合并后的libAB.a
@import SwiftA;
@import SwiftB;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
SwiftATeacher *a = [SwiftATeacher new];
[a speak];
SwiftBTeacher *b = [SwiftBTeacher new];
[b speak];
}
此時(shí)OC代碼是可以正常的使用Swift庫的
此時(shí)的Swift代碼也是可以通過import SwiftA來正常使用Swift庫
4.OC通過頭文件來調(diào)用Swift
創(chuàng)建一個(gè)public文件夾,來存放2個(gè)framework的頭文件

xcconfig中設(shè)置Header搜索路徑
HEADER_SEARCH_PATHS = ${SRCROOT}/Pods/Public
此時(shí)去驗(yàn)證的話,也是能夠使用的。
#import <SwiftA-Swift.h>
#import <SwiftB-Swift.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
SwiftATeacher *a = [SwiftATeacher new];
[a speak];
SwiftBTeacher *b = [SwiftBTeacher new];
[b speak];
// SwiftCTeacher *c = [SwiftCTeacher new];
// [c speaks];
}
因?yàn)榇藭r(shí)是通過頭文件去訪問的,并且設(shè)置了頭文件的搜索路徑。此時(shí)就能夠使用頭文件里暴露出的函數(shù)。OC使用Swift只需要頭文件就可以了。