Swift項目兼容Objective-C問題彙總

http://www.cocoachina.com/swift/20150608/12025.htmlhtml

 

本文是投稿文章,做者:一葉(博客
歡迎將原創文章或者譯文投給咱們,投稿方式:support@cocoachina.com或者在首頁點擊「投稿爆料」

1、解決問題編程

Swift項目須要使用封裝好的Objective-c組件、第三方類庫,蘋果提供的解決方案可以處理平常大部分需求,但還不能稱之爲完美,混編過程當中會遇到不少問題。本文將Swift兼容Objective-c的問題彙總,以幫助你們更好的使用Swift,內容列表以下:swift

1. Swift調用Objective-c代碼框架

2. Objective-c調用Swift代碼async

3. Swift兼容Xib/Storyboardide

4. Objective-c巧妙調用不兼容的Swift方法ui

5. 多Target編譯錯誤解決atom

6. 第三方類庫支持spa

2、基礎混合編程3d

Swift與Objective-c的代碼相互調用,並不像Objective-c與C/C++那樣方便,須要作一些額外的配置工做。不管是Swift調用Objective-c仍是Objective-c調用Swift,Xcode在處理上都須要兩個步驟:

image_step.png

2.1 Swift調用Objective-c代碼

Xcode對於Swift調用Objective-c代碼,除宏定義外,其它支持相對完善。

2.1.1 使用Objetvie-c的第一步

告訴Xcode、哪些Objective-c類要使用,新建.h頭文件,文件名能夠任意取,建議採用「項目名-Bridging-Header.h」命令格式。

image_0.png

Tips

Swift之IOS項目,在Xcode6建立類文件,默認會自動選擇OS X標籤下的文件,這時必定要選擇iOS標籤下的文件,不然會出現語法智能提示不起做用,嚴重時會致使打包出錯。

2.1.2 第二步,Target配置,使建立的頭文件生效

QQ截圖20150605172845.png

設置Objective-C Bridging Header時,路徑要配置正確,例如:建立的名爲「ILSwift-Bridging-Header.h」文件,存於ILSwift項目文件夾的根目錄下,寫法以下:

1
ILSwift/ILSwift-Bridging-Header.h

固然,在新項目中,直接建立一個Objective-c類,Xcode會提示:

QQ截圖20150605172937.png

直接選擇Yes便可,若是不當心點了其它按鈕,能夠按照上面的步驟一步一步添加。

2.2 Objective-c調用Swift代碼

2.2.1 Objective-c調用Swift代碼兩個步驟

第一步告訴Xcode哪些類須要使用(繼承自NSObject的類自動處理,不須要此步驟),經過關鍵字@objc(className)來標記

1
2
3
4
5
6
7
8
import UIKit
@objc(ILWriteBySwift)
class ILWriteBySwift {
     var  name: String!
     class func newInstance() -> ILWriteBySwift {
         return  ILWriteBySwift()
     }
}

第二步引入頭文件,Xcode頭文件的命名規則爲

1
$(SWIFT_MODULE_NAME)-Swift.h

示例以下:

1
#import "ILSwift-Swift.h"

Tips

不清楚SWIFT_MODULE_NAME可經過如下步驟查看

54.png

2.2.2找不到$(SWIFT_MODULE_NAME)-Swift.h

 

image_4.png

1.遇到此問題可按如下步驟作常規性檢查

  • 肯定導入SWIFT_MODULE_NAME)-Swift.h頭文件的文件名正確

  • SWIFT_MODULE_NAME)-Swift.h在clean後沒有從新構建,執行Xcode->Product->Build

2.頭文件循環

在混合編程的項目中,因爲兩種語言的同時使用,常常會出現如下需求:在Swift項目中須要使用Objectvie-c寫的A類,而A類又會用到Swift的一些功能,頭文件的循環,致使編譯器不能正確構建$(SWIFT_MODULE_NAME)-Swift.h,遇到此問題時,在.h文件作以下處理

1
2
3
4
//刪除如下頭文件
//#import "ILSwift-Swift.h"
//經過代碼導入類
@class ILSwiftBean;

在Objevtive-c的.m文件最上面,添加

1
#import "ILSwift-Swift.h"

出現Use of undecalared identifier錯誤或者找不到方法,以下:

87.png

引發的緣由有如下幾種可能:

  • 使用的Swift類不是繼承自NSObject,加入關鍵字便可

  • SWIFT_MODULE_NAME)-Swift.h沒有實時更新,Xcode->Product->Build

  • 此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

3、Xib/StoryBoard支持

Swift項目在使用Xib/StoryBoard時,會遇到兩種不一樣的問題

  • Xib:不加載視圖內容

  • Storyboard:找不到類文件

3.1 Xib不加載視圖內容

在建立UIViewController時,默認選中Xib文件,在Xib與類文件名一致時,可經過如下代碼實例化:

1
let controller = ILViewController()

運行,界面上空無一物,Xib沒有被加載。解決辦法,在類的前面加上@objc(類名),例如:  

1
2
3
4
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中的空白,直接回車

 

22.png

3.2.2 無@objc(類名)標記的Swift類

1
2015-06-02 11:36:29.788 ILSwift[2719:417490] Unknown class ILNotFindSwiftController  in  Interface Builder file.

解決辦法,按下圖,選擇正確的Module

 

23.png

3.產生上面錯誤的緣由: 在設置好Storyboard後,直接在類文件中,添加或者刪除@objc(類名)關鍵字,致使Storyboard中 Module屬性沒有自動更新,因此一個更通用的解決辦法是,讓Storyboard自動更新Module,以下:

69.png

3.3 錯誤模擬Demo下載

爲了可以讓你們更清楚的瞭解解決流程,將上面的錯誤進行了模擬,想動手嘗試解決以上問題的同窗能夠直接下載demo

4、Objective-c巧妙調用不兼容的Swift方法

在Objective-c中調用Swift類中的方法時,因爲部分Swift語法不支持轉換,會遇到沒法找到對應方法的狀況,以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import UIKit
enum 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文件爲:

1
2
3
4
SWIFT_CLASS( "_TtC12ILSwiftTests13ILHTTPRequest" )
@interface ILHTTPRequest : NSObject
- (SWIFT_NULLABILITY(nonnull) instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

從上面的頭文件中能夠看出,方法requestLogin使用了不支持的Swift枚舉,轉換時方法被自動忽略掉,有如下兩種辦法,能夠巧妙解決相似問題:

4.1 用支持的Swift語法包裝

在Swift文件中,添加一個可兼容包裝方法wrapRequestLogin,注意此方法中不能使用不兼容的類型或者語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import UIKit
enum 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文件爲:

1
2
3
4
5
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作爲父類

1
2
3
4
5
6
7
8
9
10
11
@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文件中調用那些不支持的語法

1
2
3
4
5
6
7
8
import UIKit
class ILLoginController: ILLoginSuperController {
     override func loginButtonPressed(sender: AnyObject!) {
         ILHTTPRequest.requestLogin(self.userNameField.text, password: self.passwordField.text) { (state) -> (Void)  in
             //具體業務邏輯
         }
     }
}

5、多Target編譯錯誤解決

在使用多Target時,會出現一些編譯錯誤

5.1 Use of undeclared type

image_9.png

此類錯誤,是由於當前運行的Target找不到必須編譯文件。將文件添加到Target便可,以下支持ILSwiftTests Target,選中ILSwiftTests前的複選框便可

5.2.png

5.2 does not have a member named

此類錯誤可能因爲以下兩種緣由引發,解決辦法同上:

5.22.png

1.此方法來自父類,父類文件沒有加入到當前Target

2.此方法來自擴展,擴展沒有加入到當前Target

Tips

若是檢查發現,全部的類文件都已經準確添加到Target中,但編譯仍是不經過,此時着重檢查橋接文件是否正確設置,是否將相應的頭文件加入到了橋接文件中。如無特別要求,建議將全部Target的橋接文件全都指向同一文件。關於橋接文件的設置,請參考2.1

6、第三方類庫支持

Swift項目取消了預編譯文件,一些第三方Objective-c庫沒有導入必要框架(如UIKit)引發編譯錯誤

6.1 Cocoapods找不到.o文件

在使用了Cocoapods項目中,會出現部分類庫的.o文件找不到,致使此種錯誤主要是如下兩種問題:

  • 類庫自己存在編譯錯誤

  • Swift沒有預編譯,UIKit等沒有導入

將此庫文件中的代碼文件直接加到項目中,編譯,解決錯誤。

6.2 JSONModel支持

在Swift中可使用JSONModel部分簡單功能,一些複雜的數據模型建議使用Objevtive-c

1
2
3
4
5
6
7
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錯誤:

006.png

解決辦法,找到Other Linker Flags,添加-lz

74.png

7、綜述

如今大部分紅熟的第三方框架都是使用Objective-c寫的,開發時不可避免的涉及到兩種語言的混合編程,期間會遇到不少奇怪的問題。由於未知纔有探索的價值,Swift的簡潔快速,可以極大的推動開發進度。因此從今天開始,大膽的開始嘗試。

相關文章
相關標籤/搜索