優雅的開發Swift和Objective-C混編的Framework
前言
為什么要寫這樣一篇文章,因為昨天和一個朋友討論到Swift和Objective C如何混合開發Framework,中途發現了很多有意思的坑。
用Swift封裝OC的庫是一件比較常見的事情,畢竟對于大多數公司來說,老的代碼都是用OC寫的,而且經過多次迭代,這些OC的代碼已經被驗證了是穩定的,用Swift重寫代價太大。這就引入了一個需求:
- 用Swift和OC來混編一個Framework。
如果你之前沒有用Swift和Objective C混合開發,建議看看這篇文檔:
- 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。類的內容如下:
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調用OC
新建SwiftSource.swift
- open class SwiftIt{
- public init(){}
- let ocObject = OCSource()
- public func encapsulate(){
- ocObject.functionFromOC()
- }
- }
然后,按照文檔中,為了讓Swift文件訪問Objective C文件,我們應該在umbrella header,也就是MixFramework.h中,暴露所需要的header。
也就是,MixFramework.h,
- #import <MixFramework/OCSource.h>
然后,自信滿滿的點擊build。
Boom~~~,編譯不通過。
原因:OCSource.h默認編譯的時候是Project權限. 為了在umbrella header中使用,要把這個文件的權限改成Public
按照圖中的方式拖過去即可。
嗯,現在build,可以看到build成功了。
OC調用Swift
在SwiftSource.swift中,增加一個類,
- open class ClassForOC:NSObject{
- public static let textForOC = "textForOC"
- }
然后,為了在OC中調用Swift的方法,我們需要導入頭文件,這時候,OCSource.m文件內容如下
- #import "OCSource.h"
- #import <MixFramework/MixFramework-Swift.h>
- @implementation OCSource
- - (void)functionFromOC{
- NSLog(@"%@",[ClassForOC textForOC]);
- }
- @end
然后,build,發現成功了,很開心。
外部調用
在ViewController.swift中,我們調用Framework中的內容。
- 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.
- }
- }
然后運行,發現控制臺打印出
- 2017-03-02 16:08:24.000 HostApplication[19524:167669] textForOC
嗯,framework打包成功了。
問題
通常,我們希望暴露給外部的接口是純Swift,而OC文件的具體接口應該隱藏,這就是我標題中的優雅兩個字的含義。
如果你好奇,你會發現,在ViewController.swift中你可以這么調用
- var s = OCSource()
也就是說,OC的內容也暴露出來了,這破壞了Framework的封裝特性。
通過查看MixFramework的編譯結果,發現***暴露出的接口是這樣子的
- 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對應的實現暴露出來了
- import MixFramework.OCSource
優雅的解決方案
不再通過umbrella header的方式讓framework中的Swift調用OC方法。而是通過modulemap。
新建一個module.modulemap文件,內容如下
- 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不是簡單把一包文件加個殼子。