標籤:swift-oc-兼
一、解決問題
Swift項目需要使用封裝好的Objective-c組件、第三方類庫,蘋果提供的解決方案能夠處理日常大部分需求,但還不能稱之為完美,混編過程中會遇到很多問題。本文將Swift相容Objective-c的問題匯總,以協助大家更好的使用Swift,內容列表如下:
1. Swift調用Objective-c代碼2. Objective-c調用Swift代碼3. Swift相容Xib/Storyboard4. Objective-c巧妙調用不相容的Swift方法5. 多Target編譯錯誤解決6. 第三方類庫支援
二、基礎混合編程
Swift與Objective-c的代碼相互調用,並不像Objective-c與C/C++那樣方便,需要做一些額外的配置工作。無論是Swift調用Objective-c還是Objective-c調用Swift,Xcode在處理上都需要兩個步驟:
2.1 Swift調用Objective-c代碼
Xcode對於Swift調用Objective-c代碼,除宏定義外,其它支援相對完善。
2.1.1 使用Objetvie-c的第一步,
告訴Xcode、哪些Objective-c類要使用,建立.h標頭檔,檔案名稱可以任意取,建議採用“項目名-Bridging-Header.h”命令格式。
Tips
Swift之IOS項目,在Xcode6建立類檔案,預設會自動選擇OS X標籤下的檔案,這時一定要選擇iOS標籤下的檔案,否則會出現文法智能提示不起作用,嚴重時會導致打包出錯。
2.1.2 第二步,Target配置,使建立的標頭檔生效
設定Objective-C Bridging Header時,路徑要配置正確,例如:建立的名為“ILSwift-Bridging-Header.h”檔案,存於ILSwift專案檔夾的根目錄下,寫法如下:
ILSwift/ILSwift-Bridging-Header.h
當然,在新項目中,直接建立一個Objective-c類,Xcode會提示:
直接選擇Yes即可,如果不小心點了其它按鈕,可以按照上面的步驟一步一步添加。
2.2 Objective-c調用Swift代碼2.2.1 Objective-c調用Swift代碼兩個步驟
第一步告訴Xcode哪些類需要使用(繼承自NSObject的類自動處理,不需要此步驟),通過關鍵字@objc(className)來標記
import UIKit@objc(ILWriteBySwift)class ILWriteBySwift { var name: String! class func newInstance() -> ILWriteBySwift { return ILWriteBySwift() }}
第二步引入標頭檔,Xcode標頭檔的命名規則為
$(SWIFT_MODULE_NAME)-Swift.h
樣本如下:
#import "ILSwift-Swift.h"
Tips
不清楚SWIFT_MODULE_NAME可通過以下步驟查看
2.2.2找不到$(SWIFT_MODULE_NAME)-Swift.h
1.遇到此問題可按以下步驟做常規性檢查
1.確定匯入SWIFT_MODULE_NAME)-Swift.h標頭檔的檔案名稱正確2.SWIFT_MODULE_NAME)-Swift.h在clean後沒有重新構建,執行Xcode->Product->Build
2.標頭檔迴圈
在混合編程的項目中,由於兩種語言的同時使用,經常會出現以下需求:在Swift項目中需要使用Objectvie-c寫的A類,而A類又會用到Swift的一些功能,標頭檔的迴圈,導致編譯器不能正確構建$(SWIFT_MODULE_NAME)-Swift.h,遇到此問題時,在.h檔案做如下處理
//刪除以下標頭檔//#import "ILSwift-Swift.h"//通過代碼匯入類@class ILSwiftBean;
在Objevtive-c的.m檔案最上面,添加
#import "ILSwift-Swift.h"
出現Use of undecalared identifier錯誤或者找不到方法,如下:
引起的原因有以下幾種可能:
1.使用的Swift類不是繼承自NSObject,加入關鍵字即可2.SWIFT_MODULE_NAME)-Swift.h沒有即時更新,Xcode->Product->Build3.此Swift檔案中使用了Objective-c不支援的類型或者文法,如private
出現部分方法找不到的問題,Xcode無智能提示:
此方法使用了Objective-c不支援的類型或者文法
蘋果官方給出的不支援轉換的類型
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
三、Xib/StoryBoard支援
Swift項目在使用Xib/StoryBoard時,會遇到兩種不同的問題
1.Xib:不載入視圖內容2.Storyboard:找不到類檔案
3.1 Xib不載入視圖內容
在建立UIViewController時,預設選中Xib檔案,在Xib與類檔案名稱一致時,可通過以下代碼執行個體化:
let controller = ILViewController()
運行,介面上空無一物,Xib沒有被載入。解決辦法,在類的前面加上@objc(類名),例如:
import UIKit@objc(ILViewController)class ILViewController: UIViewController {}
Tips:
StoryBoard中建立的UIViewController,不需要@objc(類名)也能夠保持相容
3.2 Storyboard找不到類檔案
Swift語言引入了Module概念,在通過關鍵字@objc(類名)做轉換的時候,由於Storboard沒有及時更新Module屬性,會導致如下兩種類型錯誤:
3.2.1 用
@objc(類名)標記的Swift類或者Objective-c類可能出現錯誤:
2015-06-02 11:27:42.626 ILSwift[2431:379047] Unknown class _TtC7ILSwift33ILNotFindSwiftTagByObjcController in Interface Builder file.
解決辦法,按,選中Module中的空白,直接斷行符號
3.2.2 無
@objc(類名)標記的Swift類
2015-06-02 11:36:29.788 ILSwift[2719:417490] Unknown class ILNotFindSwiftController in Interface Builder file.
解決辦法,按,選擇正確的Module
3.產生上面錯誤的原因:
在設定好Storyboard後,直接在類檔案中,添加或者刪除@objc(類名)關鍵字,導致Storyboard中 Module屬性沒有自動更新,所以一個更通用的解決辦法是,讓Storyboard自動更新Module,如下:
3.3 錯誤類比Demo下載
為了能夠讓大家更清楚的瞭解解決流程,將上面的錯誤進行了類比,想動手嘗試解決以上問題的同學可以直接下載demo
四、Objective-c巧妙調用不相容的Swift方法
在Objective-c中調用Swift類中的方法時,由於部分Swift文法不支援轉換,會遇到無法找到對應方法的情況,如下:
import UIKitenum HTTPState { case Succed, Failed, NetworkError, ServerError, Others}class ILHTTPRequest: NSObject { class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) { dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void in NSThread.sleepForTimeInterval(3) dispatch_async(dispatch_get_main_queue(), { () -> Void in callback(state: HTTPState.Succed) }) }) }}
對應的$(SWIFT_MODULE_NAME)-Swift.h檔案為:
SWIFT_CLASS("_TtC12ILSwiftTests13ILHTTPRequest")@interface ILHTTPRequest : NSObject- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;@end
從上面的標頭檔中可以看出,方法requestLogin使用了不支援的Swift枚舉,轉換時方法被自動忽略掉,有以下兩種辦法,可以巧妙解決類似問題:
4.1 用支援的Swift文法封裝
在Swift檔案中,添加一個可相容封裝方法wrapRequestLogin,注意此方法中不能使用不相容的類型或者文法
import UIKitenum HTTPState: Int { case Succed = 0, Failed = 1, NetworkError = 2, ServerError = 3, Others = 4}class ILHTTPRequest: NSObject { class func requestLogin(userName: String, password: String, callback: (state: HTTPState) -> (Void)) { dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void in NSThread.sleepForTimeInterval(3) dispatch_async(dispatch_get_main_queue(), { () -> Void in callback(state: HTTPState.Succed) }) }) } class func wrapRequestLogin(userName: String, password: String, callback: (state: Int) -> (Void)) { self.requestLogin(userName, password: password) { (state) -> (Void) in callback(state: state.rawValue) } }}
對應的$(SWIFT_MODULE_NAME)-Swift.h檔案為:
SWIFT_CLASS("_TtC12ILSwiftTests13ILHTTPRequest")@interface ILHTTPRequest : NSObject+ (void)wrapRequestLogin:(NSString * __nonnull)userName password:(NSString * __nonnull)password callback:(void (^ __nonnull)(NSInteger))callback;- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;@end
此時,我們可以在Objective-c中直接使用封裝後的方法wrapRequestLogin
4.2 巧妙使用繼承
使用繼承可以支援所有的Swift類型,主要的功能在Objective-c中實現,不支援的文法在Swift檔案中調用,例如,ILLoginSuperController做為父類
@interface ILLoginSuperController : UIViewController@property (weak, nonatomic) IBOutlet UITextField *userNameField;@property (weak, nonatomic) IBOutlet UITextField *passwordField;- (IBAction)loginButtonPressed:(id)sender;@end////////////////////////////////////////////////////////////////@implementation ILLoginSuperController- (IBAction)loginButtonPressed:(id)sender{}@end
建立Swift檔案,繼承自ILLoginSuperController,在此Swift檔案中調用那些不支援的文法
import UIKitclass ILLoginController: ILLoginSuperController { override func loginButtonPressed(sender: AnyObject!) { ILHTTPRequest.requestLogin(self.userNameField.text, password: self.passwordField.text) { (state) -> (Void) in //具體商務邏輯 } }}
五、多Target編譯錯誤解決
在使用多Target時,會出現一些編譯錯誤
5.1 Use of undeclared type
此類錯誤,是因為當前啟動並執行Target找不到必須編譯檔案。將檔案添加到Target即可,如下支援ILSwiftTests Target,選中ILSwiftTests前的複選框即可
5.2 does not have a member named
此類錯誤可能由於如下兩種原因引起,解決辦法同上:
1.此方法來自父類,父類檔案沒有加入到當前Target2.此方法來自擴充,擴充沒有加入到當前Target
Tips
如果檢查發現,所有的類檔案都已經準確添加到Target中,但編譯還是不通過,此時著重檢查橋接檔案是否正確設定,是否將相應的標頭檔加入到了橋接檔案中。如無特別要求,建議將所有Target的橋接檔案全都指向同一檔案。關於橋接檔案的設定,請參考2.1
六、第三方類庫支援
Swift項目取消了先行編譯檔案,一些第三方Objective-c庫沒有匯入必要架構(如UIKit)引起編譯錯誤
6.1 Cocoapods找不到.o檔案
在使用了Cocoapods項目中,會出現部分類庫的.o檔案找不到,導致此種錯誤主要是以下兩種問題:
1.類庫本身存在編譯錯誤2.Swift沒有先行編譯,UIKit等沒有匯入
將此庫檔案中的代碼檔案直接加到項目中,編譯,解決錯誤
6.2 JSONModel支援
在Swift中可以使用JSONModel部分簡易功能,一些複雜的資料模型建議使用Objevtive-c
import UIKit@objc(ILLoginBean)public class ILLoginBean: JSONModel { var userAvatarURL: NSString? var userPhone: NSString! var uid: NSString!}
Tips
在Swift使用JSONModel架構時,欄位只能是NSFoundation中的支援類型,Swift下新添加的String、Int、Array等都不能使用
6.3 友盟統計
Swift項目中引入友盟統計SDK會出現referenced from錯誤:
解決辦法,找到Other Linker Flags,添加-lz
七、綜述
現在大部分成熟的第三方架構都是使用Objective-c寫的,開發時不可避免的涉及到兩種語言的混合編程,期間會遇到很多奇怪的問題。因為未知才有探索的價值,Swift的簡潔快速,能夠極大的推進開發進度。所以從今天開始,大膽的開始嘗試
我有一杯灑,可以慰風尖,哈哈~~哈~
Swift項目相容Objective-c問題匯總