優(yōu)雅的開發(fā)Swift和Objective-C混編的Framework
前言
為什么要寫這樣一篇文章,因?yàn)樽蛱旌鸵粋€朋友討論到Swift和Objective C如何混合開發(fā)Framework,中途發(fā)現(xiàn)了很多有意思的坑。
用Swift封裝OC的庫是一件比較常見的事情,畢竟對于大多數(shù)公司來說,老的代碼都是用OC寫的,而且經(jīng)過多次迭代,這些OC的代碼已經(jīng)被驗(yàn)證了是穩(wěn)定的,用Swift重寫代價太大。這就引入了一個需求:
- 用Swift和OC來混編一個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
按照文檔一步一步來
新建一個基于單頁面工程,然后新建一個一個Target,選中Cocoa Touch Framework。然后,分別新建一個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)編譯的時候是Project權(quán)限. 為了在umbrella header中使用,要把這個文件的權(quán)限改成Public
按照圖中的方式拖過去即可。
嗯,現(xiàn)在build,可以看到build成功了。
OC調(diào)用Swift
在SwiftSource.swift中,增加一個類,
- open class ClassForOC:NSObject{
 - public static let textForOC = "textForOC"
 - }
 
然后,為了在OC中調(diào)用Swift的方法,我們需要導(dǎo)入頭文件,這時候,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)控制臺打印出
- 2017-03-02 16:08:24.000 HostApplication[19524:167669] textForOC
 
嗯,framework打包成功了。
問題
通常,我們希望暴露給外部的接口是純Swift,而OC文件的具體接口應(yīng)該隱藏,這就是我標(biāo)題中的優(yōu)雅兩個字的含義。
如果你好奇,你會發(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對應(yīng)的實(shí)現(xiàn)暴露出來了
- import MixFramework.OCSource
 
優(yōu)雅的解決方案
不再通過umbrella header的方式讓framework中的Swift調(diào)用OC方法。而是通過modulemap。
新建一個module.modulemap文件,內(nèi)容如下
- module OCSource [system] {
 - //由于module.modulemap和OCSource.h是在同一個文件夾的,如果不是同一個,路徑要寫全
 - header "OCSource.h"
 - export *
 - }
 
這里的#(SRCROOT)是XCode的宏,會自動替換成項(xiàng)目所在的根目錄,這里輸入的路徑是module.modulemap文件所在的路徑。
然后,刪除MixFramework.h(umbrella header)中#import 的OC header。
把OCSource.h的權(quán)限改回默認(rèn)的project。
再編譯,發(fā)現(xiàn)OC的類被隱藏了。
總結(jié)
如果你要開發(fā)一個framework,一定要想清楚哪些接口暴露出去,哪些封裝起來,framework不是簡單把一包文件加個殼子。



















 
 
 



 
 
 
 