源码

首页 » 归档 » 源码 » iOS 快速从OC过渡到Swift,由理论到实战-Swift基础

iOS 快速从OC过渡到Swift,由理论到实战-Swift基础

引言

本文旨在帮助开发者快速从OC开发过渡到Swift开发,挑选了一些比较浅显的但是比较常用的Swift语法特性,在介绍的过程中,通常会拿OC中的语言特性作比较,让大家更好的注意到Swift的不同。 

另外需要说明的是,笔者也仅仅是刚刚接触Swift不久,如果有说的不对的地方,还望指正,这里贴出Swift中文翻译地址,方便大家可以深入了解Swift。

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基础

1、常量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

2、输出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

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

a. 布尔值

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

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")
}

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

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

b. 数组(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,valuein a.enumerated(){    // (,)表示元组类型
    print("index=/(index),element=/(value)")
}

c. 字典(Dictionary)

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

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

0

字典的一些常用操作

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

1

d. 元组

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

Swift的元组使用圆括号’()’创建,不能省略,这点和Python不同

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

2

应用举例:实现交换

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

3

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

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

e. 可选类型

可选类型:有值,为x 或者 没有值。 

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

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

4

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

e1. 声明一个可选类型

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

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

5

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

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

6

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

e2. 可选绑定 

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

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

7

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

e3. 隐式解析可选类型

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

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

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

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

8

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

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

f. Any、AnyObject和AnyClass

Any : 表示任意类型,Any? 还包括了 nil 

AnyObject : 代表任务 class 类型,无论 class 是否谁的子类又或者是基类 

AnyClass : AnyObject的别名,和AnyObject一样

4、几种运算符

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

a. 区间运算符

区间运算符通常用于整形或者范围。区间运算符有两种: ‘..<’ 和 ‘…’ ,前者为CountableRange,后者为类型ClosedRange

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

9

b. 空合运算符

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

let/var name: type = value

0

c. 溢出运算符

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

let/var name: type = value

1

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

let/var name: type = value

2

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

let/var name: type = value

3

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

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

5、控制语句

Swift提供了多种控制流语句,包括while循环、if、guard、switch、跳转break、continue等等。在Swift中,for-in循环可以更简单的遍历数组、字段、区间、字符等序列类型,不同于C和OC,Swift在新版本中取消了C的for条件循环,即for var i=0;i

我们这里介绍提前退出guard

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

let/var name: type = value

4

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

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

6、函数和闭包

a. 函数

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

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

let/var name: type = value

5

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

let/var name: type = value

6

a.1. 函数类型

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

a.1.1 函数类型作为常量

let/var name: type = value

7

a.1.2 函数类型作为参数

let/var name: type = value

8

a.1.3 函数类型作为返回值

let/var name: type = value

9

a.2 嵌套函数

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

0

b. 闭包

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

闭包可以捕获和存储其所在上下文中的任意常量和变量的引用。在上面介绍的全局函数和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一: 

  1. 全局函数是一个有名字但不会捕获任何值的闭包 

  2. 嵌套函数是一个有名字并可以捕获其封闭函数作用域内值的闭包 

  3. 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

b.1 闭包表达式

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

1

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

b.1.1 实现一个内联闭包

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

2

b.1.2 根据上下文推断类型

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

3

b.1.3 单表达式闭包隐式返回

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

4

b.1.4 参数名缩写

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

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

5

b.2 尾随闭包

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

b.3 逃逸闭包

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

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

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

6

7、类和结构体

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

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

  1. 类可以继承

  2. 类型转换允许在运行时检查和解释一个实例的类型

  3. 析构器允许一个实例释放任何其所被分配的资源

  4. 类是引用类型,而结构体是值类型

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

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

7

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

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

8

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

  1. 子类的指定构造方法必须调用父类构造方法,并确保调用发生在子类存储属性初始化之后,而且指定构造方法不能调用同一个类中的其他指定构造方法

  2. 构造便利构造方法必须调用同一个类中的其他指定构造方法(可以是指定构造方法或者便利构造方法),不能直接调用父类构造方法(用以保证最终以指定方法结束)

  3. 如果父类仅有一个无参数构造方法(不管是否包含便利构造方法),子类的构造方法默认就会自动调用父类的无参构造方法(这种情况下可以不用手动调用)

  4. 常量属性必须默认指定初始值或者在当前类的构造方法中初始化,不能在子类构造方法中初始化

关于类和结构体的选择

在开发中,我们可以使用类和结构体来自定义数据类型,但是在两种数据类型选择的上需要考虑不同的业务情况因为结构体实例总是通过值传递,类实例总是通过引用传递。 

按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:

  1. 该数据结构的主要目的是用来封装少量相关简单数据值

  2. 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用

  3. 该数据结构中存储的值类型属性,也应该被拷贝,而不是引用

  4. 该数据结构不需要去继承另一个既有类型的属性或行为

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

  1. 几何形状的大小:封装一个width属性和height属性

  2. 一定范围的路径:封装一个start属性和length属性

  3. 三维坐标系内的一点:封装x,y和z属性

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

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

8、属性

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

属性将值和特定的类、结构、枚举关联。属性有存储属性和计算属性,前者存储常量或者变量,只能用于类和结构体,后者计算一个值,不仅可以用于类和结构体,还可以是枚举。 

另外,我们可以使用属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自定义存储属性,也可以添加到继承父类的属性上。

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

a. 计算属性

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

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

9

b. 延迟属性(懒加载)

OC中的懒加载在Swift中使用延迟属性完成,使用关键字 lazy 来标记。 

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

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

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

0

c. 属性观察器

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

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

  • didSet 在新值被设置之后调用,并可以获取到 oldValye。

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

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

1

d. 类型属性

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

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

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

9、枚举

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

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

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

a. 枚举语法

使用 enum 关键字来创建枚举

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

2

示例

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

3

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

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

4

b. 关联值

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

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

5

10、扩展

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

  1. 添加计算型实例或类型属性

  2. 定义实例方法和类型方法

  3. 提供新的构造器

  4. 定义下标

  5. 定义和使用新的嵌套型

  6. 实现已知类型的某个协议方法

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

a. 扩展的语法

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

6

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

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

7

使用

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

8

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

11、协议

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

a. 协议语法

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

9

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

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

0

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

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

1

示例

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

2

b. 可选协议

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

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

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

3

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

12、循环引用

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

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

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

a. 弱引用(weak)

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

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

4

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

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

5

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

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

6

b. 无主引用(unowned)

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

注:

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

b.1 闭包中的循环引用

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

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

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

7

c. 弱引用和无主引用的选择

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

13、类型转换

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

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

a. is 检查类型,as 向下转型

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

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

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

b. 实例对象向下转换

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

8

c. Any 和 AnyObject 的类型转换

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

  1. Any 可以表示任何类型,包括函数类型

  2. AnyObject 可以表示任何类类型的实例

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

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

9

声明

上述部分内容参考自: 

Swift 4.0 教程 - Swift编程 

Kenshin Cui’s Blog

作者:LOLITA0164

链接:https://blog.csdn.net/LOLITA0164/article/details/82017800

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/ios-kuaisucongocguodudaoswiftyoulilundaoshizhan-swiftjichu.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:9月 19, 2018 at 10:33 下午

热评文章

发表评论

[必填]

看不清?

提交后请等待三秒以免造成未提交成功和重复