Swift -- 13.Swift混編(下)

一.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)到這里后
PrivateHeaders
  • 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.hLGCat.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)沒有了modulemapHeaders,暫時(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只需要頭文件就可以了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容