美文网首页Swift编程SwiftiOS
Swift和Objective-C混编

Swift和Objective-C混编

作者: 桔子听 | 来源:发表于2018-03-05 10:54 被阅读344次

    翻译自苹果官方文档

    和Objective-C交互

    互用性是指,在Swift和Objective-C之间可以建立一个互通接口,不管是Swift生成接口给Objective-C对接,还是Objective-C生成接口给Swift对接。既然你决定开始用Swift来开发,那么有必要理解一下怎么运用互用性来重定义、提高、改进你写Cocoa app的方式。

    互用性重要性之一是,在Swift中调用Objective-C的API。在你import一个Objective-C框架之后,你就可以用Swift的语法来实例化里面的类,继而使用它们。

    初始化

    要在Swift里初始化一个Objective-C类,需要用Swift的初始化语法来调Objective-C的初始化方法。

    Objective-C初始化方法都以init开头,或者,如果有一个或多个参数,会以initWith:开头。在Swift文件里如果要调用Objective-C初始化方法,那么init前缀会变成Swift初始化方法。如果此时初始化方法带有参数,会去掉with,而其他参数会根据情况划分到各个参数中。

    Objective-C初始化方法的声明:

    - (instancetype)init;
    - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
    

    转为Swift的初始化声明:

    init() { /* ... */ }
    init(frame: CGRect, style: UITableViewStyle) { /* ... */ }
    

    实例化对象的过程,更能看出Objective-C和Swift语法的不同:

    Objective-C:

    UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
    

    Swift:

    let myTableView: UITableView = UITableView(frame: .zero, style: .grouped)
    

    不用调用alloc,Swift替你处理了。还有,调用Swift风格的初始化函数,不会到处出现init

    当给变量或者常量赋值的时候,你可以指明一个类型,或者可以不指明这个类型,让Swift根据初始化方法自动推导出类型。

    let myTextField = UITextField(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 40.0))
    

    这里的UITableViewUITextField和你在Objective-C里实例化出来的对象是一样的,Objective-C里怎么用,这里就怎么用,根据各自的类型,获取属性、调用方法都一样。

    类工厂方法和方便初始化方法

    为了保持一致性和简单,Objective-C的类工厂方法引入Swift后,会改为方便初始化方法。这样,使用这些方法就像使用初始化方法一样。

    例如,下面这个就是Objective-C中的一个工厂方法:

    UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];
    

    在Swift中,要这样调用:

    let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)
    

    可失败的初始化

    Objective-C中初始化函数直接返回初始化的结果,如果初始化失败,就会直接返回nil。在Swift中,这个模式由语言的一个特性来实现,叫可失败的初始化(原文:failable initialization)。

    很多系统框架里的初始化方法都有标识初始化能不能失败。在你自己的Objective-C类里可以使用可空性(原文:nullability)来标识是不是能失败,Nullability and Optionals里有描述。在Objective-C里不可失败的话,在Swift里是init(...),可失败的话,用init?(...)。否则,都用init!(...)

    例如,UIImage(contentsOfFile:)初始化方法,如果提供的路径下面没有图片文件,就会初始化失败。如果可失败初始化函数初始化成功了,你可以用可选绑定去解包返回的结果。(译者注:public init?(contentsOfFile path: String))

    if let image = UIImage(contentsOfFile: "MyImage.png") {
        // loaded the image successfully
    } else {
        // could not load the image
    }
    

    获取属性

    在Objective-C中用@property声明的属性,Swift会做下面的处理:

    已经用nonnull、nullable和 null_resettable修饰的属性,按照Nullability and Optionals说的,转成可选和非可选属性。

    readonly属性会转化为Swift中的计算型属性,也即只有一个getter方法({ get })。

    weak属性转化为Swift中的weak属性(weak var)。

    对于不是weak的所有者属性(原文:ownership property)(即assigncopystrongunsafe_unretained)都会转成相对应的存储属性。

    class修饰的属性(译者注:Objective-C在Xcode9引入了一个新的属性class,应该就是为了对接Swift吧),转为在Swift中类型属性。

    Objective-C中的原子性修饰符(atomicnonatomic)在Swift中没有对应的修饰符,但是原子性所提供的功能在Swift里是依然存在的。(译者注:Atomicity property attributes (atomic and nonatomic) are not reflected in the corresponding Swift property declaration, but the atomicity guarantees of the Objective-C implementation still hold when the imported property is accessed from Swift.这句翻译的不是太好,请指教!)

    存取属性(getter=setter=)在Swift被忽略掉。(译者注:今天才知道getter=setter=也是属性)

    在Swift中,获取Objective-C 对象的属性值,用点语法直接带上属性名字,不需要括号。

    例如,设置UITextFieldtextColortext属性:

    myTextField.textColor = .darkGray
    myTextField.text = "Hello world"
    

    Objective-C中用点语法调用无参数有返回值的方法,形式和获取属性很像(译者注:方法除了点,还有括号,其实并不一样)。虽然调用形式一样,方法在Swift中还是会转换为方法,只有Objective-C中用@property声明的变量才能转为Swift中的属性。关于方法的引入和调用可以参考方法的使用

    方法的使用

    在Swift中,用点语法来调用Objective-C方法。

    Objective-C方法引入到Swift后,方法的第一部分,变成方法名,在括号前面。第一个参数在括号里,没有参数名。剩下的参数对应各自的参数名排在括号里。方法的所有组成部分都是需要的,少了任何部分,调用地址就是不对的。

    例如Objective-C中:

    [myTableView insertSubview:mySubview atIndex:2];
    

    在Swift中,会是这样:

    myTableView.insertSubview(mySubview, at: 2)
    

    调用一个没有参数的方法,依然要带上括号。

    myTableView.layoutIfNeeded()
    

    id兼容

    Objective-C中的id类型会转为Swift中的Any类型。在编译时和运行时,将Swift的值或者对象(译者注:Swift里引入了结构体,所以不光是类引用类型,也包括结构体值类型,所以,总是出现“值或者对象”)传给Objective-C的类型为id参数时,编译器会去执行一个全局桥接转换操作(原文:universal bridging conversion operation)。当将一个Objective-C中的id传给Swift的Any参数时,运行时会自动桥接回Swift的类引用或者值类型。(译者注:换句话说就是,idAny可以转换,并且转换由系统完成)

    var x: Any = "hello" as String
    x as? String   // String with value "hello"
    x as? NSString // NSString with value "hello"
     
    x = "goodbye" as NSString
    x as? String   // String with value "goodbye"
    x as? NSString // NSString with value "goodbye"
    

    (译者注:这个例子代码说明啥?不是特别清楚,求指教!)

    向下转换Any(译者注:从一个比较宽的类型,转化为一个比较具体的类型)

    如果知道Any里实际是什么类型,那么将其向下转化到一个更具体的类型比较有用。由于Any类型可以包含任意类型,所以,向下转型到一个更具体的类型不一定都能成功。

    可以试试可选类型转化操作符(as?),返回一个包裹着转化结果的可选值:

    let userDefaults = UserDefaults.standard
    let lastRefreshDate = userDefaults.object(forKey: "LastRefreshDate") // lastRefreshDate is of type Any?
    if let date = lastRefreshDate as? Date {
        print("\(date.timeIntervalSinceReferenceDate)")
    }
    

    如果你能确定对象的类型,那么可以用强制向下类型转换(as!)。

    let myDate = lastRefreshDate as! Date
    let timeInterval = myDate.timeIntervalSinceReferenceDate
    

    如果强制向下转换失败,会触发一个运行时错误:

    let myDate = lastRefreshDate as! String // Error
    

    动态方法查找

    Swift还有一个AnyObject类型,用来表示某些对象类型(译者注:和Any不同的是,Any还可以存储值类型),并且这个类型还有特别的能力,可以将@objc修饰的方法转为动态查找。通过这个特性,对Objective-C返回的id类型的值可以更好的操作与维护。(译者注:AnyObject能力比较强大,因为Swift是强类型,不指定具体类型是不能调用方法的,但是AnyObject不一样,可以调用任意方法。如果一个对象的方法或者属性标记为@objc,那么这些方法就变成可以动态查找的方法。因为Objective-C的方法是采用动态查找实现的,所以,只有这样,这些方法才能提供给Objective-C使用。@objc后面有详细叙述)

    例如,可以给AnyObject类型的常量或者变量赋值任意的对象,这个变量又可以再赋值一个不同类型的对象。你不需要转换为具体的类型,直接通过AnyObject类型就可以调用Objective-C具体类的方法和属性。

    var myObject: AnyObject = UITableViewCell()
    myObject = NSDate()
    let futureDate = myObject.addingTimeInterval(10)
    let timeSinceNow = myObject.timeIntervalSinceNow
    

    不能识别的方法和可选链

    因为直到运行时才能知道AnyObject的值到底是什么类型,所以,就有可能写出不安全的代码。无论是Swift还是Objective-C,试图去调用一个不存在的方法都会触发一个找不到方法(译者注:unrecognized selector)的错误。

    例如,下面的代码不会有编译时的警告,但是在运行时会报错:

    myObject.character(at: 5)
    // crash, myObject doesn't respond to that method
    

    Swift可以用可选的方式来避免不安全的行为。调用AnyObject类型的方法时,实际进行了一个隐式解包。可以用可选链语法来调用AnyObject类型的方法。

    例如下面的代码,第一行和第二行不会执行,因为NSDate对象没有count属性和character(at:)方法。常量myCount会被推导为可选Int类型,值为nil。可以用if let语句来解包方法返回的结果,如第三行所示。

    // myObject has AnyObject type and NSDate value
    let myCount = myObject.count
    // myCount has Int? type and nil value
    let myChar = myObject.character?(at: 5)
    // myChar has unichar? type and nil value
    if let fifthCharacter = myObject.character?(at: 5) {
        print("Found \(fifthCharacter) at index 5")
    }
    // conditional branch not executed
    

    注意

    虽然Swift没有强制要求,AnyObject类型的值调用方法时,一定要解包,但还是推荐解包,以此来避免不可预知的行为。(译者注:Although Swift does not require forced unwrapping when calling methods on values of type AnyObject, it is recommended as a way to safeguard against unexpected behavior.不是太理解,求指教!)

    可空和可选

    Objective-C中,通过原始指针(原文:raw pointer)来引用对象,指针可能是NULL(在Objective-C中是nil)。在Swift中,所有的结构体类型,或者对象类型都不会为空(译者注:不会直接是nil,而是一个可选值)。如果要表示这个值可以为空,那么要将这个值包装为可选类型。关于可选值的更多信息,请看Swift编程语言(Swift 4.0.3)可选值

    Objective-C中可以用可空性标识来表示参数、属性、返回值是不是可以为NULL或者nil。单个的类型声明,我们可以用_Nullable_Nonnull标识,单个的属性声明可以用nullable, nonnullnull_resettable标识,或者用宏NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END来标识整个区域的可空性。如果没有给一个类型设置可空性修饰,Swift无法区分是可选还是非可选,那么统一设置为隐式解包可选类型。(译者注:对于隐式解包可选类型加个说明,隐式解包的可选类型主要用在一个变量/常量在定义瞬间完成之后值一定会存在的情况,在使用时,不需要解包,直接使用,系统自动解包)

    • 用_Nonnull标识,或者在整个非空宏区域里的类型,声明为不可空,在Swift里都转为不可选。

    • 声明为_Nullable的可空类型,在Swift里转为可选类型。

    • 没有任何可空性标识的类型,在Swift里都转为隐式解包可选类型。

    例如,下面的Objective-C声明:

    @property (nullable) id nullableProperty;
    @property (nonnull) id nonNullProperty;
    @property id unannotatedProperty;
     
    NS_ASSUME_NONNULL_BEGIN
    - (id)returnsNonNullValue;
    - (void)takesNonNullParameter:(id)value;
    NS_ASSUME_NONNULL_END
     
    - (nullable id)returnsNullableValue;
    - (void)takesNullableParameter:(nullable id)value;
     
    - (id)returnsUnannotatedValue;
    - (void)takesUnannotatedParameter:(id)value;
    

    下面是在Swift中的样子:

    var nullableProperty: Any?
    var nonNullProperty: Any
    var unannotatedProperty: Any!
     
    func returnsNonNullValue() -> Any
    func takesNonNullParameter(value: Any)
     
    func returnsNullableValue() -> Any?
    func takesNullableParameter(value: Any?)
     
    func returnsUnannotatedValue() -> Any!
    func takesUnannotatedParameter(value: Any!)
    

    大部分的系统框架,包括Foundation,都有可空性标识,所以在和这些值打交道时,可以保持类型安全和语言的习惯

    桥接可选类型到非空对象

    Swift将可选值桥接为Objective-C的非空对象,如果可选值里有值,那么把这个值传递给Objective-C对象,如果可选值是nil,那么会传递一个NSNull实例给Objective-C对象。例如,可以将一个Swift可选类型直接传入Objective-C接受非空id参数的API,也可以将包含可选项的数组([T?])桥接为NSArray

    下面的代码可以看出,怎么根据实际所包含的值来桥接String?实例到Objective-C中。

    @implementation OptionalBridging
    + (void)logSomeValue:(nonnull id)valueFromSwift {
        if ([valueFromSwift isKindOfClass: [NSNull class]]) {
            os_log(OS_LOG_DEFAULT, "Received an NSNull value.");
        } else {
            os_log(OS_LOG_DEFAULT, "%s", [valueFromSwift UTF8String]);
        }
    }
    @end
    

    valueFromSwift参数类型是id,所以在Swift中要传入Any类型,如下所示。一般情况下,给参数为Any函数传入一个可选类型并不普遍,所以,需要将传入logSomeValue(_:)类方法的可选值进行显式的转换为Any类型,避免编译器警告。

    let someValue: String? = "Bridge me, please."
    let nilValue: String? = nil
     
    OptionalBridging.logSomeValue(someValue as Any)  // Bridge me, please.
    OptionalBridging.logSomeValue(nilValue as Any)   // Received an NSNull value.
    

    协议限定类(Protocol-Qualified Classes)

    实现了一个或者多个协议的Objective-C类,在Swift中会转换成协议组合类型。例如以下的Objective-C中定义的view controller属性

    @property UIViewController<UITableViewDataSource, UITableViewDelegate> * myController;
    

    Swift中会是这样:

    var myController: UIViewController & UITableViewDataSource & UITableViewDelegate
    

    Objective-C中协议限定元类(protocol-qualified metaclasses),在Swift中转换为协议元类型(protocol metatypes)。例如以下Objective-C方法对特定的类执行操作(原文:Objective-C protocol-qualified metaclasses are imported by Swift as protocol metatypes. For example, given the following Objective-C method that performs an operation on the specified class):

    - (void)doSomethingForClass:(Class<NSCoding>)codingClass;
    

    Swift中的样子:

    func doSomething(for codingClass: NSCoding.Type)
    

    轻量级泛型

    用轻量级泛型参数化(译者注:generic parameterization泛型参数化,意思是,给泛型指定了类型,也即尖括号里有具体的类型。所以后面说到的参数化,都是指已经给泛型指定了具体的类型)。声明的Objective-C类型,内容的类型信息在Swift会保留下来。例如下面的Objective-C属性声明:(译者注:Objective-C里面的泛型实际是Xcode7引入的一个语法糖,称为轻量级泛型。Stack Overflow上有这个讨论)

    @property NSArray<NSDate *> *dates;
    @property NSCache<NSObject *, id<NSDiscardableContent>> *cachedData;
    @property NSDictionary <NSString *, NSArray<NSLocale *>> *supportedLocales;
    

    Swift中会是这样:

    var dates: [Date]
    var cachedData: NSCache<NSObject, NSDiscardableContent>
    var supportedLocales: [String: [Locale]]
    

    一个Objective-C中参数化的类,引入Swift后,转换为泛型类,有同样数量的类型参数(译者注:type parameters类型参数,这里的意思是,泛型尖括号里的类型)。所有Objective-C中的泛型类型参数,在Swift中,会将类型参数转换为像(T: Any)类似的类的形式。如果Objective-C里泛型参数化指定了一个限制类(译者注:class qualification限制类,意思是,这个类起到了一个限制的作用,下面说道的protocol qualification协议限制,也是一样的理解,表示,这个协议对这个泛型起到一个限制作用),在Swift中也会转换这个限制:这个类必须是这个限制类的子类。如果Objective-C离泛型参数化指定了一个限制协议,在Swift中也会转换这个限制:这个类也必须遵守指定的协议。对于没有任何限制的Objective-C类型,Swift会推导给出限制,如下,Objective-C类和类别的声明:

    @interface List<T: id<NSCopying>> : NSObject
    - (List<T> *)listByAppendingItemsInList:(List<T> *)otherList;
    @end
     
    @interface ListContainer : NSObject
    - (List<NSValue *> *)listOfValues;
    @end
     
    @interface ListContainer (ObjectList)
    - (List *)listOfObjects;
    @end
    

    Swift中是这个样子的:

    class List<T: NSCopying> : NSObject {
        func listByAppendingItemsInList(otherList: List<T>) -> List<T>
    }
    
    class ListContainer : NSObject {
        func listOfValues() -> List<NSValue>
    }
    
    extension ListContainer {
        func listOfObjects() -> List<NSCopying>
    }
    

    扩展

    Swift扩展和Objective-C中的类别相似。扩展增加了已有类,结构体,枚举的功能,也可以扩展在Objective-C中定义的这些类型(译者注:应该是系统将Objective-C中的结构体和枚举类型转成了Swift中的结构体和枚举类型,这样才具备扩展功能)。可以给系统类型添加扩展,也可以给自定义的类添加扩展。Swift里引入Objective-C的模块,用Objective-C中的名字 ,来引用这些类,结构体,枚举类型。

    例如,给UIBezierPath类扩展功能,根据边长和起点生成一个等边三角形,再由这个等边三角形创建一个贝塞尔路径。

    extension UIBezierPath {
        convenience init(triangleSideLength: CGFloat, origin: CGPoint) {
            self.init()
            let squareRoot = CGFloat(sqrt(3.0))
            let altitude = (squareRoot * triangleSideLength) / 2
            move(to: origin)
            addLine(to: CGPoint(x: origin.x + triangleSideLength, y: origin.y))
            addLine(to: CGPoint(x: origin.x + triangleSideLength / 2, y: origin.y + altitude))
            close()
        }
    }
    

    可以用扩展来添加属性(包括类属性和静态属性)。但是,只能是计算属性;扩展不能给类,结构体,枚举类型添加存储属性。

    下面的例子是给CGRect结构体扩展一个计算属性area:

    extension CGRect {
        var area: CGFloat {
            return width * height
        }
    }
    let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
    let area = rect.area
    

    可以不通过继承,通过扩展来让一个类来实现协议(译者注:Objective-C中分类也是可以实现这个功能)。如果协议是在Swift中定义的,那么也可以给Objective-C和Swift中的结构体,枚举类型添加实现协议的扩展。

    扩展不能覆盖Objective-C中已经存在的方法和属性。

    闭包

    Objective-C中的block使用@convention(block)自动转换为Swift中的闭包。下面是Objective-C block变量:

    void (^completionBlock)(NSData *) = ^(NSData *data) {
       // ...
    }
    

    Swift中:

    let completionBlock: (Data) -> Void = { data in
        // ...
    }
    

    Swift闭包和Objective-C block是兼容的,可以将Swift闭包传递给接受block的Objective-C方法。Swift闭包和函数是相同的类型,所以也可以给Objective-C的block传递一个Swift的函数名字。

    闭包和block有相似的值捕获功能,但是闭包有一点不一样:变量默认是可以修改的。换句话说,Objective-C中给变量添加__block修饰,在Swift是默认行为。

    避免捕获self而造成循环引用

    Objective-C中,在block中捕获self,需要考虑内存管理。

    block会对任何捕获的对象保持一个强引用,包括self。如果self又对block保持一个强引用,例如block是self的一个copy属性,那么会造成一个循环引用。为了避免循环引用,我们让block捕获一个弱引用的self。

    __weak typeof(self) weakSelf = self;
    self.block = ^{
       __strong typeof(self) strongSelf = weakSelf;
       [strongSelf doSomething];
    };
    

    和Objective-C里的block一样,Swift中的闭包也会对捕获的对象保持一个强引用,当然也包括self。为了防止造成循环引用,可以在闭包的捕获列表里,将self设置为unowned

    self.closure = { [unowned self] in
        self.doSomething()
    }
    

    更多信息参照Swift编程语言(Swift 4.0.3)中的解决闭包循环引用

    对象比较

    在Swift中有两个不同的比较类型来比较两个对象。一个是等号(==),来比较对象的内容。还有一个是恒等(===),来判断比较的变量或常量是不是指向同一个对象。

    Swift对=====操作符提供了默认的实现,对于继承自NSObject的对象,遵守Equatable协议。==的默认实现是调用isEqual:方法,===的默认实现是检查指针是否相等。从Objective-C引入的类型,不能重写等号和恒等操作符。

    NSObject类提供的isEqual:实现和恒等是一样的,也是比较指针是否相等。在Swift和Objective-C中,可以通过重写isEqual:,来比较对象的内容是不是相等,而不是比较指针。关于比较逻辑实现的更多信息,请看Cocoa Core Competencies里的对象比较

    注意

    Swift也提供了等和恒等对应的相反的实现(!=!==)。这两个操作符不能重写。

    哈希

    Objective-C中NSDictionary没有对key指定类型限制,Swift会转化为Dictionarykey的类型是AnyHashableNSSet也一样,如果在Objective-C中没有对其中的元素指定类型限制,Swift会将Set中的元素类型设置为AnyHashable。如果NSDictionaryNSSet已经参数化key或者元素的,那么Swift里就会用相应的类型。如下Objective-C的声明:

    @property NSDictionary *unqualifiedDictionary;
    @property NSDictionary<NSString *, NSDate *> *qualifiedDictionary;
     
    @property NSSet *unqualifiedSet;
    @property NSSet<NSString *> *qualifiedSet;
    

    转为Swift:

    var unqualifiedDictionary: [AnyHashable: Any]
    var qualifiedDictionary: [String: Date]
     
    var unqualifiedSet: Set<AnyHashable>
    var qualifiedSet: Set<String>
    

    Objective-C中未指明类型或者类型为id的类型,转到Swift中都是AnyHashable,为什么不是Any类型,因为在这里,这些类型都需要遵守Hashable协议。AnyHashable类型表示任何可哈希的类型,可以用as?as!操作符转化为更具体的类型。

    更多信息,见AnyHashable

    Swift类型兼容

    Swift可以继承Objective-C类,生成一个Swift类,其中成员属性、方法、索引(译者注:subscript索引是Swift里的新功能)、初始化方法,这些能在Objective-C中找到对应的转化,都会自动转化为Objective-C相应的功能。但是有些特性是Swift特有的,无法转到Objective-C,如下所示:

    • 泛型(译者注:Objective-C没有泛型,后来引入的称作轻量级泛型,只是一个编译器的语法糖,和Swift中真正的泛型不是一回事)

    • 元祖

    • 在Swift中定义的枚举类型,raw值不为Int类型(译者注:Objective-C中也有枚举类型,但是,原始值一定是int类型。在Swift中,枚举类型可以是其他类型,不光是int类型,所以要和Objective-C兼容的话,必须限定为int类型)。

    • Swift中定义的结构体

    • Swift中定义的顶级函数(原文:Top-level functions意思是,不属于某个类,某个结构体,枚举等的函数,直接写在文件里的函数)

    • Swift中定义的全局变量

    • Swift中的Typealiases关键字

    • Swift风格的可变参数(译者注:Objective-C中也有可变参数,但是和Swift相比,功能弱很多,所以,Swift特有的功能转不过去)

    • 嵌套类型(译者注:Swift里类型可以嵌套,例如类里面还可以定义类)

    • 柯里化函数(译者注:王巍的Swift 100 tips第一节就是介绍柯里化)

    Swift API转为Objective-C API,和上面的Objective-C API转为Swift API类似,下面是Swift转Objective-C:

    • Swift可选类型转为__nullable

    • Swift非可选类型转为__nonnull

    • Swift常量的存储属性和计算属性都转为Objective-C的read-only。

    • Swift变量存储属性转为Objective-C的read-write。

    • Swift的类型属性(type properties)转为class属性(译者注:参考Xcode8添加一个属性class)。

    • Swif类型方法转为Objective-C的类方法(静态方法)。

    • Swift初始化方法(译者注:Swift的初始化函数,在Swift里是按特殊函数对待,但是Objective-C初始化函数也是实例函数)和实例方法转为Objective-C的实例方法。

    • Swift中(throw)抛出错误的方法,会转化为Objective-C中的带NSError **参数的方法。如果这个Swift方法没有参数,会追加AndReturnError: 到Objective-C方法名的后面,否则就追加error:参数。如果Swift方法没有指定返回类型,相应的Objective-C方法会返回一个BOOL类型。如果Swift方法返回一个非可选类型,相应的Objective-C方法会返回一个可选类型(译者注:原文是“ If the Swift method returns a nonoptional type, the corresponding Objective-C method has an optional return type. ”,Objective-C中没有所谓的可选类型,这句不甚了解,求指导!)。

    以下Swift声明:

    class Jukebox: NSObject {
        var library: Set<String>
        
        var nowPlaying: String?
        
        var isCurrentlyPlaying: Bool {
            return nowPlaying != nil
        }
        
        class var favoritesPlaylist: [String] {
            // return an array of song names
        }
        
        init(songs: String...) {
            self.library = Set<String>(songs)
        }
        
        func playSong(named name: String) throws {
            // play song or throw an error if unavailable
        }
    }
    

    转为Objective-C:

    @interface Jukebox : NSObject
    @property (nonatomic, strong, nonnull) NSSet<NSString *> *library;
    @property (nonatomic, copy, nullable) NSString *nowPlaying;
    @property (nonatomic, readonly, getter=isCurrentlyPlaying) BOOL currentlyPlaying;
    @property (nonatomic, class, readonly, nonnull) NSArray<NSString *> *favoritesPlaylist;
    - (nonnull instancetype)initWithSongs:(NSArray<NSString *> * __nonnull)songs OBJC_DESIGNATED_INITIALIZER;
    - (BOOL)playSong:(NSString * __nonnull)name error:(NSError * __nullable * __null_unspecified)error;
    @end
    

    注意

    Objective-C不能继承Swift类。

    调整Swift,适配Objective-C

    某些情况下,对于暴露给Objective-C的Swift API需要有一个更细粒度的控制(译者注:原文finer grained control)。用@objc(name)可以给类,属性,方法,枚举,及枚举里的case重新改一个名字,暴露给Objective-C。

    例如,Swift类包含的字符是Objective-C不支持的,那么我们可以改个名字暴露给Objective-C。如果要给Swift函数改名,需要用Objective-C的selector语法。如果有参数,不要漏了冒号(:)

    @objc(Color)
    enum Цвет: Int {
        @objc(Red)
        case Красный
        
        @objc(Black)
        case Черный
    }
     
    @objc(Squirrel)
    class Белка: NSObject {
        @objc(color)
        var цвет: Цвет = .Красный
        
        @objc(initWithName:)
        init (имя: String) {
            // ...
        }
        @objc(hideNuts:inTree:)
        func прячьОрехи(количество: Int, вДереве дерево: Дерево) {
            // ...
        }
    }
    

    当给一个Swift类使用@objc(name)属性的时候,Objective-C就可以访问这个类,并且不会带有任何命名空间信息(译者注:因为Swift是有命名空间的,如果不去掉命名空间,直接搬到Objective-C中使用,类名就会是xxx.类名的形式,这个在Objective-C里当然不能使用,所以这里的意思是,转到Objective-C,会去掉命名空间,也就是去掉类名前的xxx)。在将可归档的Objective-C类迁移到Swift中时(译者注:意思是将一个Objective-C类改写为Swift类),这个属性也非常有用。因为归档的对象会储存类名,用@objc(name)指定一个和Objective-C中一样的名字,这样才能用新的Swift类来解档之前Objective-C归档的类。(译者注:用我的话说就是,Objective-C归档的时候,没有所谓的命名空间,直接按照类名来归档,但是改成Swift之后,想把之前用Objective-C归档的东西解档,就必须去掉命名空间,去掉命名空间的做法,就是用@objc(name)来指定类名)

    注意
    相反,Swift也有@nonobjc属性,这个属性表示Objective-C中不能访问。You can use it to resolve circularity for bridging methods and to allow overloading of methods for classes imported by Objective-C.(译者注:译者对于混编经验不足,这句意思不甚了解,求指教!原文在上面)有些功能,例如可变参数,在Objective-C中不能表示,所以,对于这类方法,需要标记为@nonobjc

    要求动态派发(译者注:Objective-C里面的消息调用机制称作动态派发)

    暴露给Objective-C调用的Swift API,可必须是通过动态派发的方式调用。但是,这些通过动态派发的Swift API,当Swift调用这些API的时候,Swift编译器会选择一个更加高效的方法来调用,而不会直接用动态派发的方式调用(译者注:其实Objective-C中的动态派发是一种低效的机制,Swift已经摒弃)。

    @objc后面加上dynamic,表示通过Objective-C运行时动态派发来访问成员。很少情况下需要这种动态派发。但是在使用KVO和 method_exchangeImplementations等那些需要在运行时动态替换方法的时候,就需要指明动态派发。

    添加dynamic的声明也必须添加@objc,除非@objc被系统隐式添加了。@objc隐式添加的相关信息请看Swift编程语言属性声明

    选择子(译者注:原文selector)

    Objective-C中,selector表示一个方法的类型。Swift中,用Selector结构体表示Objective-C的selector类型,可以用#selector表达式来创建这个结构体。要给一个方法创建一个可供Objective-C调用的selector,需要传入方法名,例如#selector(MyViewController.tappedButton(_:))。如果要给一个属性的getter或者setter方法创建一个selector,需要传递一个以getter或者setter标签为前缀的属性名,例如#selector(getter: MyViewController.myButton)。

    import UIKit
    class MyViewController: UIViewController {
        let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
        
        override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
            super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
            let action = #selector(MyViewController.tappedButton)
            myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
        }
        
        @objc func tappedButton(_ sender: UIButton?) {
            print("tapped button")
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
        }
    }
    

    注意
    Objective-C方法指针可以加括号,可以使用as操作符来区别不同的重载函数,例如#selector(((UIView.insert(subview:at:)) as (UIView) -> (UIView, Int) -> Void))(译者注:可以参考Stack Overflow

    Objective-C方法的不安全调用

    可以使用perform(_:)或者它的变体去调用一个Objective-C方法。用selector调用方法是不安全的,因为编译器对结果不能做任何保证,甚至不能保证对象是否能响应这个selector。除非非要依赖Objective-C运行时的动态解决方案,否则我们不建议使用这些API。例如,你需要实现一个类,使用target-action设计模式,像NSResponder那样,这种情况使用这些API是合适的。大部分情况下,将对象转为AnyObject,再使用可选链来调用,会更安全,更方便。这个在id兼容性里有说到(译者注:这里苹果推荐先将对象转为AnyObject,再直接来调用方法,而不是使用selector,出于安全考虑)。

    通过selector同步执行方法,例如perform(_:),返回一个隐式解包的Unmanaged指针,指向AnyObject实例(Unmanaged<AnyObject>!),因为返回值的类型和所有者在编译期不能确定。按照约定,在一个指定的线程,或者做一个延时再执行一个selector,例如perform(_:on:with:waitUntilDone:modes:)perform(_:with:afterDelay:)不会返回一个值。更多信息见非托管对象

    let string: NSString = "Hello, Cocoa!"
    let selector = #selector(NSString.lowercased(with:))
    let locale = Locale.current
    if let result = string.perform(selector, with: locale) {
        print(result.takeUnretainedValue())
    }
    // Prints "hello, cocoa!"
    

    对象调用不能识别的selector,会触发doesNotRecognizeSelector(_:),抛出NSInvalidArgumentException异常。

    let array: NSArray = ["delta", "alpha", "zulu"]
     
    // Not a compile-time error because NSDictionary has this selector.
    let selector = #selector(NSDictionary.allKeysForObject)
     
    // Raises an exception because NSArray does not respond to this selector.
    array.perform(selector)
    

    Key和Key Path

    Objective-C中,key是一个字符串,表示一个对象的属性。key path是一个用点分割的字符串,表示一个对象属性的属性。key和key path经常用于键值编码(KVC),一种通过字符串间接获取对象属性的机制。key和key path也用于健值观察(KVO),一个对象的属性改变,会通知另外一个对象的机制。

    Swift中,可以用key-path表达式创建一个key path来获取属性,例如用\Animal.name来获取Animalname属性,例如下面代码。用key-path表达式创建的key path指向的属性已经包含了类型信息。对一个实例用key path取得的属性值,和直接用属性取得的值一样。key-path表达式可以是一个属性,也可以是链式的属性,如\Animal.name.count

    class Animal: NSObject {
        @objc var name: String
        
        init(name: String) {
            self.name = name
        }
    }
     
    let llama = Animal(name: "Llama")
    let nameAccessor = \Animal.name
    let nameCountAccessor = \Animal.name.count
     
    llama[keyPath: nameAccessor]
    // "Llama"
    llama[keyPath: nameCountAccessor]
    // "5"
    

    Swift中,还可以用#keyPath字符串表达式创建一个可以在编译期检查的key和key path,用于KVC方法,如value(forKey:)value(forKeyPath:),用于KVO方法,如addObserver(_:forKeyPath:options:context:)。#keyPath也支持链式的方法或属性。链中可以有可选值,如#keyPath(Person.bestFriend.name)(译者注:bestFriend就是一个可选值)。和用key-path表达式创建的key path不同,用#keyPath字符串创建的key path所指定的属性或者方法没有类型信息。

    注意
    #keyPath字符串语法和#selector表达式类似,见Selectors

    class Person: NSObject {
        @objc var name: String
        @objc var friends: [Person] = []
        @objc var bestFriend: Person? = nil
        
        init(name: String) {
            self.name = name
        }
    }
     
    let gabrielle = Person(name: "Gabrielle")
    let jim = Person(name: "Jim")
    let yuanyuan = Person(name: "Yuanyuan")
    gabrielle.friends = [jim, yuanyuan]
    gabrielle.bestFriend = yuanyuan
     
    #keyPath(Person.name)
    // "name"
    gabrielle.value(forKey: #keyPath(Person.name))
    // "Gabrielle"
    #keyPath(Person.bestFriend.name)
    // "bestFriend.name"
    gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
    // "Yuanyuan"
    #keyPath(Person.friends.name)
    // "friends.name"
    gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
    // ["Yuanyuan", "Jim"]
    

    相关文章

      网友评论

        本文标题:Swift和Objective-C混编

        本文链接:https://www.haomeiwen.com/subject/oxncfftx.html