oc 兼容swift

2018-08-27 22:11:43 LOLITA0164 阅读数 11918

引言

本文旨在帮助开发者快速从OC开发过渡到Swift开发,挑选了一些比较浅显的但是比较常用的Swift语法特性,在介绍的过程中,通常会拿OC中的语言特性作比较,让大家更好的注意到Swift的不同。
另外需要说明的是,笔者也仅仅是刚刚接触Swift不久,如果有说的不对的地方,还望指正,这里贴出本文所参考的 Swift中文翻译地址,方便大家可以深入了解Swift。

本文发布时,Swift 刚出到 4.1 版本,目前已经发布 5.2 版本,文中的部分内容可能已经不适用,后期会给予修正。

Swift简介

Swift 是一门开发 iOS、macOC、watchOS 和 tvOS 应用的新语言,在初期,为了让 OC 开发者快速的过渡到 Swift 开发,Swift 继承了很多 C、OC 的语法特性,但是随着 Swift 的不断完善,已经慢慢自称一体,不仅保留了 OC 的很多语言特性,还借鉴了很多语言,如 C#、Java、Python 等。
Swift 包含了 C 和 OC 上所有的基础数据类型:Int、Double、Float、Bool、String,集合类型:Array、Set 和 Dictionary,除此之外,Swift 增加了 OC 中没有的高阶数据类型如元组(Tuple),元组方便我们接收或传递一组数据,或返回多个值而不必使用结构体、类等。
另外,Swift 新增了可选(Optional)类型,可选表示有值或者没有值,需要注意的是,Swift 中,不同类型的数据类型是不能够相互操作的,如 Int?和 Int 属于不同类型,又如 Int 和 Float。

Swift基础篇

常量let和变量var

常量一旦设定就不可改变,变量则值可变。常量使用let来声明,变量使用var表示。

let PI = 3.1415
var Str = "string"

Swift 可以根据赋值推断出类型,你也可以使用类型标注(type annotation)来指定类型

let PI: Float = 3.1415
var Str: String = "string"

一般来说声明常量或变量形式如下:

let/var name: type = value

输出print

你可以使用print函数输出常量和变量,输完换行。Swift 取消了旧版的println输出方法,另外,如果你想使用OC中的NSLog也是可以的。print(, separator: , terminator: )中,将 terminator 参数设为空字符""则可以不进行换行。
Swift使用字符串插值(string interpolation)的方式将常量或变量当作占位符加入到长字符串中,我们可以借此拼接长字符。

let value1 = "123"
var value2 = 345
print("value1 = \(value1) , value2 = \(value2)")
// 输出: value1 = 123 , value2 = 345

数据类型(布尔值、数组、字典、元组、可选类型)

  • 布尔值

和 OC 不同,Swift 中的布尔值使用truefalse表示真、假

let boolValue = true
if boolValue {
    print("value is true")
}

这里需要说明的是,Swift 不能像 OC 中那样,数值为0即表示布尔值NO,非0即为YES,因此下面OC代码可以通过

float value = 0.001;
if (value) {
	NSLog(@"value:%f",value);
}
// 输出: value:0.001000

而在Swift中会编译报错

let value = 0.001
if boolValue {
    print("value is true")
}

Swift 在判断语句中必须采用布尔值

let value1 = 1.1
let value2 = 2.0
if value1 > value2 {
    print("value1:\(value1) 大于 value2:\(value2)")
}else{
    print("value1:\(value1) 小于 value2:\(value2)")
}
// 输出: value1:1.1 小于 value2:2.0
  • 数组(array)

数组是一种泛型应用,关于泛型,后面会说到。

数组有多种创建方式

var list1:Array = [1,2,3]  // 标准创建,元素类型由系统推断
var list2:Array<Int> = [1,2,3]  // 指定类型,一种泛型应用
var list3 = [1,2,3]     // 由系统推断类型
var list4:[Int] = [1,2,3]   // 简写Array,并指定元素
var list5 = [Int]([1,2,3])  // [Int]()则创建空数组
var list6 = Array([1,2,3]) // 等同于Array.init([1,2,3])
var list7 = Array.init(repeating: 1, count: 3)  // 重复3次元素1,数组类型由元素推断

需要说明的是:Swift 中并没有可变不可变两种数组类型之分,不可变类型可以通过let来声明,var声明的数组都是可变的,类似的,字典类型也是如此。

数组的一些常用操作

var a = [Float]()
a.append(1.1) // 添加元素
a += [2,5]	// 拼接其他数组
a[1...2] = [11,22] // 修改元素, '...'表示范围range
a.insert(33, at: a.count) // 插入元素,此处插在了数组的末尾
a.remove(at: a.count-1) // 移除指定位置的,此处相当于 a.removeLast()
a.removeFirst() // 移除第一个
// 遍历数组
for value in a {
    print("index=\(a.index(of: value)!),element=\(value)")
}
for (index,value) in a.enumerated() {	// (,)表示元组类型
    print("index=\(index),element=\(value)")
}
  • 字典(Dictionary)

字典的创建和数组类似,可以由系统推断出元素类型,也可以指定元素类型。和 OC 不同的是,Swift 创建不使用 @{} 语法糖创建,而是使用和数组一样的 [] 形式,和数组不同的是,[] 中需要 : 分隔,左边是 key 的类型,右边是 value 类型,下面演示其中一种创建方式。

// [Int:String]为简写,其中Int为key的类型,String为value类型
var d:[Int:String] = [200:"success",404:"not found"]

字典的一些常用操作

d[200] // 取值
d[500] = "internal server error" // 添加
// 遍历字典
for key in d.keys {  // 遍历key
    print("key=\(key)")
}
for value in d.values {  // 遍历value
    print("value=\(value)")
}
for (key,value) in d {   // 遍历元组
    print("key=\(key),value=\(value)")
}
  • 元组

元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,这一点和数组不同,它不要求元素相同类型,这里的元组和 Python 中的元组功能类似。
Swift 的元组使用圆括号()创建,不能省略,这点和Python不同。

let size = (100,200)
// 输出 100 200
print(size.0,size.1)
// 也可以指定名称
let httpError = (code:404,reason:"Not Found")
print(httpError.code,httpError.reason)  // httpError.code 和 httpError.0 是一样的
// 输出 404 Not Found

应用举例:实现交换

var (v1,v2) = (10,20)
print("v1:\(v1),v2:\(v2)")
// 输出 v1:10,v2:20
(v1,v2) = (v2,v1)
print("v1:\(v1),v2:\(v2)")
// 输出 v1:20,v2:10

应用举例:函数多参数返回,在数组和字典中的遍历过程中已经有体现

需要注意的是:元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。

  • 可选类型

可选类型:有值 或者 没有值

我们先来看下系统方法产出的可选类型。Int 类型有一种构造器,将一个 String 值转换为一个 Int 值,但是,并不是所有的字符串都可以转换为一个整数。如 “123” 可以被转换为数字123,而字符串"hello, world"则不可以。

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// 当你敲convertedNumber时,可以看到类型"Int?"类型 

这时因为这个构造器可能会失败,所以它返回一个可选类型 Int?,而不是 Int(注意,这两种属于不同类型),? 表示包含的值可能有值,也可能没有值,当我们需要使用 Int 类型时,需要转换为 Int 才可以。

声明一个可选类型

使用 ? 来声明一个可选类型

var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
print("\(serverResponseCode!)")  // 这里的'!'表示强解
// 输出 404
serverResponseCode = nil
// serverResponseCode 现在不包含值

如果你声明一个可选常量或者变量却没有赋值,他们会自动被设置为nil,就像下面这样:

var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil

这时如果你想要使用 ! 强解 surveyAnswer 会发生错误,所以在确定一个可选值时,务必保证可选值不为 nil。

可选绑定

我们可以使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含值就把值赋值给一个临时常量或者变量。通常会出现在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋值给一个常量或者变量。

let possibleNumber = "123"
if let actualNumber = Int(possibleNumber) {
    print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
    print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出 "'123' has an integer value of 123"

因为 Int(possibleNumber) 包含来一个值,因此进入 if 的方法体,并会将该值赋值给常量 actualNumber。

隐式解析可选类型

可选类型除了使用可选绑定来解析值,我们也可以使用 ! 来解析值,但是可选此时不可出现 nil 的情况,否则会出现错误。

有时候,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。

这种类型的可选状态被定义为隐式解析可选类型,我们可以将 ? 换成 ! 来声明一个隐式解析可选类型,这种情况下,我们无需手动使用 ! 来解析可选值,系统会帮我们完整可选类型的解析。

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

// 表示该成员变量必定有值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString  // 不需要感叹号

需要注意的是:和普通可选类型一样,如果你在隐式解析可选类型没有值的时候尝试取值,同样会触发运行时错误。因此,如果一个变量之后可能变为 nil 的话,请不要使用隐式解析可选类型,否则请使用普通可选类型。

可选类型在开发过程中经常出现,大家需要好好理解,正确使用使用 ?!

  • Any、AnyObject和AnyClass

Any : 表示任意类型,Any? 还包括了 nil
AnyObject : 代表任务 class 类型,无论 class 是否谁的子类又或者是基类
AnyClass : AnyObject的别名,和AnyObject一样

几种运算符

这里提及一些和 OC 中不一样的几种运算符。

  • 区间运算符

区间运算符通常用于整型或者范围。区间运算符有两种: ..<... ,前者为开区间 CountableRange,后者为闭合区间 ClosedRange。

// 闭区间运算符
for i in 0...9 {
    print(i)
}
// 半开区间运算符
let names = ["Anna", "Alex", "Brian", "Jack"]
for i in 0..<names.count {
    print("第 \(i + 1) 个人叫 \(names[i])")
}
// 第 1 个人叫 Anna
// 第 2 个人叫 Alex
// 第 3 个人叫 Brian
// 第 4 个人叫 Jack

// 单侧区间
for name in names[2...] {
    print(name)
}
// Brian
// Jack
for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

let range = ...5
range.contains(7)   // false
range.contains(-1)  // true
  • 空合运算符

空合运算符(a ?? b)将对可选类型 a 进行判断,如果 a 包含一个值就进行解封,否则就返回一个默认值 b。注:表达式 a 必须是可选类型,而 b 的类型必须和 a 存储值的类型保持一致。

let bValue = "default value"	// 作为默认值
var aValue : String?  // 默认值为 nil
let result = aValue ?? bValue
print(result)
// 输出 default value
  • 溢出运算符

在默认情况下,当向一个整数赋值超过它的容量时,Swift 会报错。我们可以使用 maxmin 访问整形的最大或最小值

var maxInt = Int8.max
var minInt = Int8.min
print("Int8.max:\(maxInt), Int8.min:\(minInt)")
// 输出 Int.max:127, Int.min:-128

如果此时我们尝试给 maxInt 加1或者给 minInt 减1都会错误

maxInt += 1		// 编译错误
minInt -= 1		// 编译错误

我们可以使用溢出运算符来解决这种上溢或下溢现象

minInt = minInt &- 1	// 相同的有 &- &* &/
print("\(minInt)")
// 输出 127

Int8 型整数能容纳的最小值是 -128,以二进制表示即 10000000。当使用溢出减法运算符对其进行减 1 运算时,符号位被翻转,得到二进制数值 01111111,也就是十进制数值的 127,这个值也是 Int8 型整数所能容纳的最大值。

另外,在新版 Swift 中,++-- 运算符被取消,因此 i++ 这种形式的累加需要换成 i += 1 这种形式。

控制语句

Swift 提供了多种控制流语句,包括 while 循环、if、guard、switch、跳转break、continue 等等。在 Swift 中,for-in 循环可以更简单的遍历数组、字段、区间、字符等序列类型,不同于 C 和 OC,Swift 在新版本中取消了 C 的 for 条件循环,即 for var i=0;i<a;i++ 这种形式。值得一提的是,Swift中的 switch 语句比 C 中更加强大,case 可以匹配不同的模式,包括范围匹配,元组和特定类型匹配,甚至是使用 where 来描述更多约束的情况。详情可以看官网翻译的控制流。

我们这里介绍提前退出 guard

像 if 语句一样,guard 的执行取决于一个表达式的布尔值。当 guard 要求条件的为真时,可以继续执行 guard 语句之后的代码,否则提前返回结束。和 if 不同的是,guard通常只有一个 else 从句。

func greet(_ name:String) {
    guard name == "LOLITA0164" else {
        print("hello! \(name)")
        return
    }
    print("\(name)")
}
greet("xiao ming")	// 输出 hello! xiao ming
greet("LOLITA0164")	// 输出 LOLITA0164

我们看到,当输入"xiao ming" 时,条件为 false,输出 “hello! xiao ming” 后返回退出,当输入 “LOLITA0164” 时,条件为真,继续执行 guard 后面当语句,输出 “LOLITA0164”。

因此 guard 可以用来控制语句不满足条件时提前结束。

函数和闭包

  • 函数

Swift 的函数参数和返回值非常灵活。参数可以是的无参、多参、默认参、可变参、甚至一些高级语言中的输入输出参数,参数类型也非常多,甚至包括另一个函数,另外Swift 函数支持嵌套参数。在返回类型上,可以是无返回类型,多参数返回、甚至是一个函数。

Swift 的函数使用 func 声明一个函数,形式为:

func name(parameters) -> return type {
    function body
}

在上一个介绍中,已经涉及到了函数创建和使用 greet,下面介绍一下常用的函数形式。

// 无参数函数,有返回值
func sayHello() -> String {
    return "hello, world"
}
// 多参数函数,无返回值( -> Void 可以省略 )
func personInfo(name: String, age: Int) -> Void {
    // function body
}
// 多重返回值,这里返回一个元组
func minMax(array: [Int]) -> (min: Int, max: Int) {
    return (array.min()!, array.max()!)
}
// 可选返回类型,‘_’ 表示忽略参数标签
func max(_ array: [Int]) -> Int? {
    return array.max()
}
// 指定参数标签(参数说明,和参数名以空格分隔),区别于参数名,默认参数名就是外部的标签名
func someFunction(parameterDescription parameter: String) {
    print(parameter)
}
someFunction(parameterDescription: "parameter")
// 可以看到参数标签只供外部调用显示使用,而参数名parameter可以供内部函数使用
// 默认参数
func sumFunc(a: Int, b: Int = 12) -> Int {
    return a &+ b
}
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
print(sumFunc(a: 3))    // 输出 15
// 可变参数,使用(...)的方式接收可变参数,并且必须作为左后一个参数
func sumFunc2(_ numbers: Int ...) -> Int {
    var total: Int = 0
    // 和OC实现不定参数不同,Swift已将不定参转换为数组
    for number in numbers {
        total += number
    }
    return total
}
print("\(sumFunc2(1,2,3,4,5))") // 输出 15
/*
 输入输出参数
 如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就可以将这个参数定义为输入输出参数
 这有点类似OC中常见的NSError的使用,他们的本质是接受一个地址,直接操作内存地址进行修改和访问
 可是使用 `inout` 关键字来定义输入输出参数,需要注意的是,在新版本中,已经将 inout 放在了 '参数名:' 后面,而不是前面
 */
func swapTwoObj(_ a: inout String, _ b : inout String) {
    (a, b) = (b, a)
}
var str1 = "123"
var str2 = "456"
swapTwoObj(&str1, &str2)
print("str1:\(str1),str2:\(str2)")  // 输出 str1:456,str2:123

a. 函数类型

在 Swift 中,函数可以看成是一种数据类型,使用就像使用其他类型一样,我们可以定义一个类型为函数类型的常量或变量,并将适当的函数赋值给它。

函数类型作为常量

// 我们将上面新写的交换方法赋值给一个常量
let newSwap = swapTwoObj
// 我们可以使用该常量正常调用
newSwap(&str1, &str2)
print("str1:\(str1),str2:\(str2)")
// 输出 str1:456,str2:123

函数类型作为参数

// 定义求和函数
func sum(_ a: Int,_ b: Int) -> Int{
    return a + b
}
// 定义求差函数
func diff(_ a: Int,_ b: Int) -> Int{
    return a - b
}
// 定义计算函数,可以接受一个函数类型的参数
func calculate(_ a: Int,_ b: Int, fn: (Int, Int) -> Int) -> Int {
    return fn(a,b)
}
print("sum:\(calculate(10, 5, fn: sum))")       // 输出 sum:15
print("diff:\(calculate(10, 5, fn: diff))")     // 输出 sum:5

函数类型作为返回值

// 获取一个函数类型
func getFunc() -> (Int, Int) -> Int {
    // 正如前面提到的,Swift支持嵌套函数,这里我们在函数内部定义一个嵌套函数作为返回值
    func sumNew(_ a: Int,_ b: Int) -> Int{
        return a*a + b*b
    }
    return sumNew
}
let fn = getFunc()
print("\(fn(2,3))")     // 输出 13 (2*2+3*3)

b. 嵌套函数

func add() -> ()->Void {
    var total = 0
    var step = 1
    func fn() {
        total += step
        print("total:\(total)")
    }
    return fn
}
var a = add()
a()     // 输出 total:1
a()     // 输出 total:2
// 我们发现,第二次调用a()后,输出值为2而不是1,这说明虽然add()调用完成,但是嵌套函数 fn 捕获了total和step副本
  • 闭包

闭包是只包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 OC 中的代码块(block)以及其他编程语言中的匿名函数比较相似。

闭包可以捕获和存储其所在上下文中的任意常量和变量的引用。

在上面介绍的全局函数和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:

  1. 全局函数是一个有名字但不会捕获任何值的闭包
  2. 嵌套函数是一个有名字并可以捕获其封闭函数作用域内值的闭包
  3. 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

闭包表达式

{ (parameters) -> returnType in
    statements
}

Swift 提供了名为 sorted(by:) 的方法,它会根据你提供的用于排序的闭包函数将数组中的元素进行排序。你可以提供一个闭包函数,或者直接实现它的闭包函数(也叫内联闭包)。

内联闭包

// 定义一个字符串数组。作为数据源
let numbers = ["12", "21", "22", "3", "14"]
var numbers_sorted = numbers.sorted { (s1: String, s2: string) -> Bool in
    return Int(s1)! > Int(s2)!
}
print("\(numbers_sorted)")
// 输出 ["22", "21", "14", "12", "3"]

根据上下文推断类型

// 这里可以推断出参数类型以及返回值类型,可以省去参数类型和返回值类型
numbers_sorted = numbers.sorted { (s1, s2) in
    return Int(s1)! > Int(s2)!
}

单表达式闭包隐式返回

// 如果方法体是单行,我们可以省去return关键字,直接返回单行表达式的结果
numbers_sorted = numbers.sorted { (s1, s2) in
    Int(s1)! > Int(s2)!
}

参数名缩写

Swift 自动为内联闭包提供了参数名缩写功能,你可以直接通过 $0$1 等顺序访问参数。

// 当我们使用了参数名缩写时,in关键字也可以省略
numbers_sorted = numbers.sorted { Int($0)! > Int($1)! }

尾随闭包

如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用,事实上,当你使用尾随闭包时,你甚至可以把 () 省去,另外,前面关于 sorted 内联闭包实现就是尾随闭包,一般来说,闭包会在 () 内部。

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数当函数时,你可以在参数名之前标注@escaping,用来指明这个闭包是允许 逃逸 出这个函数的。

在iOS开发中,在我们使用异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要 逃逸 出函数,因为闭包需要在函数返回之后被调用。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

类和结构体

和 OC 不同,Swift 并不要求你为自定义的类去创建独立接口 .h和实现 .m 文件。你所需要做的是一个单一文件定义一个类或者结构体,系统将会自动生成面向其他代码的外部接口。

在 Swift 中,类可以实现的功能结构体大部分都可以完成,如存储值、定义方法、初始化、构造器、可扩展、实现协议等,但是两者还是有一些差别的,相比结构体,类还具有如下功能:

  • 类可以继承
  • 类型转换允许在运行时检查和解释一个实例的类型
  • 析构器允许一个实例释放任何其所被分配的资源
  • 类是引用类型,而结构体是值类型

我们可以使用关键字 class 来声明一个类,struct 来声明一个结构体。

class Person {    // Swift中一个不继承于任何其他基类,那么此类本身就是一个基类
    // 定义属性,Swift中有两种属性,一个是计算属性,一种是存储属性
    // 实例属性
    var name:String     // 存储属性
    var height = 0.0    // 存储属性
    var info: String {  // 计算属性
        return "\(name)" + "\(height)"
    }
    // 类型属性
    static var address = "fuzhou"
    // 懒加载,必须是变量,并且需要一个初始值
    lazy var data: [String] = [String]()
    
    // 构造方法,注意如果不编写构造方法,默认自动创建一个无参数的构造方法
    init(name:String ,height:Double) {
        self.name = name
        self.height = height
    }
    // 便利构造器,提供默认值来简化构造方法实现
    convenience init(name:String) {
        self.init(name: name, height: 170.0)
    }
    // 定义方法
    // 实例方法
    func showMessage() {
        print("name=\(self.name),height=\(height)")
    }
    // 类型方法,结构体中使用 static 关键字
    class func sayHello() -> Void {
        print("hello world!")
    }
    // 析构方法,在对象被释放时调用,类似于oc的dealloc,此函数没有括号,没有参数,无法直接调用
    deinit {
        print("deinit...")
    }
}

类中常用的"元素"都在上面,接下来在外面做使用演示:

// 使用类型属性
print("\(Person.address)")  // 输出 fuzhou
// 调用类型方法
Person.sayHello()   // 输出 hello world!
var p: Person? = Person.init(name: "LOLITA0164") // 创建一个实例
// 调用实例方法
p!.showMessage()  // 输出 name=LOLITA0164,height=170.0
// 测试释放(注意,实例必须是可选类型才可以赋值为nil)
p = nil     // 输出 deinit...

关于类的构造器的一些说明。

  • 子类的指定构造方法必须调用父类构造方法,并确保调用发生在子类存储属性初始化之后,而且指定构造方法不能调用同一个类中的其他指定构造方法
  • 便利构造方法必须调用同一个类中的其他指定构造方法(可以是指定构造方法或者便利构造方法),不能直接调用父类构造方法(用以保证最终以指定构造方法结束)
  • 如果父类仅有一个无参数构造方法(不管是否包含便利构造方法),子类的构造方法默认就会自动调用父类的无参构造方法(这种情况下可以不用手动调用)
  • 常量属性必须默认指定初始值或者在当前类的构造方法中初始化,不能在子类构造方法中初始化

关于类和结构体的选择。

在开发中,我们可以使用类和结构体来自定义数据类型,但是在两种数据类型选择的上需要考虑不同的业务情况因为结构体实例总是通过值传递,类实例总是通过引用传递。
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:

  1. 该数据结构的主要目的是用来封装少量相关简单数据值
  2. 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用
  3. 该数据结构中存储的值类型属性,也应该被拷贝,而不是引用
  4. 该数据结构不需要去继承另一个既有类型的属性或行为

举例来说,以下情景中适合使用结构体:

  1. 几何形状的大小:封装一个 width 属性和 height 属性
  2. 一定范围的路径:封装一个 start 属性和 length 属性
  3. 三维坐标系内的一点:封装 x,y 和 z 属性

在所有其他案例中,定义一个类,生成一个它的实例,并通过引用来管理和传递,在实际应用中,这意味着绝大部分的自定义数据类型都应该是类,而不是结构体。

在 Swift 中,许多基本类型,诸如 String、Array 和 Dictionary 类型都是以结构体的形式实现,这意味着被赋值给新的常量或变量,或传入函数、方法中时,他们的值会被拷贝。在OC中,则相反,它们是类实现,引用传递。

属性

在 OC 中,除了属性之外,还可以使用成员变量作为属性值的后端存储。而在 Swift 中,把这些理论统一使用属性来实现。

属性将值和特定的类、结构、枚举关联。属性有存储属性和计算属性,前者存储常量或者变量,只能用于类和结构体,后者计算一个值,不仅可以用于类和结构体,还可以是枚举。
另外,我们可以使用属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自定义存储属性,也可以添加到继承父类的属性上。

存储属性在函数一节有所提及,下面说一些特别的属性。

  • 计算属性

计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter ,来间接获取和设置其他属性或变量的值。

class Person {
    var firstName = ""  // 存储属性 
    var lastName = ""   // 存储属性
    // set、get的计算属性
    var fullName: String {
        get{
            return "\(firstName)" + "\(lastName)"
        }
        set {
            // set中的newValue表示即将赋予的新值,也可以定义其他名称 set(otherValye){}
            let array:Array = newValue.components(separatedBy: ".")
            if array.count>=2 {
                firstName = array[0]
                lastName = array[1]
            }
        }
    }
    // 只读的get属性,这时可以去掉get和花括号
    var info: String {
        return fullName
    }
}
  • 延迟属性(懒加载)

OC 中的懒加载在 Swift 中使用延迟属性完成,使用关键字 lazy 来标记。
延迟属性必须是变量(var关键字),这是因为属性的初始值可能在实例构造完成之后才会得到,而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

延迟属性在开发过程中非常有用,它可以避免一些不必要的初始化,而将属性初始化延迟到第一次调用的时刻。

class Person {
    lazy var data: [String] = [String]()
    lazy var p: Person = {
        return Person()
    }()
}
  • 属性观察器

Swift 中提供了 willSetdidSet 两个属性观察器来监控和响应存储属性值的变化,你不必为非重写的计算属性添加属性观察器,因为它们可以通过 settergetter 来监控和响应值的变化。

willSet 在新值被设置之前调用,并可以获取到 newValue;
didSet 在新值被设置之后调用,并可以获取到 oldValue。

注:如果在didSet方法中再次对该属性赋值,那么新值将被覆盖。在构造器中进行第一次初始化时,不会触发观察器。

class Person {
    var firstName = "" {
	    willSet {
		    // do something
	    }
	    didSet {
		    // do something
	    }
    }
}
  • 类型属性

实例属性属于一个特定的实例,每创建一个实例,该实例拥有属于自己的一套属性值,实例之间的属性相互独立。如果一种属性的拥有者属于同一种类型,那么我们称之为类型属性,这种类型用于所有实例共享数据。我们通过 classstatic 来定义类型属性。

存储型类型属性可以是变量,也可以是常量,计算型类型属性只能是变量属性,这一点和实例计算型属性一样。

注:存储型的类型属性必须指定默认值。

枚举

枚举为一组相关的值定义了一个共同的类型。

Swift 中的枚举的原始值类型可以是字符串、字符、整型或者浮点数。

此外,枚举成员可以指定任意类型的关联值存储到枚举成员中。

  • 枚举语法

使用 enum 关键字来创建枚举

enum SomeEnumeration {
    // 枚举定义放在这里
}

示例

// 单行
enum Season{
    case spring, summer, autumn, winter
}
// 多行,并指定原始值
enum Week: Int {
    case monday     = 1
    case tuesday    = 2
    case wednesday  = 3
    case thursday   = 4
    case friday     = 5
    case saturday   = 6
    case sunday     = 7
}

print("\(Week.friday)")				// 输出 friday
print("\(Week.friday.rawValue)")	// 输出 5

枚举类型也可以拥有一些结构体的特性,如计算属性、构造方法、方法等

enum Week: Int {
    case monday     = 1
    case tuesday    = 2
    case wednesday  = 3
    case thursday   = 4
    case friday     = 5
    case saturday   = 6
    case sunday     = 7
    
    // 定义计算属性(枚举中不能使用存储属性)
    var tag: Int {
        return self.rawValue
    }
    // 定义类型计算属性
    static var enumName:String {
        return "Week"
    }
    // 定义实例方法
    func showRowValue() {
        print("\(self.rawValue)")
    }
    // 定义类型方法
    static func showInfo() {
        print("Enum name is \(Week.enumName)")
    }
    // 定义构造方法,因为可能构造失败返回nil,因此需要返回值为可选类型
    init?(name:String){
        if name.contains("mon") {
            self = .monday
        }else if name.contains("tue") {
            self = .tuesday
        }else if name.contains("wed") {
            self = .wednesday
        }else if name.contains("thu") {
            self = .thursday
        }else if name.contains("fri") {
            self = .friday
        }else if name.contains("sat") {
            self = .saturday
        }else if name.contains("sun") {
            self = .sunday
        }else{
            return nil
        }
    }
}

var day = Week.monday
print("\(day.tag)")         // 输出 1
print("\(Week.enumName)")   // 输出 Week
day.showRowValue()          // 输出 1
Week.showInfo()             // 输出 Enum name is Week
// 构造一个枚举类型,系统有默认的构造器,这里我们使用自定义的构造器
var dayNew = Week.init(name: "wed")
dayNew?.showRowValue()      // 输出 3
  • 关联值

前面有提到,枚举可以拥有关联值,这让枚举可以存储一些额外的自定义信息(这些信息是任意类型),额外信息并不影响你正常使用枚举,下面举个关于颜色的例子。

enum Color {
    case RGB(String)
    case CMYK(Float,Float,Float,Float)
    case HSB(Int,Int,Int)
}

var red = Color.RGB("#FF0000")
switch red {
case let .RGB(colorValue):
    print("color's value=\(colorValue)")
case let .CMYK(c, m, y, k):
    print("c=\(c),m=\(m),y=\(y),k=\(k)")
case let .HSB(h, s, b):
    print("h=\(h),s=\(s),b=\(b)")
}
// 输出 color value = #FF0000

扩展

在 Swift 中,扩展可以为一个已知的类、结构体、枚举或者协议类型添加新的功能,这个扩展和 OC 的分类类似,OC中的分类需要名字的,而 Swift 只需使用 extension 关键字加 需要扩展的类型即可。另外,但是要比OC的分类功能强大,它可以:

  1. 添加计算型实例或类型属性
  2. 定义实例方法和类型方法
  3. 提供新的构造器
  4. 定义下标
  5. 定义和使用新的嵌套型
  6. 实现已知类型的某个协议方法

注:扩展虽然可以添加新功能,但是不能重写已有的功能。

扩展的语法

extension SomeType {
    // 为 SomeType 添加的新功能写到这里
}

我们来扩展一个 UIColor 类来演示扩展的使用。

extension UIColor {
    /// 扩展RGB类型,类型方法
    static func rgba(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat,_ a:CGFloat) -> UIColor {
        return UIColor.init(red: (r)/255.0, green: (g)/255.0, blue: (b)/255.0, alpha: a)
    }
    static func rgb(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat) -> UIColor {
        return rgba((r), (g), (b), 1)
    }
    /// 十六进制的色值,类型方法
    static func hex(_ value:Int) -> UIColor{
        return rgb(CGFloat((value & 0xFF0000) >> 16),
                   CGFloat((value & 0x00FF00) >> 8),
                   CGFloat((value & 0x0000FF)))
    }
    /// 随机色,类型属性
    static var rand: UIColor {
        return rgb(CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)))
    }
}

使用

// 获取一个随机色
var color = UIColor.rand
// 获取十六进制的颜色
color = UIColor.hex(0xff0000)
// 获取RGB的颜色
color = UIColor.rgb(0, 0, 255)

对于其他的扩展功能,大家自行尝试。

协议

协议,用来约束某个类型而定义的某些特定的任务或者功能方法、属性等,类、结构体、枚举都可以遵循和实现协议。关于协议,可以在官方翻译中查询更多的详细介绍。

  • 协议语法
protocol SomeProtocol {
    // 这里是协议的定义部分
}

要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号 : 分隔。遵循多个协议时,各协议之间用逗号 , 分隔:

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 这里是结构体的定义部分
}

拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 这里是类的定义部分
}

示例

// 定义一个可以说话的协议
protocol sayHelloProtocol {
    func sayHello()
}
// 定义一个Person类,并遵循这个协议
class Person: sayHelloProtocol {
    // 实现这个sayHello功能
    func sayHello() {
        print("hello, world!")
    }
}
// 定义一个Dog类,并遵循这个协议
class Dog: sayHelloProtocol {
    // 实现这个sayHello功能
    func sayHello() {
        print("wang! wang wang!")
    }
}
let p = Person()
let d = Dog()
p.sayHello()    // 输出 hello, world!
d.sayHello()    // 输出 wang! wang wang!
  • 可选协议

协议中的要求默认都是必须实现的,当协议可以定义可选要求时,遵循协议当类型可以选择实现这些要求,或选择不实现。

在协议中使用 optional 关键字作为前缀来定义可选要求,可选要求用在 OC 混编中,协议和可选要求必须带上 @objc 关键字。标记 @objc 特性的协议只能被继承自 OC 的类或者 @objc 类遵循,其他类以及结构体、枚举均不能遵循这种协议。

// 定义一个可以说话的协议
@objc protocol sayHelloProtocol {
    func sayHello()
    @objc optional func run()
}

在我们定义的 sayHello 协议中,我们定义了一个可选要求的 run() 方法,在 Person 类和 Dog 类中都可以选择不实现。

循环引用

Swift 中采用的是 ARC 来管理内存,这中 ARC 和 OC 中的 ARC 非常相似,因此 Swift 中的循环引用和 OC 中的循环引用非常类似。这里就只介绍循环引用的解决办法。

Swift 提供了两种办法来解决实例之间的循环引用问题:弱引用(weak)和无主引用(unowned)。

相比于当前实例,其他实例有更短的生命周期时,使用弱引用,相反的,当其他实例有相同的或者更长生命周期时,请使用无主引用。

  • 弱引用(weak)

因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC会在引用的实例被销毁后自动将其赋值为nil。并且因为弱引用可以允许它们的值在运行时被赋值为 nil,所以它们会被定义为可选类型变量,而不是常量。

class Person {
    var apartment: Apartment?
    deinit { print("Person is being deinitialized") }
}

class Apartment {
    weak var tenant: Person?
    deinit { print("Apartment is being deinitialized") }
}

var p: Person? = Person()
var a: Apartment? = Apartment()
// 循环引用
p?.apartment = a
a?.tenant = p

Person 实例保持对 Apartment 实例对强引用,但是 Apartment 实例只是对 Person 实例的弱引用。这意味着当你断开 p 变量所保持的强引用时,再也没有指向Person实例的强引用了:

p = nil // 输出 Person is being deinitialized

现在唯一剩下的指向 Apartment 实例的强引用来自于变量 a 。如果你断开这个强引用,再也没有指向 Apartment 实例的强引用了,这是 a 也将会被销毁:

a = nil // 输出 Apartment is being deinitialized
  • 无主引用(unowned)

无主引用和弱引用类似,不会牢牢保持住引用的实例,但是无主引用通常都被期望拥有值,ARC无法在实例被销毁时将无主引用设为 nil,因为非可选类型的变量不允许被赋值为 nil。

注:使用无主引用,你必须确保引用始终指向一个未销毁的实例,如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。这一点和弱引用相反。

闭包中的循环引用

Swift 提供了一种优雅的方法来解决这个问题,称之为闭包捕获列表。我们在定义闭包时同时定义捕获列表作为闭包的一部分。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。

闭包捕获列表需要放在参数列表和返回类型前面,如果没有则放在 in 前面:

var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // 这里是闭包的函数体
}
  • 弱引用和无主引用的选择

相比于当前实例,其他实例有更短的生命周期时,使用弱引用;相反的,当其他实例有相同的或者更长生命周期时,请使用无主引用。

类型转换

关于类型转换,在实际开发中经常使用,想当初笔者没有什么正确的文档参考,更具xcode的提示,加之自己多次尝试,才摸出点门道来。

类型转换无非是判断实例的类型,将其转换为其子类或者父类,当然转为父类是不很必要的,我们遇到最多的情况就是将父类转为其子类。

  • is (检查类型) as (向下转型)

用类型检查操作符 is 来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。

某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(as?as!)。

由于向下转型可能失败,因此类型转换有两种不同的形式。as? 返回一个可选值,强制形式 as! 会强制解包转换结果。当你不确定是否成功,使用 as?,如果转型失败,返回 nil,这时你能够检查向下转型是否成功。只有当你可以确定向下转型一定会成功时,才使用强制形式 as! 。当你试图向下转型一个不确定的类型时,强制形式的类型转换会触发一个运行时错误。

实例对象向下转换

// 定义一个动物类
class animal: NSObject {
    var name: String
    init(name: String) {
        self.name = name
    }
    func showMessage() {
        print("\(name) is \(self.classForCoder) 类型")
    }
}
// 定义一个猫类,该类继承自动物类
class Cat: animal {
    func eat() {
        print("Cat loves to eat fish")
    }
}
// 定义一个狗类,该类继承自动物类
class Dog: animal {
    func bark() {
        print("Dog barking")
    }
}

let cat = Cat.init(name: "mi mi")
let dog = Dog.init(name: "wang cai")
// 将如我们有一个实例数组
let animals = [cat,dog]
// 当我们需要根据不同实例类型调用不同功能时
for anim in animals {
    anim.showMessage()
    // 使用 is 关键字判断类型
    if anim is Cat {
        // 使用 as 转换类型
        let cat = anim as! Cat
        cat.eat()
    }
    // 使用 isKind 方法来判断类型
    else if anim.isKind(of: Dog.classForCoder()){
        let dog = anim as! Dog
        dog.bark()
    }
}

// 输出
/*
mi mi is Cat 类型
Cat loves to eat fish
wang cai is Dog 类型
Dog barking
*/
  • Any 和 AnyObject 的类型转换

Swift 为不确定类型提供了两种特殊的类型别名:

  1. Any 可以表示任何类型,包括函数类型
  2. AnyObject 可以表示任何类类型的实例

例如我们有一个 Any 类型来混合不同类型一起工作,当我们需要特性类型执行特性任务时,就需要将 Any 转为不同的类型。

var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append({ (name: String) -> String in "Hello, \(name)" })
for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as a Double")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        print("an (x, y) point at \(x), \(y)")
    case let stringConverter as (String) -> String:
        print(stringConverter("Michael"))
    default:
        print("something else")
    }
}

OC 和 Swift 混编

Swift 和 OC 的映射关系

Swift OC 说明
AnyObject id(ObjC中的对象任意类型) 由于ObjC中的对象可能为nil,所以Swift中如果用到ObjC中类型的参数会标记为对应的可选类型
Array、Dictionary、Set NSArray、NSDictionary、NSSet 注意:ObjC中的数组和字典不能存储基本数据类型,只能存储对象类型,这样一来对于Swift中的Int、UInt、Float、Double、Bool转化时会自动桥接成NSNumber
Int NSInteger、NSUInteger 其他基本类型情况类似,不再一一列举
NSObjectProtocol NSObject协议(注意不是NSObject类)
ErrorType NSError
#selector(ab:) @selector(ab:)
init(x:X,y:Y) initWithX:(X)x y:(Y)y
func xY(a:A,b:B) void xY:(A)a b:(B)b
extension(扩展) category(分类) 注意:不能为ObjC中存在的方法进行extension
Closure(闭包) block(块) 注意:Swift中的闭包可以直接修改外部变量,但是block中要修改外部变量必须声明为__block

Swift 兼容大部分 OC,当然还有一些 Swift 不能够使用的,例如 OC 中的预处理指令,即宏定义不可使用,虽然在目前 4.1 版本下,已经开始支持了少量的宏,如

#if DEBUG
#else
#endif

这种简单的预处理指令。Swift 中推荐使用常量定义,定义一些全局的常量实例、全局方法来充当 宏 的作用。

Swift 中的一些自定义基类可以是不继承自 NSObject,这是 OC 调用这类的的时刻,就需要使用关键自 @objc 来标记,这样就可以正常使用来。

Swift 中还有很多小细节部分,待大家在进一步学习过程中慢慢体会。

Objc 调用 Swift

由于国内大部分还都是 OC 编程环境,所以通常是先学习 OC 下调用 Swift。接下来我演示一下 OC 下配置桥接文件,以及 Objc 和 Swift 的相互调用。

当我们在项目中第一次添加不同语言的文件时,Xcode就会询问你是否创建桥接文件,你只需要点击"YES",Xcode就可以帮你完成桥接配置(注意:第二次不会提示你,即使你删除来桥接文件)。

桥接文件

这份桥接文件是 Swift 调用 OC 文件的时,导入 OC 头文件使用的。在桥接文件中你可看到以下内容。

//  Use this file to import your target's public headers that you would like to expose to Swift.

当然你也可以手动创建桥接文件,自己配置环境。

我们可以创建:项目名称 -> Bridging -> Header 一个 .h 文件作为桥接文件,然后为项目配置此文件的路径。

配置文件

此文件是存放 Swift 使用的 OC 头文件,此小节先不管它。

在 OC 调用 Swift 情况下,是通过Swift生成的一个头文件实现的,这个头文件是编译器自动完成的,它会将你在项目是到的 Swift 文件自动映射称 OC 语法,其他并不需要开发者关注,该文件名称为 项目名称-Swift,你可以在 Build Settings 里搜索 Swift 可以看到:

Swift配置环境

这里还可以配置其他 Swift 的选项,如语言版本等。

接下来我们创建一个名为 AViewController.swift 的控制器,我们现在要在 OC 文件使用到该文件,首先,我们导入 项目名称-Swift.h 的文件,不会提示,需要你手动写入(可以按住Command点击头文件查看)。

在 OC 的 “ViewController.h” 文件中,我们进行跳转控制器,这个控制器的使用和使用 OC 的类没有任何区别,系统的 项目名称-Swift.h 文件已经帮我完成了映射。

#import "ViewController.h"
#import "TestDemo-Swift.h"
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"OC页面";
    self.view.backgroundColor = [UIColor whiteColor];
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    AViewController *ctrl = [AViewController new];
    [self.navigationController pushViewController:ctrl animated:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

Swift 调用 Objc

前面提到了 TestDemo-Bridging-Header.h 文件,这个文件中存储的是用于供给 Swift 使用的 OC 头文件。

我们将 OC 控制器文件 ViewController.h 导入该文件,这样我们在 Swift 文件中就可以使用该文件。

import UIKit
class AViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Swift页面";
        self.view.backgroundColor = UIColor.red
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let ctrl = ViewController()
        self.navigationController?.pushViewController(ctrl, animated: true)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

演示

上面介绍了在 OC 工程中创建 Swift 文件,其实在 Swift 工程中创建 OC 文件是类似的,大家可以尝试一下。

Swift中的宏和工具类

通过前面的学习,我们已经对 Swift 的语法,和 OC 进行混编等知识有了一定的了解,下面演示一些在项目中可能使用到的工具类。

我们知道,在目前的 Swift 中,还不能够调用 OC 中的宏定义,这些东西势必需要我们重新写过。在 Swift 中,所对应 OC 的宏,只不过是一些全局常量或者方法。下面列举一些简单的

// MARK: -------------------------- 适配 --------------------------
/// 屏幕宽度
let kScreenHeight = UIScreen.main.bounds.height
/// 屏幕高度
let kScreenWidth = UIScreen.main.bounds.width

/// 状态栏高度
let kStatusBarHeight = UIApplication.shared.statusBarFrame.size.height
/// 导航栏高度
let kNavigationbarHeight = (kStatusBarHeight+44.0)
/// Tab高度
let kTabBarHeight = kStatusBarHeight > 20 ? 83 : 49

/// 屏幕比率
let kScreenWidthRatio = (kScreenWidth/375.0)
/// 调整
func AdaptedValue(x:Float) -> Float {
    return Float(CGFloat(x) * kScreenWidthRatio)
}

/// 调整字体大小
func AdaptedFontSizeValue(x:Float) -> Float {
    return (x) * Float(kScreenWidthRatio)
}

// MARK: -------------------------- 颜色 --------------------------
// MARK: 请参考 UIColor_Extentsion.swift 文件

// MARK: -------------------------- 偏好 --------------------------
func SaveInfoForKey(_ value:String ,_ Key:String) {
    UserDefaults.standard.set(value, forKey: Key)
    UserDefaults.standard.synchronize()
}
func GetInfoForKey(_ Key:String) -> Any! {
   return UserDefaults.standard.object(forKey: Key)
}
func RemoveObjectForKey(_ Key:String){
    UserDefaults.standard.removeObject(forKey: Key)
    UserDefaults.standard.synchronize()
}

// MARK: -------------------------- 输出 --------------------------
// 带方法名、行数
func printLog<T>(_ message:T,method:String = #function,line:Int = #line){
    print("-[method:\(method)] " + "[line:\(line)] " + "\(message)")
}
func DLog(_ format: String, method:String = #function,line:Int = #line,_ args: CVarArg...){
//    print("-[method:\(method)] " + "[line:\(line)] ", separator: "", terminator: "")
    #if DEBUG
        let va_list = getVaList(args)
        NSLogv(format, va_list)
    #else
    #endif
}
func echo(_ format: String,_ args: CVarArg...) {
    #if DEBUG
        let va_list = getVaList(args)
        NSLogv(format, va_list)
    #else
    #endif
}

使用【扩展】功能创建一些工具

颜色类

extension UIColor {
	// RGB颜色
    static func rgba(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat,_ a:CGFloat) -> UIColor {
        return UIColor.init(red: (r)/255.0, green: (g)/255.0, blue: (b)/255.0, alpha: a)
    }
    static func rgb(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat) -> UIColor {
        return rgba((r), (g), (b), 1)
    }
    /// 十六进制的色值 例如:0xfff000
    static func hex(_ value:Int) -> UIColor{
        return rgb(CGFloat((value & 0xFF0000) >> 16),
                   CGFloat((value & 0x00FF00) >> 8),
                   CGFloat((value & 0x0000FF)))
    }
    /// 十六进制的色值,例如:“#FF0000”,“0xFF0000”
    static func hexString(_ value:String) -> UIColor{
        var cString:String = value.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        cString = cString.lowercased()
        if cString.hasPrefix("#") {
            cString = cString.substring(from: cString.index(after: cString.startIndex))
        }
        if cString.hasPrefix("0x") {
            cString = cString.substring(from: cString.index(cString.startIndex, offsetBy: 2))
        }
        if cString.lengthOfBytes(using: String.Encoding.utf8) != 6 {
            return UIColor.clear
        }
        var startIndex = cString.startIndex
        var endIndex = cString.index(startIndex, offsetBy: 2)
        let rStr = cString[startIndex..<endIndex]
        startIndex = cString.index(startIndex, offsetBy: 2)
        endIndex = cString.index(startIndex, offsetBy: 2)
        let gStr = cString[startIndex..<endIndex]
        startIndex = cString.index(startIndex, offsetBy: 2)
        endIndex = cString.index(startIndex, offsetBy: 2)
        let bStr = cString[startIndex..<endIndex]
        var r :UInt32 = 0x0;
        var g :UInt32 = 0x0;
        var b :UInt32 = 0x0;
        Scanner.init(string: String(rStr)).scanHexInt32(&r);
        Scanner.init(string: String(gStr)).scanHexInt32(&g);
        Scanner.init(string: String(bStr)).scanHexInt32(&b);
        return rgb(CGFloat(r), CGFloat(g), CGFloat(b))
    }
    /// 随机色
    static var rand: UIColor {
        return rgb(CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)))
    }
}

字符转数字类

extension String {
    /// 将字符串转换为合法的数字字符
    ///
    /// - Returns: String类型
    func toPureNumber() -> String {
        let characters:String = "0123456789."
        var originString:String = ""
        for c in self {
            if characters.contains(c){
                if ".".contains(c) && originString.contains(c){}
                else{
                    originString.append(c)
                }
            }
        }
        return originString
    }
    /// 将字符串转换为 Double 类型的数字
    ///
    /// - Returns: Double类型
    func toDouble() -> Double {
        return Double(self.toPureNumber())!
    }
    /// 将字符串转换为 Float 类型的数字
    ///
    /// - Returns: Float类型
    func toFloat() -> Float {
        return Float(self.toDouble())
    }
    /// 将字符串转换为 Int 类型的数字
    ///
    /// - Returns: Int类型
    func toInt() -> Int {
        return Int(self.toDouble())
    }
    
    // 保留旧版本的字符转换为数字类型,例:"123".floatValue
    var pureNumber: String {
        return self.toPureNumber()
    }
    var doubleValue: Double {
        return self.toDouble()
    }
    var floatValue: Float {
        return self.toFloat()
    }
    var intValue: Int {
        return self.toInt()
    }
}

以上介绍了几种工具类的创建方式,相信大家进过基础一节过后,都能看懂。


实战篇

在这一节中,我们利用之前的测试项目简单的演示 Swift 下如何搭建页面、和 OC 进行数据传递,其中会涉及到自定义数据模型、延迟加载、类型转换、协议、协议代理模式、弱引用、闭包、闭包传值、检测释放等等,希望能都帮助到第一次接触 Swift 的小白们。

Demo中,简单搭建一个 Swift 页面,页面中展示一个列表,给列表 Cell 有标题,自标题和一张图片,用户点击某个cell时,将数据回传到上一个OC页面。

定义Model层

首先我们需要要一个自定义模型,以及一个用来获取、处理数据的View-Model。

首先是自定义模型:

import UIKit
// 列表数据源 
class AVTableModel: NSObject {
    var title: String?
    var subTitle: String?
    var image: String?
}

View-Model:

import UIKit

class AVTableManager: NSObject {
    // 定义数据请求,使用闭包回调
    class func requestData(response:@escaping (_ flag : Bool,_ resArray : [AVTableModel]?) -> Void) {
        // 模拟数据
        var resList = [AVTableModel]()
        for i in 0...30 {
	        // 使用自定义模型
            let avtm:AVTableModel = AVTableModel()
            avtm.title = "\(i) row"
            avtm.subTitle = [
                                "月光推开窗亲吻脸颊,沁得芬芳轻叩门,敛去湖中婆娑影,拈起肩上落梨花",
                                "屋檐下的风轻轻拂过了衣角,弄皱了桌上的画卷,月影疏疏,落花朵朵,不经意间,看成了风景",
                                "远处的烟穿过水路,哼着不知名的小调,踏碎了一方的圆月",
                                "谁家的酒酿醉了空气中潮湿的时光,睁眼瞬间,藏进了楼阁"
                            ][i%4]
            avtm.image = ["0.jpg","1.jpg","2.jpg"][i%3]
            resList.append(avtm)
        }
        // 回调数据和当前状态
        response(true, resList)
    }
}

控制器

控制器需要完成UI的搭建,因此需要一个列表,完成相应的列表代理,再者就是定义数据,完成页面交互逻辑。

Swift 中列表是使用和 OC 非常类似,仅仅是方法的实现和调用方面是链式调用。

import UIKit

class AViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {	// 实现列表的代理
    // 数据源,延迟属性
    lazy var dataArray = [AVTableModel]()
    // 列表视图
    @IBOutlet weak var table: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Swift页面";
        // 配置UI
        self.setUpUI()
        // 获取数据
        self.getData()  // 获取数据
    }
    
    // 配置UI
    func setUpUI() {
        self.table.tableFooterView = UIView()
    }
    
    // 获取数据
    func getData() {
        // 这里因为是类方法,没有持有者,不会产生循环引用
        AVTableManager.requestData { (flag, resList) in
            if flag {
                self.dataArray = resList!
                self.table.reloadData()
            }
        }
    }
    
    // tableView的代理
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataArray.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 创建cell,并返回给tableView
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60.5
    }
    
    // 检测释放
    deinit {
        print("\(self.classForCoder) 释放了。。。")
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

视图层

我这里使用的是 xib,使用方式和 OC 下并无多少差异,将一些必要的控件拉到类中做入口。

import UIKit
class AVTableViewCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!      // 标题
    @IBOutlet weak var subTitleLabel: UILabel!   // 子标题
    @IBOutlet weak var imgView: UIImageView!     // 图片
}

在控制器中完成列表 cell 的注册,以及 cell 的赋值

import UIKit

class AViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    // 数据源,延迟属性
    lazy var dataArray = [AVTableModel]()
    // 列表视图
    @IBOutlet weak var table: UITableView!
    let cellId = "cellId"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Swift页面";
        // 配置UI
        self.setUpUI()
        // 获取数据
        self.getData()  // 获取数据
    }
    // 配置UI
    func setUpUI() {
        self.table.tableFooterView = UIView()
        // 注册cell
        self.table.register(UINib.init(nibName: "AVTableViewCell", bundle: nil), forCellReuseIdentifier: cellId)
    }
    // 获取数据
    func getData() {
        // 这里因为是类方法,不会产生循环引用
        AVTableManager.requestData { (flag, resList) in
            if flag {
                self.dataArray = resList!
                self.table.reloadData()
            }
        }
    }
    // tableView的代理
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataArray.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 将UITableViewCell向下转换为AVTableViewCell
        let cell: AVTableViewCell = table.dequeueReusableCell(withIdentifier: cellId) as! AVTableViewCell
        let model = self.dataArray[indexPath.row]
        // 将数据赋值给cell
        cell.titleLabel.text = model.title
        cell.subTitleLabel.text = model.subTitle
        cell.imgView.image = UIImage.init(named: model.image!)
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60.5
    }
    deinit {
        print("\(self.classForCoder) 释放了。。。")
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

当前样式

将数据回传到 OC 页面

这里演示代理和闭包两种方式

首先是代理,和 OC 一样,我们需要先定义一个协议用来约束遵循者完成该协议方法。

import Foundation
@objc protocol AViewControllerDelegate {
    // 默认是必须实现的,这里将其定义为可选类型
    @objc optional func aViewCtrlData(model: AVTableModel) -> Void
}

那么在控制器中,代理和闭包的声明就可以想下面那样

// 代理,将数据传递回其他类,可选类型
weak var delegate: AViewControllerDelegate?
// 闭包,将第一条数据传递回其他类, 该闭包和代理一样是可选类型
var returnModelBlock: ((_ model: AVTableModel) ->Void)?

我们在用户点击事件中将对应数据回传到 OC 页面中去

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        // 我们将前十项的数据使用代理方法回传,剩余的数据使用闭包方式回传
        if indexPath.row > 10 {
            // 将数据通过代理回传给 OC 页面
            let model = self.dataArray[indexPath.row]
            self.delegate?.aViewCtrlData!(model: model)
        }
        else {
            // 将数据回传给其他类
            if returnModelBlock != nil {
                returnModelBlock!(self.dataArray.first!)
            }
        }
        
        // 返回
        self.navigationController?.popViewController(animated: true)
    }

在 OC 页面中,使用 Swift 页面中的block和代理和平常没有什么区别

AViewController *ctrl = [AViewController new];
ctrl.delegate = self;
// 这里并没有循环引用,因为self并没有持有ctrl,ctrl仅是被self.navigationController所持有
ctrl.returnModelBlock = ^(AVTableModel * model) {
        [self aViewCtrlDataWithModel:model];
};
[self.navigationController pushViewController:ctrl animated:YES];

演示:

演示

注:由于 OC 和 Swift 混编是由桥接文件实现桥接的,因此并不是你写完一个 public 属性在 OC 中就能够使用的,在使用前,你应该手动 command + B 进行编译完成桥接文件的内容。你可以使用 command + 点击TestDemo-Swift.h文件 进入桥接文件查看桥接内容。

桥接内容

总结

本文第一部分内容较长,但也仅仅列出了 Swift 中非常常用而又重要的部分,有关其他相关的细节部分,依旧需要在后期的学习中查漏补缺。第二部介绍了混编的使用,在目前的行情下,需要混编的请还是非常常见的,但是混编时,应用的安装包会较大,相比较纯 OC 或者 纯 Swift 要大出很多(希望苹果后期能优化此部分),如果对安装包有现只要求,最好不要轻易尝试混编。

参考

鸣谢

2020-03-09 19:00:00 olsQ93038o99S 阅读数 170

作者丨zhangferry

来源丨iOS成长之路

运行环境:Xcode 11.1 Swift5.0

最近参与的一个项目需要从Objective-C(以下简称OC)转到Swift,期间遇到了一些坑,于是有了这篇总结性的文档。如果你也有将OC项目Swift化的需求,可以作为参考。

OC转Swift有一个大前提就是你要对Swift有一定的了解,熟悉Swift语法,最好是完整看过一遍官方的Language Guide。

转换的过程分自动化和手动转译,鉴于自动化工具的识别率不能让人满意,大部分情况都是需要手动转换的。

自动化工具

有一个比较好的自动化工具Swiftify,可以将OC文件甚至OC工程整个转成Swift,号称准确率能达到90%。我试用了一些免费版中的功能,但感觉效果并不理想,因为没有使用过付费版,所以也不好评价它就是不好。

Swiftify还有一个Xcode的插件Swiftify for Xcode,可以实现对选中代码和单文件的转化。这个插件还挺不错,对纯系统代码转化还算精确,但部分代码还存在一些识别问题,需要手动再修改。

手动Swift化

桥接文件

如果你是在项目中首次使用Swift代码,在添加Swift文件时,Xcode会提示你添加一个 .h的桥接文件。如果不小心点了不添加还可以手动导入,就是自己手动生成一个 .h文件,然后在 BuildSettings>SwiftCompiler-General>Objective-CBridgingHeader中填入该 .h文件的路径。

这个桥接文件的作用就是供Swift代码引用OC代码,或者OC的三方库。

#import "Utility.h"
#import <Masonry/Masonry.h>

BridgingHeader的下面还有一个配置项是 Objective-CGeneratedInterfaceHeaderName,对应的值是 ProjectName-Swift.h。这是由Xcode自动生成的一个隐藏头文件,每次Build的过程会将Swift代码中声明为外接调用的部分转成OC代码,OC部分的文件会类似 pch一样全局引用这个头文件。因为是Build过程中生成的,所以只有 .m文件中可以直接引用,对于在 .h文件中的引用下文有介绍。

Appdelegate(程序入口)

Swift中没有 main.m文件,取而代之的是 @UIApplicationMain命令,该命令等效于原有的执行 main.m。所以我们可以把 main.m文件进行移除。

系统API

对于 UIKit框架中的大部分代码转换可以直接查看系统API文档进行转换,这里就不过多介绍。

property(属性)

Swift没有 property,也没有 copynonatomic等属性修饰词,只有表示属性是否可变的 letvar

注意点一OC中一个类分 .h.m两个文件,分别表示用于暴露给外接的方法,变量和仅供内部使用的方法变量。迁移到Swift时,应该将 .m中的property标为 private,即外接无法直接访问,对于 .h中的property不做处理,取默认的 internal,即同模块可访问。

对于函数的迁移也是相同的。

注意点二有一种特殊情况是在OC项目中,某些属性在内部( .m)可变,外部( .h)只读。这种情况可以这么处理:

private(set) var value: String

就是只对 valueset方法就行 private标记。

注意点三Swift中针对空类型有个专门的符号 ?,对应OC中的 nil。OC中没有这个符号,但是可以通过在 nullablenonnull表示该种属性,方法参数或者返回值是否可以空。

如果OC中没有声明一个属性是否可以为空,那就去默认值 nonnull

如果我们想让一个类的所有属性,函数返回值都是 nonnull,除了手动一个个添加之外还有一个宏命令。

NS_ASSUME_NONNULL_BEGIN
/* code */
NS_ASSUME_NONNULL_END

enum(枚举)

OC代码:

typedef NS_ENUM(NSInteger, PlayerState) {
PlayerStateNone= 0,
PlayerStatePlaying,
PlayerStatePause,
PlayerStateBuffer,
PlayerStateFailed,
};
typedef NS_OPTIONS(NSUInteger, XXViewAnimationOptions) {
XXViewAnimationOptionNone= 1<<  0,
XXViewAnimationOptionSelcted1= 1<<  1,
XXViewAnimationOptionSelcted2= 1<<  2,
}

Swift代码

enumPlayerState: Int{
case none = 0
case playing
case pause
case buffer
case failed
}
structViewAnimationOptions: OptionSet{
let rawValue: UInt
staticletNone= ViewAnimationOptions(rawValue: 1<<0)
staticletSelected1= ViewAnimationOptions(rawValue: 1<<0)
staticletSelected2= ViewAnimationOptions(rawValue: 1<< 2)
//...
}

Swift没有 NS_OPTIONS的概念,取而代之的是为了满足 OptionSet协议的 struct类型。

懒加载

OC代码:

- (MTObject*)object{
if(!_object) {
        _object = [MTObjectnew];
}
return _object;
}

Swift代码:

lazy varobject: MTObject= {
letobject= MTObject()
return imagobjecteView
}()

闭包

OC代码:

typedefvoid(^DownloadStateBlock)(BOOL isComplete);

Swift代码:

typealias DownloadStateBlock= ((_ isComplete: Bool) -> Void)

单例

OC代码:

+ (XXManager*)shareInstance {
staticdispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
});
return instance;
}

Swift对单例的实现比较简单,有两种方式:

第一种

swiftletshared=XXManager()// 声明在全局命名区(global namespace)ClassXXManager{}

你可能会疑惑,为什么没有 dispatch_once,如何保证多线程下创建的唯一性?其实是这样的,Swift中全局变量是懒加载,在AppDelegate中被初始化,之后所有的调用都会使用该实例。而且全局变量的初始化是默认使用 dispatch_once的,这保证了全局变量的构造器(initializer)只会被调用一次,保证了 shard原子性

第二种

ClassXXManager{
staticlet shared = XXManager()
privateoverride init() {
// do something
}
}

Swift 2 开始增加了 static关键字,用于限定变量的作用域。如果不使用 static,那么每一个 shared都会对应一个实例。而使用 static之后, shared成为全局变量,就成了跟上面第一种方式原理一致。可以注意到,由于构造器使用了 private 关键字,所以也保证了单例的原子性。

初始化方法和析构函数

对于初始化方法OC先调用父类的初始化方法,然后初始自己的成员变量。Swift先初始化自己的成员变量,然后在调用父类的初始化方法。

OC代码:

// 初始化方法
@interfaceMainView: UIView
@property(nonatomic, strong) NSString*title;
- (instancetype)initWithFrame:(CGRect)frame title:(NSString*)title NS_DESIGNATED_INITIALIZER;
@end
@implementationMainView
- (instancetype)initWithFrame:(CGRect)frame title:(NSString*)title {
if(self= [super initWithFrame:frame]) {
self.title = title;
}
returnself;
}
@end
// 析构函数
- (void)dealloc {
//dealloc
}

Swift代码:

classMainViewSwift: UIView{
let title: String
    init(frame: CGRect, title: String) {
self.title = title
super.init(frame: frame)
}
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
}
        deinit {
//deinit
}
}

函数调用

OC代码:

// 实例函数(共有方法)
- (void)configModelWith:(XXModel*)model {}
// 实例函数(私有方法)
- (void)calculateProgress {}
// 类函数
+ (void)configModelWith:(XXModel*)model {}

Swift代码

// 实例函数(共有方法)
func configModel(with model: XXModel) {}
// 实例函数(私有方法)
private func calculateProgress() {}
// 类函数(不可以被子类重写)
static func configModel(with model: XXModel) {}
// 类函数(可以被子类重写)
class func configModel(with model: XXModel) {}
// 类函数(不可以被子类重写)
classfinal func configModel(with model: XXModel) {}

OC可以通过是否将方法声明在 .h文件表明该方法是否为私有方法。Swift中没有了 .h文件,对于方法的权限控制是通过权限关键词进行的,各关键词权限大小为:private<fileprivate<internal<public<open

其中 internal为默认权限,可以在同一 module下访问。

NSNotification(通知)

OC代码:

// add observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method) name:@"NotificationName"object:nil];
// post
[NSNotificationCenter.defaultCenter postNotificationName:@"NotificationName"object:nil];

Swift代码:

// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: NSNotification.Name(rawValue: "NotificationName"), object: nil)
// post
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NotificationName"), object: self)

可以注意到,Swift中通知中心 NotificationCenter不带 NS前缀,通知名由字符串变成了 NSNotification.Name的结构体。

改成结构体的目的就是为了便于管理字符串,原本的字符串类型变成了指定的 NSNotification.Name类型。上面的Swift代码可以修改为:

extension NSNotification.Name{
staticletNotificationName= NSNotification.Name("NotificationName")
}
// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: .NotificationName, object: nil)
// post
NotificationCenter.default.post(name: .NotificationName, object: self)

protocol(协议/代理)

OC代码:

@protocolXXManagerDelegate<NSObject>
- (void)downloadFileFailed:(NSError*)error;
@optional
- (void)downloadFileComplete;
@end
@interfaceXXManager: NSObject
@property(nonatomic, weak) id<XXManagerDelegate> delegate;
@end

Swift中对 protocol的使用拓宽了许多,不光是 class对象, structenum也都可以实现协议。需要注意的是 structenum为指引用类型,不能使用 weak修饰。只有指定当前代理只支持类对象,才能使用 weak。将上面的代码转成对应的Swift代码,就是:

@objc protocol XXManagerDelegate{
    func downloadFailFailed(error: Error)
@objc optional func downloadFileComplete() // 可选协议的实现
}
classXXManager: NSObject{
    weak vardelegate: XXManagerDelegate?
}

@objc是表明当前代码是针对 NSObject对象,也就是 class对象,就可以正常使用weak了。

如果不是针对NSObject对象的delegate,仅仅是普通的class对象可以这样设置代理:

protocol XXManagerDelegate: class{
    func downloadFailFailed(error: Error)
}
classXXManager{
    weak vardelegate: XXManagerDelegate?
}

值得注意的是,仅 @objc标记的 protocol可以使用 @optional

Swift和OC混编注意事项

函数名的变化

如果你在一个Swift类里定义了一个delegate方法:

@objc protocol MarkButtonDelegate{
    func clickBtn(title: String)
}

如果你要在OC中实现这个协议,这时候方法名就变成了:

- (void)clickBtnWithTitle:(NSString*)title {
// code
}

这主要是因为Swift有指定参数标签,OC却没有,所以在由Swift方法名生成OC方法名时编译器会自动加一些修饰词,已使函数作为一个句子可以"通顺"。

在OC的头文件里调用Swift类

如果要在OC的头文件里引用Swift类,因为Swift没有头文件,而为了让在头文件能够识别该Swift类,需要通过 @class的方法引入。

@classSwiftClass;
@interfaceXXOCClass: NSObject
@property(nonatomic, strong) SwiftClass*object;
@end

对OC类在Swift调用下重命名

因为Swift对不同的module都有命名空间,所以Swift类都不需要添加前缀。如果有一个带前缀的OC公共组件,在Swift环境下调用时不得不指定前缀是一件很不优雅的事情,所以苹果添加了一个宏命令 NS_SWIFT_NAME,允许在OC类在Swift环境下的重命名:

NS_SWIFT_NAME(LoginManager)
@interfaceXXLoginManager: NSObject
@end

这样我们就将 XXLoginManager在Swift环境下的类名改为了 LoginManager

引用类型和值类型

  • struct 和 enum 是值类型,类 class 是引用类型。

  • String, Array和 Dictionary都是结构体,因此赋值直接是拷贝,而 NSStringNSArray 和 NSDictionary则是类,所以是使用引用的方式。

  • struct 比 class 更“轻量级”, struct 分配在栈中, class 分配在堆中。

id类型和AnyObject

OC中 id类型被Swift调用时会自动转成 AnyObject,他们很相似,但却其实概念并不一致。Swift中还有一个概念是 Any,他们三者的区别是:

  • id 是一种通用的对象类型,它可以指向属于任何类的对象,在OC中即是可以代表所有继承于 NSObject的对象。

  • AnyObject可以代表任何 class类型的实例。

  • Any可以代表任何类型,甚至包括 func类型。

从范围大小比较就是:id<AnyObject<Any

其他语法区别及注意事项(待补充)

1、Swift语句中不需要加分号 ;

2、关于Bool类型更加严格,Swift不再是OC中的非0就是真,真假只对应 truefalse

3、Swift类内一般不需要写 self,但是闭包内是需要写的。

4、Swift是强类型语言,必须要指定明确的类型。在Swift中 IntFloat是不能直接做运算的,必须要将他们转成同一类型才可以运算。

5、Swift抛弃了传统的 ++--运算,抛弃了传统的C语言式的 for循环写法,而改为 for-in

6、Swift的 switch操作,不需要在每个case语句结束的时候都添加 break

7、Swift对 enum的使用做了很大的扩展,可以支持任意类型,而OC枚举仅支持 Int类型,如果要写兼容代码,要选择Int型枚举。

8、Swift代码要想被OC调用,需要在属性和方法名前面加上 @objc

9、Swift独有的特性,如泛型, struct,非Int型的 enum等被包含才函数参数中,即使添加 @objc也不会被编译器通过。

10、Swift支持重载,OC不支持。

11、带默认值的Swift函数再被OC调用时会自动展开。

语法检查

对于OC转Swift之后的语法变化还有很多细节值得注意,特别是对于初次使用Swift这门语言的同学,很容易遗漏或者待着OC的思想去写代码。这里推荐一个语法检查的框架SwiftLint,可以自动化的检查我们的代码是否符合Swift规范。

可以通过 cocoapods进行引入,配置好之后,每次 Build的过程,Lint脚本都会执行一遍Swift代码的语法检查操作,Lint还会将代码规范进行分级,严重的代码错误会直接报错,导致程序无法启动,不太严重的会显示代码警告(⚠️)。

如果你感觉SwiftLint有点过于严格了,还可以通过修改 .swiftlint.yml文件,自定义属于自己的语法规范。

近期精彩内容推荐:  

 有个程序媛上司是什么体验

 一个天才程序员的黑帮大佬人生

 200行Python代码做一个换脸程序

 在 IntelliJ IDEA 中使用 Git,太方便了!

在看点这里好文分享给更多人↓↓

2017-04-12 14:35:21 cordova 阅读数 1459

OC和Swift之间的互调很简单,iOS8以后官方给出了这两种语言之间的桥接方案,简单说就是在Swift工程中,通过提示创建的Bridging头文件可以将OC文件和Swift文件衔接在一起,从而可以在OC文件中引用Swift类,或者在Swift文件中引用OC的类。应用较多的主要是在Swift中调用OC类,使得在最新的swift工程中仍然能够兼容使用以前的OC类库等。

这里通过一个简单的例子展示在Swift工程中OC和Swift之间的互调:

首先建立一个Swift工程,这里创建了一个Single View Application,工程名为SwiftBridge,并分别新建了一个新的名为SwiftClass的swift类文件和一个名为OCClass的OC类文件,创建第一个OC文件后会提示创建一个Bridging头文件,创建后就可以对两者进行桥接:

这里我们在ViewController.swift中调用OCClass类,同时在OCClass类中调用SwiftClass.swift。其中在swift中引用OC类时要在Bridging头文件中引入OC类的头文件(swift文件中没有头文件及其引用),而在OC中引用swift类时直接引入“SwiftBridge-swift.h”即可,不需要在Bridging头文件设置,其中“SwiftBridge”为工程名,该文件是隐藏的,对工程中所有swift类文件进行了向OC语言的翻译,从而在OC文件中可以像调用其他OC文件一样调用工程中的swift类文件。

  1. 在SwiftClass.swift中定义一个实例方法和一个类方法:
import UIKit

class SwiftClass: NSObject {
    func SwiftInstanceMethod() -> Void {
        print("swift instance method!");
    }
    class func SwiftClassMethod() -> Void {
        print("swift class method!");
    }
}
  1. 在OCClass类中调用SwiftClass:
/* OCClass.h */
#import <Foundation/Foundation.h>

@interface OCClass : NSObject
- (void)OCInstanceMethod;
+ (void)OCClassMethod;
@end

/* OCClass.m */
#import "OCClass.h"
#import "SwiftBridge-swift.h" /* 引入swift类头文件 */

@implementation OCClass
- (void)OCInstanceMethod {
    /* 调用swift实例方法 */
    SwiftClass *swiftc = [[SwiftClass alloc] init];
    [swiftc SwiftInstanceMethod];
    NSLog(@"oc instance method!");
}
+ (void)OCClassMethod {
    /* 调用swift类方法 */
    [SwiftClass SwiftClassMethod];
    NSLog(@"oc class method!");
}
@end
  1. 在Bridging头文件引入OC类头文件供swift调用:
/* SwiftBridge-swift.h */
#import "OCClass.h"
  1. 在ViewController.swift中调用OCClass类:
/* ViewController.swift */
import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let occ = OCClass()
        occ.ocInstanceMethod()
        OCClass.ocClassMethod()
    }
}

打印结果:

swift instance method!
2017-04-12 12:40:39.657 SwiftBridge[3773:5420107] oc instance method!
swift class method!
2017-04-12 12:40:39.657 SwiftBridge[3773:5420107] oc class method!

另外类似的也可以实现swift和C语言之间的互调。

2017-07-10 11:16:59 polarGW 阅读数 869

随着swift趋向于稳定,越来越多的公司启动的新项目也想使用swift过把瘾,使用swift的好处与弊端不再赘述。目前版本是swift3.0,而且Xcode9beta版中已经更新到swift4.0。使用swift开发是大趋势,这里从以下几个方面看一下如何进行简单的配置。

  • OC中引用Swift文件
  • Swift中引用OC文件,以及第三方库
  • Xcode8以后编译swift2.x
  • OC引用Swift第三方库

一、OC中如何引用Swift文件:
首先先来创建一个object-c语言的项目,然后直接一个swift类,这时会弹出如下提示:
配置桥接头文件
先选择Do not Create,稍后会提到,系统会自动创建一个格式:xxx项目名-Swift.h的头文件,不显示但是可以直接引用,直接引用,这时发现报错了。
报错了
这是为什么呢?虽然一般情况下是直接引用xxx项目名-Swift.h,真正需要引用的是Product Moudle Name对应的文件名:
模块名配置
也可在此基础上搜索swift,对应的文件名正是想要的文件名,为什么上面还要提搜索pack找Product Moudle Name直接搜swift不就好了吗?因为这个名字是拼出来的:Product Moudle Name-Swift.h,可以去pack里面删除Product Moudle Name对应的内容,再回来搜swift看一下就明白了。
搜索swift
项目在创建的时候名称有的时候可能有歧义系统自动帮我们修改。swift在OC项目中是作为Moudle来使用的,名称继承自PROJECT下的Product Moudle Name,导入时报错请自行检查是否匹配。
目前为止导入头文件成功了,调用一下SwiftTest类中的log方法。
方法配置
却发现一直报未定义该类的错误:
未定义报错
点击生成的项目名-Swift.h的头文件里面却发现没有关于Swift声明:
未声明该类
后来经过查找发现是因为没有创建桥接文件导致的,手动来创建一下桥接文件,命名规则:项目名-Bridging-Header。
创建桥接文件
到项目里手动配置文件路径:
配置方法
配置完成后clean然后build点击项目名-Swift.h进入就可以发现定义swift类的声明,Command + R 运行成功。
二、Swift中引用OC文件,以及第三方库
swift引用OC文件比较简单:
1)创建桥接文件(参考上面步骤)
2)把需要用到类或者第三方SD、framework头文件引入
3)直接在swift文件中使用
由于比较简单不在赘述,稍后会提到注意事项。
三、Xcode8以后编译swift2.x以及pod引用的swift框架
Xcode8以后swift的currentVersion改为3.0,如果是swift2.x项目打开往往会有这样的提示:
转换提示
如果你想转换的直接点击convert,但是要注意备份小心一万点爆红,如果点击了later之后又想转换了可以进行如下操作:
手动转换
如果想要继续编译swift2.x需要设置版本:
设置版本
如果没有使用第三方框架那么现在就可以build了,而且是成功的,但是如果使用了第三方的framework,那么每个framework都需要设置一次,如下:
设置pod的版本
这里几点需要注意的:
1)pod里面的framework的版本必须与taget里面的一致否则编译失败。
2)必须与最低版本一致有一个framework使用了swift2.x其他都必须设置为YES
3)任何时候点击convertToCurrentVersion都是转换到最新版本也就是当前的swift3.0,即使你设置了使用以前版本再次进行转换也是转换到swift3.0不会转换到以前版本。
4)每次pod以后之前的配置都会重置,需要从新设置版本。但是可以在Podfile里面加入如下代码,将会对所有提交的framework进行设置:

post_install do |installer|
  installer.pods_project.targets.each do |target|
  target.build_configurations.each do |config|
  config.build_settings['SWIFT_VERSION'] = '3.0'
  end
 end
end

如下图:
pod设置
四、OC引用Swift的framework:
配置方法跟pod配置版本号方法相同,设置完版本号后在需要的文件中引用:#import <framework/framework-Swift.h>】也可以通过引入模块的方式:@import framework;这两种方式效果相同。
注意:很多swift三方库,并不支持OC使用,而绝大部分OC库支持swift,是向下兼容的,OC与Swift混编的注意事项,稍后补充。
ps:文章有点啰嗦,本人系新手,主要是记录给自己看。

2015-07-09 16:02:25 z7222946 阅读数 2529

虽然说swift是基于OC的,但是swift和OC还是有很多去别的,今天我们来说一下枚举变量

下面是某个免费短信验证码的验证方法的返回状态

enum SMS_ResponseState
{
    SMS_ResponseStateFail = 0,
    SMS_ResponseStateSuccess=1
};
这是一个正常的OC枚举写法,但是如果你在swift里面调用这个写在OC里面的枚举想进行操作的话,那么对不起,你会收到各种错误提示

比如你不能这样

if verifyState == SMS_ResponseState.Success

会报下面的错误(这个绝不是.Success调用错的愿意)

'SMS_ResponseState.Type' does not have a member named 'Success'
你也不能这样

<span style="font-size:12px;">if verifyState == 1</span>
会报下面的错误

Binary operator '==' cannot be applied to operands of type 'SMS_ResponseState' and 'Int'

但是下面的方法可以进行调用,但是我没有找到下一步进行操作的方法,有人研究出来还望不吝指教

let state = SMS_ResponseState(1)
同时你也不能这样去写

let state1 = SMS_ResponseState(0)
let state2 = SMS_ResponseState(1)
if state1 == state2 {
}

具体为什么会这样,我也不知道原因,如果哪位大神研究出来了,希望能分享一下,最终我的解决方法是只能修改枚举的写法,比如下面的写法

typedef NS_OPTIONS(NSUInteger, SMS_ResponseState){swift
    SMS_ResponseStateFail = 0,
    SMS_ResponseStateSuccess=1
};



OC和Swift中的static

阅读数 1696