前言
為什麼要寫這樣一篇文章,因為昨天和一個朋友討論到Swift和Objective C如何混合開發Framework,中途發現了很多有意思的坑。
用Swift封裝OC的庫是一件比較常見的事情,畢竟對於大多數公司來說,老的程式碼都是用OC寫的,而且經過多次迭代,這些OC的程式碼已經被驗證了是穩定的,用Swift重寫代價太大。這就引入了一個需求:
- 用Swift和OC來混編一個Framework。
如果你之前沒有用Swift和Objective C混合開發,建議看看這篇文件:
這篇文件很詳細的講解了如何運用Objective C和Swift進行混合開發App和Framework。於是,我們先按照文件來寫一個混編的Framework
按照文件一步一步來
新建一個基於單頁面工程,然後新建一個一個Target,選中Cocoa Touch Framework。然後,分別新建一個Swift檔案和Objective C類,注意Target Member Ship選中Framework。類的內容如下:
OCSource.h
1 2 3 4 5 6 |
#import <span class="hljs-title"><Foundation/Foundation.h></span> @interface OCSource : NSObject - (void)functionFromOC; @end |
OCSource.m
1 2 3 4 5 6 7 8 9 10 |
#import "OCSource.h" @implementation OCSource - (void)functionFromOC{ NSLog(@"%@",@"Log from objective c in framework"); } @end |
Swift呼叫OC
新建SwiftSource.swift
1 2 3 4 5 6 7 |
open class SwiftIt{ public init(){} let ocObject = OCSource() public func encapsulate(){ ocObject.functionFromOC() } } |
然後,按照文件中,為了讓Swift檔案訪問Objective C檔案,我們應該在umbrella header,也就是MixFramework.h
中,暴露所需要的header。
也就是,MixFramework.h
,
1 |
#import <MixFramework/OCSource.h> |
然後,自信滿滿的點選build。
Boom~~~,編譯不通過。
原因:OCSource.h預設編譯的時候是Project許可權. 為了在umbrella header中使用,要把這個檔案的許可權改成Public
按照圖中的方式拖過去即可。
嗯,現在build,可以看到build成功了。
OC呼叫Swift
在SwiftSource.swift中,增加一個類,
1 2 3 |
open class ClassForOC:NSObject{ public static let textForOC = "textForOC" } |
然後,為了在OC中呼叫Swift的方法,我們需要匯入標頭檔案,這時候,OCSource.m檔案內容如下
1 2 3 4 5 6 7 8 9 |
#import "OCSource.h" #import <MixFramework/MixFramework-Swift.h> @implementation OCSource - (void)functionFromOC{ NSLog(@"%@",[ClassForOC textForOC]); } @end |
然後,build,發現成功了,很開心。
外部呼叫
在ViewController.swift中,我們呼叫Framework中的內容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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. } } |
然後執行,發現控制檯列印出
1 |
2017-03-02 16:08:24.000 HostApplication[19524:167669] textForOC |
嗯,framework打包成功了。
問題
通常,我們希望暴露給外部的介面是純Swift,而OC檔案的具體介面應該隱藏,這就是我標題中的優雅兩個字的含義。
如果你好奇,你會發現,在ViewController.swift中你可以這麼呼叫
1 |
var s = OCSource() |
也就是說,OC的內容也暴露出來了,這破壞了Framework的封裝特性。
通過檢視MixFramework的編譯結果,發現最後暴露出的介面是這樣子的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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對應的實現暴露出來了
1 |
import MixFramework.OCSource |
優雅的解決方案
不再通過umbrella header的方式讓framework中的Swift呼叫OC方法。而是通過modulemap。
新建一個module.modulemap
檔案,內容如下
1 2 3 4 5 |
module OCSource [system] { //由於module.modulemap和OCSource.h是在同一個資料夾的,如果不是同一個,路徑要寫全 header "OCSource.h" export * } |
這裡的#(SRCROOT)是XCode的巨集,會自動替換成專案所在的根目錄,這裡輸入的路徑是module.modulemap檔案所在的路徑。
然後,刪除MixFramework.h
(umbrella header)中#import 的OC header。
把OCSource.h的許可權改回預設的project。
再編譯,發現OC的類被隱藏了。
總結
如果你要開發一個framework,一定要想清楚哪些介面暴露出去,哪些封裝起來,framework不是簡單把一包檔案加個殼子。