Swift互用性:与 C的API交互(Swift 2.0版)

Using Swift with Cocoa and Objective-C(Swift 2.0):互通性--与 C的API交互
节包含内容:

  • 基本数据类型(Primitive Types)
  • 枚举(Enumerations)
  • 指针(Pointer)
  • 全局常量(Global Constants)
  • 预处理指令(Preprocessor Directives)

作为与Objective-C语言的互用性的一部分,Swift也对一些C语言的类型和特性保持了兼容性。如果你的代码有需要,Swift也提供了一些方式来使用常见的C结构和模式。

基本数据类型

Swift提供了一些与C语言基本类型如char,int,float和double等的对应类型。然而,这些类型和Swift核心基本类型之间不能进行隐式转换,如Int。因此,只有你的代码明确要求时才使用这些类型,其它任何可能的情况下都应该使用Int。


枚举

任何用宏NS_ENUM来声明的C风格的枚举,都会被Swfit导入为一个Swfit枚举类型。无论枚举值是在系统框架还是在自己的代码中定义的,当它们导入到Swift时,它们的前缀名将被截掉。

例如,看这个Objective-C枚举的声明:

    //Objective-C
    typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
        UITableViewCellStyleDefault,
        UITableViewCellStyleValue1,
        UITableViewCellStyleValue2,
        UITableViewCellStyleSubtitle
    };

在Swift中,会被导入为这样:

    //Swift
    enum UITableViewCellStyle: Int {
        case Default
        case Value1
        case Value2
        case Subtitle
    }

当你需要使用一个枚举值时,使用以点(.)开头的枚举名称:

    //Swift
    let cellStyle: UITableViewCellStyle = .Default

选项集

对使用宏NS_OPTIONS声明的C风格的枚举,Swift会把它导入为一个Swfit选项集类型。选项集像枚举一样,会把前缀截掉,只剩下选项值名称。

例如,看这个Objective-C选项的声明:

    //Objective-C
    typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
        NSJSONReadingMutableContainers = (1UL << 0),
        NSJSONReadingMutableLeaves = (1UL << 1),
        NSJSONReadingAllowFragments = (1UL << 2)
    };

在Swift中,它被导入为这样:

    //Swift
    struct NSJSONReadingOptions : OptionSetType {
        init(rawValue: UInt)
        static var MutableContainers: NSJSONReadingOptions { get }
        static var MutableLeaves: NSJSONReadingOptions { get }
        static var AllowFragments: NSJSONReadingOptions { get }
    }

在Objective-C中,一个选项集是整数值的一个位掩码。你可以使用按位或操作符(|)来组合选项值,使用按位与操作符(&)以检测选项值。创建一个选项集,可以使用常量值或者表达式。一个空的选项集使用常数0来表示。

在Swift中,选项集使用一个遵循OptionSetType协议的结构体来表示,其中每个选项值都是一个静态变量。选项集类似于Swift的集合类型Set,你可以用insert(:)或者unionInPlace(:)方法来添加选项值,用remove(:)或者subtractInPlace(:)方法来删除选项值,用contains(_:)方法来检测选项值。创建一个选项集的值可以使用一个数组字面量,里面的选项值像枚举一样都用点(.)开头。创建一个空的选项集可以使用一个空的数组字面量,也可以调用默认初始化函数。

    //Swift
    let options: NSDataBase64EncodingOptions = [
        .Encoding76CharacterLineLength,
        .EncodingEndLineWithLineFeed
    ]
    let string = data.base64EncodedStringWithOptions(options)

共用体

Swift仅部分支持C的共用体(union)类型。在导入混有C的共用体或者位段(bitfields)的类型时,例如Foundation的NSDecimal类型,Swift不能存取不支持的字段。但是,参数和/或返回值为这些类型的C和Objective-C的API是能够在Swift中使用的。

指针

Swift尽可能避免让您直接访问指针。然而,当您需要直接操作内存的时候,Swift也为您提供了多种指针类型。下面的表使用Type作为类型名称的占位符。

对于返回类型,变量和参数,使用如下形式:

对于类(class)类型,使用如下形式:


常量指针

当一个函数被声明为接受UnsafePointer参数时,这个函数可以接受下列任何一个类型作为参数:

  • nil,作为空指针传入;
  • 一个UnsafePointer,UnsafeMutablePointer, 或者
    AutoreleasingUnsafeMutablePointer的值,在必要情况下会转换成UnsafePointer的值;
  • 一个String类型的值,如果Type是Int8或者UInt8的话。该字符串会自动在一个缓冲区内被转换为UTF8,该缓冲区在本次调用期间有效;
  • 一个左值操作数为Type类型的输入输出(inout)表达式,传入的是这个左值的内存地址;
  • 一个[Type]值,传入该数组的起始指针,并且它的生命周期将在本次调用期间被延长。
    如果您这样定义了一个函数:
   //Swift
    func takesAPointer(x: UnsafePointer) { /*...*/ }

那么您可以使用以下任何一种方式来调用这个函数:

   //Swift
    var x: Float = 0.0
    var p: UnsafePointer = nil
    takesAPointer(nil)
    takesAPointer(p)
    takesAPointer(&x)
    takesAPointer([1.0, 2.0, 3.0])

如果函数被声明为使用一个UnsafePointer参数,那么这个函数接受任何Type的UnsafePointer类型的操作数。 ? 如果您这样定义了一个函数:

   //Swift
    func takesAVoidPointer(x: UnsafePointer)  { /* ... */ }

那么您可以使用以下任何一种方式来调用这个函数:

   //Swift
    var x: Float = 0.0, y: Int = 0
    var p: UnsafePointer = nil, q: UnsafePointer = nil
    takesAVoidPointer(nil)
    takesAVoidPointer(p)
    takesAVoidPointer(q)
    takesAVoidPointer(&x)
    takesAVoidPointer(&y)
    takesAVoidPointer([1.0, 2.0, 3.0] as [Float])
    let intArray = [1, 2, 3]
    takesAVoidPointer(intArray)

可变指针

当一个方法被声明为接受UnsafeMutablePointer参数时,这个函数可以接受下列任何一个类型作为参数:

  • nil,作为空指针传入;
  • 一个UnsafeMutablePointer类型的值;
  • 一个输入输出(inout)表达式,其左值操作数是Type类型的,且被存储起来了。传入的是这个左值的内存地址;
  • 一个输入输出的[Type]类型的值,传入的是该数组的起始指针,并且它的生命周期将在本次调用期间被延长。
    如果您这样定义了一个函数:
   //Swift
    func takesAMutablePointer(x: UnsafeMutablePointer) { /*...*/ }

那么您可以使用以下任何一种方式来调用这个函数:

   //Swift
    var x: Float = 0.0
    var p: UnsafeMutablePointer = nil
    var a: [Float] = [1.0, 2.0, 3.0]
    takesAMutablePointer(nil)
    takesAMutablePointer(p)
    takesAMutablePointer(&x)
    takesAMutablePointer(&a)

如果函数被声明使用一个UnsafeMutablePointer参数,那么这个函数接受任何Type的UnsafeMutablePointer类型的操作数。

如果您这样定义了一个函数:

//Swift
func takesAMutableVoidPointer(x: UnsafeMutablePointer)  { /* ... */ }

那么您可以使用以下任何一种方式来调用这个函数:

   //Swift
    var x: Float = 0.0, y: Int = 0
    var p: UnsafeMutablePointer = nil, q: UnsafeMutablePointer = nil
    var a: [Float] = [1.0, 2.0, 3.0], b: [Int] = [1, 2, 3]
    takesAMutableVoidPointer(nil)
    takesAMutableVoidPointer(p)
    takesAMutableVoidPointer(q)
    takesAMutableVoidPointer(&x)
    takesAMutableVoidPointer(&y)
    takesAMutableVoidPointer(&a)
    takesAMutableVoidPointer(&b)

自动释放指针

当一个函数被声明为接受AutoreleasingUnsafeMutablePointer参数时,这个函数可以接受下列任何一个类型作为参数:

  • nil,作为空指针传入;
  • 一个AutoreleasingUnsafeMutablePointer类型的值;
  • 一个输入输出(inout)表达式,其操作数首先被拷贝到一个无拥有者的缓冲区,传递给被调用函数的就是这个缓冲区的地址。在调用返回时,缓冲区中的值被加载、保存、并重新复制给操作数。
    注意,这个列表中没有包含数组。

如果您这样定义了一个函数:

   //Swift
    func takesAnAutoreleasingPointer(x: AutoreleasingUnsafeMutablePointer) { /* ... */ }

那么您可以使用以下任何一种方式来调用这个函数:

   //Swift
    var x: NSDate? = nil
    var p: AutoreleasingUnsafeMutablePointer = nil
    takesAnAutoreleasingPointer(nil)
    takesAnAutoreleasingPointer(p)
    takesAnAutoreleasingPointer(&x)

被指针指向的类型并不会被桥接。例如,NSString **转换到Swift后,是AutoreleasingUnsafeMutablePointer,而不是AutoreleasingUnsafeMutablePointer。

函数指针

C语言的函数指针通过调用约定,以闭包的形式被引入Swift中,表示形式为@convention(c)。例如,一个类型为int (*)(void)的C语言函数指针,会转换为Swift的@convention(c) () -> Int32。

在调用一个以函数指针为参数的函数时,给它传的值可以是一个顶层的Swift函数,也可以是个闭包字面量,或者nil。只有符合C语言函数指针调用约定的Swift函数,才能用来给函数指针类型的形参传值。例如,Core Foundation的CFArrayCreateMutable(::_:)函数,它有个参数的类型为CFArrayCallBacks结构体。这个CFArrayCallBacks结构体就是用一些函数指针进行初始化的:

   func customCopyDescription(p: UnsafePointer) -> Unmanaged! {
        // return an Unmanaged! value
    }
    let callbacks = CFArrayCallBacks(
        version: 0 as CFIndex,
        retain: nil,
        release: nil,
        copyDescription: customCopyDescription,
        equal: { (p1, p2) -> Boolean in
            // return Boolean value
        }
    )
    var mutableArray = CFArrayCreateMutable(nil, 0, callbacks)

在上面的例子中,在CFArrayCallBacks初始化时,传给retain和release作参数的是nil,传给copyDescription作参数的是函数customCopyDescription,传给equal作参数的是一个闭包字面量。

全局常量

在C和Objective-C语言源文件中定义的全局常量会自动地被Swift编译引进并做为Swift的全局常量。

预处理指令

Swift编译器不包含预处理器。取而代之的是,它充分利用了编译时属性,生成配置,和语言特性来完成相同的功能。因此,Swift没有引进预处理指令。

简单宏

在C和Objective-C中,通常使用#define指令来定义一个简单的常数,在Swift,您可以使用全局常量来代替。例如:定义一个常数的#define FADE_ANIMATION_DURATION 0.35,在Swift使用let FADE_ANIMATION_DURATION = 0.35来表述会更好一些。由于简单的用于定义常量的宏会被直接被映射成Swift全局量,Swift编译器会自动引进在C或Objective-C源文件中定义的简单宏。

复杂宏

在C和Objective-C中使用的复杂宏在Swift中没有相对应的东西。复杂宏是那些不用来定义常量的宏,包含了括号的函数式宏。您在C和Objective-C使用复杂的宏以避免类型检查的限制或避免重新键入大量的样板代码。然而,宏也会造成debug和重构起来更困难。在Swift中你可以使用函数和泛型来达到同样的效果,而没有任何的委屈折中。因此,在C和Objective-C源文件中定义的复杂宏在Swift是不能使用的。

生成配置

Swift代码使用和C、Objective-C代码不同的方式进行条件编译。Swift代码可以根据生成配置的组合进行条件编译。生成配置包括true和false字面值,命令行标志,和下表中的平台测试函数。您可以使用-D <#flag#>指定命令行标志。


注意:生成配置arch(arm)不会为64位ARM设备返回true,生成配置arch(i386)在为32位iOS模拟器编译代码时会返回true。

一个简单的条件编译可以像下面这段代码:

   #if build configuration
      statements
    #else
      statements
    #endif

由零个或多个有效的Swift语句组成的statements,可以包括表达式,普通语句和控制流语句。可以使用&&和||操作符往一个条件编译语句上添加新的编译条件,使用!操作符来否定某条件,使用#elseif来添加编译块:

    #if build configuration && !build configuration
      statements
    #elseif build configuration
      statements
    #else
      statements
    #endif

条件编译不同的是,Swift条件编译的语句必须是独立完整、语法有效的代码块。这是因为所有的Swift代码都会做语法检查,而不管会不会被编译。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容