優(yōu)雅的開發(fā)Swift和Objective-C混編的Framework
前言
為什么要寫這樣一篇文章,因?yàn)樽蛱旌鸵粋€(gè)朋友討論到Swift和Objective C如何混合開發(fā)Framework,中途發(fā)現(xiàn)了很多有意思的坑。
用Swift封裝OC的庫是一件比較常見的事情,畢竟對(duì)于大多數(shù)公司來說,老的代碼都是用OC寫的,而且經(jīng)過多次迭代,這些OC的代碼已經(jīng)被驗(yàn)證了是穩(wěn)定的,用Swift重寫代價(jià)太大。這就引入了一個(gè)需求:
- 用Swift和OC來混編一個(gè)Framework。
如果你之前沒有用Swift和Objective C混合開發(fā),建議看看這篇文檔:
- Swift and Objective-C in the Same Project
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
按照文檔一步一步來
新建一個(gè)基于單頁面工程,然后新建一個(gè)一個(gè)Target,選中Cocoa Touch Framework。然后,分別新建一個(gè)Swift文件和Objective C類,注意Target Member Ship選中Framework。類的內(nèi)容如下:
OCSource.h
- #import <span class="hljs-title"><Foundation/Foundation.h></span>
- @interface OCSource : NSObject
- - (void)functionFromOC;
- @end
OCSource.m
- #import "OCSource.h"
- @implementation OCSource
- - (void)functionFromOC{
- NSLog(@"%@",@"Log from objective c in framework");
- }
- @end
Swift調(diào)用OC
新建SwiftSource.swift
- open class SwiftIt{
- public init(){}
- let ocObject = OCSource()
- public func encapsulate(){
- ocObject.functionFromOC()
- }
- }
然后,按照文檔中,為了讓Swift文件訪問Objective C文件,我們應(yīng)該在umbrella header,也就是MixFramework.h中,暴露所需要的header。
也就是,MixFramework.h,
- #import <MixFramework/OCSource.h>
然后,自信滿滿的點(diǎn)擊build。
Boom~~~,編譯不通過。
原因:OCSource.h默認(rèn)編譯的時(shí)候是Project權(quán)限. 為了在umbrella header中使用,要把這個(gè)文件的權(quán)限改成Public
按照?qǐng)D中的方式拖過去即可。
嗯,現(xiàn)在build,可以看到build成功了。
OC調(diào)用Swift
在SwiftSource.swift中,增加一個(gè)類,
- open class ClassForOC:NSObject{
- public static let textForOC = "textForOC"
- }
然后,為了在OC中調(diào)用Swift的方法,我們需要導(dǎo)入頭文件,這時(shí)候,OCSource.m文件內(nèi)容如下
- #import "OCSource.h"
- #import <MixFramework/MixFramework-Swift.h>
- @implementation OCSource
- - (void)functionFromOC{
- NSLog(@"%@",[ClassForOC textForOC]);
- }
- @end
然后,build,發(fā)現(xiàn)成功了,很開心。
外部調(diào)用
在ViewController.swift中,我們調(diào)用Framework中的內(nèi)容。
- import MixFramework
- class ViewController: UIViewController {
- var t = SwiftIt()
- override func viewDidLoad() {
- super.viewDidLoad()
- t.encapsulate()
- // Do any additional setup after loading the view, typically from a nib.
- }
- override func didReceiveMemoryWarning() {
- super.didReceiveMemoryWarning()
- // Dispose of any resources that can be recreated.
- }
- }
然后運(yùn)行,發(fā)現(xiàn)控制臺(tái)打印出
- 2017-03-02 16:08:24.000 HostApplication[19524:167669] textForOC
嗯,framework打包成功了。
問題
通常,我們希望暴露給外部的接口是純Swift,而OC文件的具體接口應(yīng)該隱藏,這就是我標(biāo)題中的優(yōu)雅兩個(gè)字的含義。
如果你好奇,你會(huì)發(fā)現(xiàn),在ViewController.swift中你可以這么調(diào)用
- var s = OCSource()
也就是說,OC的內(nèi)容也暴露出來了,這破壞了Framework的封裝特性。
通過查看MixFramework的編譯結(jié)果,發(fā)現(xiàn)***暴露出的接口是這樣子的
- import Foundation
- import MixFramework.OCSource
- import MixFramework
- import MixFramework.Swift
- import SwiftOnoneSupport
- import UIKit
- //
- // MixFramework.h
- // MixFramework
- //
- // Created by Leo on 2017/3/2.
- // Copyright © 2017年 Leo Huang. All rights reserved.
- //
- //! Project version number for MixFramework.
- public var MixFrameworkVersionNumber: Double
- open class ClassForOC : NSObject {
- public static let textForOC: String
- }
- open class SwiftIt {
- public init()
- public func encapsulate()
- }
這一行,把OC對(duì)應(yīng)的實(shí)現(xiàn)暴露出來了
- import MixFramework.OCSource
優(yōu)雅的解決方案
不再通過umbrella header的方式讓framework中的Swift調(diào)用OC方法。而是通過modulemap。
新建一個(gè)module.modulemap文件,內(nèi)容如下
- module OCSource [system] {
- //由于module.modulemap和OCSource.h是在同一個(gè)文件夾的,如果不是同一個(gè),路徑要寫全
- header "OCSource.h"
- export *
- }
這里的#(SRCROOT)是XCode的宏,會(huì)自動(dòng)替換成項(xiàng)目所在的根目錄,這里輸入的路徑是module.modulemap文件所在的路徑。
然后,刪除MixFramework.h(umbrella header)中#import 的OC header。
把OCSource.h的權(quán)限改回默認(rèn)的project。
再編譯,發(fā)現(xiàn)OC的類被隱藏了。
總結(jié)
如果你要開發(fā)一個(gè)framework,一定要想清楚哪些接口暴露出去,哪些封裝起來,framework不是簡單把一包文件加個(gè)殼子。