Swift和Objective-C相互调用

在Swift 中使用Objective-C

官方文档

桥接文件

桥接文件是一个在Swift中调用OC类或方法的通道。Swift在同模块内文件是可以相互调用的(不能模块之间调用是需要引入模块的),但是OC文件是需要引入头文件才能使用。

在Swift中首次创建OC文件的时候,xcode会弹出一个窗口,询问是否要创建桥接文件。桥接文件默认的命名为 “项目名-Bridging-Header.h”。只需要将OC 的头文件在桥接文件中#import一下就可以在Swift中使用。

如果在询问是否创建桥接文件的时候,没有选择创建,或者不小心将桥接文件删除了,我们也可以在 Targets-->Build Settings-->Swift Compiler - General-->Objective-C Bridging Header中 手动配置。创建一个头文件,文件的命名可以按照自己习惯命名,并不一定需要按照默认桥接文件的命名方式,创建完成后只需要在Targets-->Build Settings-->Swift Compiler - General-->Objective-C Bridging Header中 配置桥接头文件的路径就可以了。

NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END的作用

在Swift中,变量是有可选(optional)和非可选(non-optional)之分的,但是在OC中并没有对一个变量是否是nil进行区分,这就造成一个问题,在 Swift与Objective-C混编时,Swift编译器并不知道一个Objective-C对象到底是optional还是non-optional,这种情况下,编译器会默认的将OC对象当成是非可选。

为了解决这个问题,苹果在Xcode 6.3引入了一个Objective-C的新特性:nullability annotations。这一新特性的核心是两个新的类型注释: __nullable__nonnull 。从字面上我们可以猜到,__nullable表示对象可以是NULLnil,而__nonnull表示对象不应该为空。当我们不遵循这一规则时,编译器就会给出警告。

但是每个属性或者方法返回值或者参数都去指定是否nullable,实在是太繁琐了,NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END就是为了简化这种操作而诞生的,在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。

//Person.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property(nonatomic,copy,nullable)NSString *name;
@property(nonatomic,copy)NSString *sex;
-(instancetype)initWithName:(NSString *)name sex:(NSString *)sex;
@end

NS_ASSUME_NONNULL_END
  
//Person.m 
#import "Person.h"

@implementation Person
-(instancetype)initWithName:(NSString *)name sex:(NSString *)sex{
    if (self = [super init]) {
        self.name = name;
        self.sex = sex;
    }
    return self;
}
@end  
let per = Person.init(name: "小明", sex: "男")
print(per.name)
print(per.sex)

//输出:
//Optional("小明")
//男

Swift 中如何使用C++

在OC文件是直接可以使用C语言的,使用C++,只需要将.m文件后缀名改为.mm就可以使用C++的语法了。

在Swift中也是可以使用C语言的,具体使用Swift和C语言的混合使用

但是对与C++的使用相对就麻烦很多,一般采用OC对象包裹的形式去使用C++的对象。在Swift项目中OC头文件中是不能引入C++的,否则的会报file not found 的错误,我们需要在.mm文件中使用C++对象

在Objective-C中使用Swift

官方文档

相对与在Swift 中使用Objective-C来说,在Objective-C中使用Swift要复杂很多。

如何将Swift文件引入到OC项目中

第一次在OC项目中创建Swift文件时,xcode也是会提醒创建桥接文件的,这个文件的作用也是给Swift文件中调用OC使用的.

  1. 在OC文件中使用当前项目的Swift文件

    我们可以在 Targets-->Build Settings-->Swift Compiler - General-->Objective-C Generated Interface Header Name 看到我们需要在OC中将引入Swift内容的头文件。这个文件默认是 **"项目名-Swift.h" ** ,这个头文件也可以改成我们自定义的名称。只需要在OC中#import这个头文件就可以了。这种方式是

    #import "ProductModuleName-Swift.h"
    
  1. 在OC文件中使用当前其他Framework中的Swift文件

    ProductModuleName-Swift.h和上面的一样,只需要在前面加上个项目名就可以使用其他framework中的swift文件

    #import <ProductName/ProductModuleName-Swift.h>
    

在OC使用Swift需要注意什么

Objective-C 对象是基于运行时的,在运行调用时再决定实际调用的具体实现。而 Swift 为了追求性能,通常情况下是不会在运行时再来决定这些的。也就是说,Swift 类型的成员或者方法在编译时就已经决定,而运行时便不再需要经过一次查找。Objective-C 中所有类都继承自NSObject,为了能在OC中调用 Swift 中的类,Swift也必须继承NSObject。

首先我们需要先了解@objc、@objcMembers和dynamic这三个关键字的含义

@objc Swift @objc 关键字文档
  • @objc修饰符是用来暴露接口给 Objective-C 的运行时(类、协议、属性和方法等。@nonobjc修饰符不暴露接给 Objective-C 的运行时,通常可以是使用了@objcMembers修饰了类,但是某些方法不想在OC中使用

  • 添加@objc修饰符并不意味着这个方法或者属性会采用 Objective-C 的方式变成动态派发,Swift 依然可能会将其优化为静态调用

  • @objc 修饰符的隐式添加的情况

    1. 覆重写一个@objc声明的计算属性或者方法

      import Foundation
      
      class Person: NSObject {
          @objc var name :String{
              return "小明"
          }
          @objc var sex:String = "男"
          
          @objc func demo(){
              
          }
      }
      
      class Student: Person {
          override var name: String{
              return ""
          }
          //Cannot override with a stored property 'sex'
          //子类不能重写父类的存储属性
          //override var sex: String = "男"
          
          override func demo() {
              
          }
      }
      
    2. 实现一个使用@objc修饰协议

      @objc protocol MyProtocol {
          func myFun()
      }
      
      
      class Person: MyProtocol {
          func myFun() {
              
          }
      }
      
    3. 当属性声明为@IBAction或者@IBOutlet

    4. 当属性声明为@NSManaged

    5. @objcMembers 修饰的类,它和它子类的属性方法扩展都会隐式的添加上@objc

      @objcMembers
      class MyClass : NSObject {
        func foo() { }             // implicitly @objc
      
        func bar() -> (Int, Int)   // not @objc, because tuple returns
            // aren't representable in Objective-C
      }
      
      extension MyClass {
        func baz() { }   // implicitly @objc
      }
      
      class MySubClass : MyClass {
        func wibble() { }   // implicitly @objc
      }
      
      extension MySubClass {
        func wobble() { }   // implicitly @objc
      }
      

下面的这些情况将不在隐式添加@objc (Swift4以后)

  • 需要特别指明dynamic不再隐式添加@objc
class MyClass {
  dynamic func foo() { }       // error: 'dynamic' method must be '@objc'
  @objc dynamic func bar() { } // okay
}
  • NSObject的子类将不再隐式添加@objc
class MyClass : NSObject {
  func foo() { } //在OC不能使用
}

即遵循 Swift API Design Guidelines 的代码将使用的Swift名称,这些名称通常会转换为非常差的Objective-C名称,从而违反了针对Objective-C Coding Guidelines for Cocoa.

class MyNumber : NSObject {
  init(_ int: Int) { }
  init(_ double: Double) { } // error: initializer 'init' with Objective-C selector 'init:' 
      // conflicts with previous declaration with the same Objective-C selector
}

class MyNumber : NSObject {
  @objc(initWithInteger:) init(_ int: Int) { }
  @objc(initWithDouble:) init(_ double: Double) { }
}

@objc(Student)// Only classes that inherit from NSObject can be declared @objc
class Person: NSObject {
}

class Person: NSObject {
    @objc(age) var name = ""
}     
@objcMembers

@objcMembers只能用来修饰类。使用@objcMembers修饰的类,系统会在自动给这个类的所有方法添加@objc,暴露给OC。

dynamic (使用dynamic修饰的属性,在Swift4之后是不会自动添加上@objc的)

dynamic修饰的属性和方法是告诉编译器使用动态分发而不是静态分发,使用动态分发,您可以更好的与OC中runtime的一些特性(如CoreData,KVC/KVO)进行交互。

在Swift中使用KVO

class Person: NSObject {
    @objc dynamic var name = "" //@objc在Swift4之后必须要添加
    func demo() {
        addObserver(self, forKeyPath: #keyPath(Person.name), options: [.initial,.new], context: nil)
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349