• Swift项目兼容Objective-C问题汇总


    原文地址:http://www.cocoachina.com/swift/20150608/12025.html


    一、解决这个问题

    Swift项目须要使用封装好的Objective-c组件、第三方类库,苹果提供的解决方式可以处理日常大部分需求,但还不能称之为完美。混编过程中会遇到非常多问题。本文将Swift兼容Objective-c的问题汇总,以帮助大家更好的使用Swift,内容列表例如以下:

    1. Swift调用Objective-c代码

    2. Objective-c调用Swift代码

    3. Swift兼容Xib/Storyboard

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

    5. 多Target编译错误解决

    6. 第三方类库支持

    二、基础混合编程

    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就可以,假设不小心点了其他button。能够依照上面的步骤一步一步加入。

    2.2 Objective-c调用Swift代码

    2.2.1 Objective-c调用Swift代码两个步骤

    第一步告诉Xcode哪些类须要使用(继承自NSObject的类自己主动处理,不须要此步骤),通过keyword@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,增加keyword就可以

    • 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

    三、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概念,在通过keyword@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(类名)keyword,导致Storyboard中 Module属性没有自己主动更新,所以一个更通用的解决的方法是,让Storyboard自己主动更新Module,例如以下:

    69.png

    3.3 错误模拟Demo下载

    为了可以让大家更清楚的了解解决流程。将上面的错误进行了模拟,想动手尝试解决以上问题的同学可以直接下载demo

    四、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
                //详细业务逻辑
            }
        }
    }

    五、多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

    六、第三方类库支持

    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

    七、综述

    如今大部分成熟的第三方框架都是使用Objective-c写的。开发时不可避免的涉及到两种语言的混合编程。期间会遇到非常多奇怪的问题。由于未知才有探索的价值,Swift的简洁高速。可以极大的推进开发进度。所以从今天開始,大胆的開始尝试。


  • 相关阅读:
    Algorithm Gossip (37) 快速排序法 ( 一 )
    Algorithm Gossip (36) Heap排序法( 堆排序 )
    Algorithm Gossip (35) Shaker法
    Algorithm Gossip (34) 希尔排序
    AlgorithmGossip (33) 选择、插入、气泡排序
    Algorithm Gossip (32) 得分排行
    Algorithm Gossip (31) 数字拆解(dp问题)
    Algorithm Gossip (30) m元素集合的 n 个元素子集
    Algorithm Gossip (29) 产生可能的集合
    Algorithm Gossip (27) 排列组合
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5209785.html
Copyright © 2020-2023  润新知