您的位置:首页 - 教程 - Swift - 正文
swift学习笔记1——基础部分

之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习、总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询用。详细可以参考the-swift-programming-language-in-chinese,或者苹果官方英文版文档

当前版本是swift2.2

print函数

函数原型
print(items, separator: String, terminator: String)

参数:

  • items:要打印的变量,或常量
  • separator:多个item参数之间的间隔符
  • terminator:打印的末尾可以增加一个字符串

weak与unowned

在OC中,当__weak指向的对象被释放时,weak指向nil,在swift中也一样,那么weak修饰的变量应该为可选类型。
unowned相当于oc中的_unsafe_unretained,对象释放后后指向一块固定的内存。

  • weak用在:当self对象优先于block调用时
  • unowned:当self对象在block调用时不会被释放

swift中提供了捕获列表更优雅的解决了这个问题

注释

支持 // 注释和块注释/**/,还支持注释嵌套(C语言不支持):

/* 这是第一个多行注释的开头
    /* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */

类型转换

一百万可以两种表示:1000000或1_000_000

let value = 3 + 0.14159 // value最终类型为Double类型,原始值3没有显式声明类型,0.14159默认为Double类型

而下面的例子,则不可以:

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine // 错误

let integerPi = Int(pi)
// integerPi 等于 3,所以被推测为 Int 类型。浮点值会被截断。也就是说4.75会变成4,-3.9会变成-3。

不同类型变量不支持隐式转换.在下面的例子中,常量twoThousand是UInt16类型,然而常量one是UInt8类型。它们不能直接相加,因为它们类型不同。所以要调用UInt16(one)来创建一个新的UInt16数字并用one的值来初始化,然后使用这个新数字来计算:

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是UInt16,可以进行相加。目标常量twoThousandAndOne的类型被推断为UInt16,因为它是两个UInt16值的和。

UInt16(one)是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,UInt16有一个构造器,可以接受一个UInt8类型的值,所以这个构造器可以用现有的UInt8来创建一个新的UInt16。注意,你并不能传入任意类型的值,只能传入UInt16内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值

float和double类型也不能直接相加

类型别名

typealias AudioSample = UInt16

定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:

var maxAmplitudeFound = AudioSample.min // 值为0

元组

把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。

你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:

let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)") // 输出 "The status code is 404"
print("The status message is \(statusMessage)") // 输出 "The status message is Not Found"

如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_)标记:

let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)") // 输出 "The status code is 404"

此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:

print("The status code is \(http404Error.0)") // 输出 "The status code is 404"
print("The status message is \(http404Error.1)") // 输出 "The status message is Not Found"

你可以在定义元组的时候给单个元素命名:

let http200Status = (statusCode: 200, description: "OK")

给元组中的元素命名后,你可以通过名字来获取这些元素的值:

print("The status code is \(http200Status.statusCode)") // 输出 "The status code is 200"
print("The status message is \(http200Status.description)") // 输出 "The status message is OK"

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

可选类型

可选类型不能像普通类型一样使用,比如两个可选类型相加会报错,不只相加,加减乘除等等
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为nil:

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

一个变量或常量被定义为可选类型,那么它就不是原来的类型了,而是可选类型,只有解析后为有值才可以回到原来的类型。

let inta:Int? = 3
let intb:Int! = 4
print(inta + intb)  // 错误,正确应该是inta! + intb
if inta != nil {
    print(inta + intb)  // 错误,正确应该是inta! + intb
}

if let intc = inta {    
    print(intc + intb)  // 可选绑定可以当非可选类型使用
}

隐式解析可选类型

一个可选类型总会有值,这种类型的可选状态被定义为隐式解析可选类型
比如例子中的intb被定义为"隐式解析可选类型",它还是可选类型,可以赋值为nil,也可以可选绑定。
在使用隐式解析可选类型时,不用加!,系统会自动解析,就从可选类型变为它本身的类型,可以把隐式解析可选类型当做一个可以自动解析的可选类型

var intb:Int! = 4 // 隐式解析可选类型
intb = nil  // 赋值为nil
if let intc = intb { // 可选绑定
    print(intc + intb)
}

强制解析

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值,这被称为可选值的强制解析。强制解析一定要确保有值,否则会程序崩溃。

nil

在oc中代表一个空指针(指向不存在对象的指针)。仅仅限于对象
在swift中代表一个可选值的状态为没有值,它是一个确定的值,用来表示值缺失,不仅限于对象

可选绑定

使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量,赋给的常量或者变量的作用域只是在if紧跟的大括号内,赋值失败,赋值成功、失败作为if判断的Bool条件

let string : String? = nil
if let constantName = string { // 这里的string必须为可选类型,不然会报错
    print(constantName)
} else {
    print(constantName)  // 错误: 超出constantName作用域
}

使用断言进行调试

断言会在运行时判断一个逻辑条件是否为true

你可以使用全局assert(_:_file:line:)函数来写一个断言。向这个函数传入一个结果为true或者false的表达式以及一条信息,当表达式的结果为false的时候这条信息会被显示:

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因为 age < 0,所以断言会触发

在这个例子中,只有age >= 0为true的时候,即age的值非负的时候,代码才会继续执行。如果age的值是负数,就像代码中那样,age >= 0为false,断言被触发,终止应用。

使用场景:

  • 整数类型的下标索引被传入一个自定义下标实现,但是下标索引值可能太小或者太大。
  • 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。
  • 一个可选值现在是nil,但是后面的代码运行需要一个非nil值。

运算符

Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(=)不返回值,以防止把想要判断相等运算符(==)的地方写成赋值符导致的错误

算术运算符(+,-,*,/,%等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果

区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(%),Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(a..<b 和 a...b),这方便我们表达一个区间内的数值。

8 % 2.5   // 等于 0.5  3个2.5  还剩余一个0.5

当元组中的值可以比较时,你也可以使用这些运算符来比较它们的大小。例如,因为 Int 和 String 类型的值可以比较,所以类型为 (Int, String) 的元组也可以被比较。相反,Bool 不能被比较,也意味着存有布尔类型的元组不能被比较。

比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:

(1, "zebra") < (2, "apple")   // true,因为 1 小于 2
(3, "apple") < (3, "bird")    // true,因为 3 等于 3,但是 apple 小于 bird
(4, "dog") == (4, "dog")      // true,因为 4 等于 4,dog 等于 dog

注意: Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。

空合运算符

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

注意: 如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值,短路运算符一般有:&&、||,其原理是:当有多个表达式时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值

闭区间运算符

闭区间运算符(a...b)定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b

半开区间运算符

半开区间(a..<b)定义一个从 a 到 b 但不包括 b 的区间

逻辑运算符

逻辑与 或 非运算符的操作数只能是bool类型,不能是整型等等。Swift 逻辑操作符 && 和 || 是左结合的

字符串

定义空字符串

var emptyString = ""               // 空字符串字面量
var anotherEmptyString = String()  // 初始化方法
// 两个字符串均为空并等价。

您可以通过检查其Bool类型的isEmpty属性来判断该字符串是否为空:

if emptyString.isEmpty {
    print("Nothing to see here")
}

字符串是值类型(Strings Are Value Types)

Swift 的String类型是值类型。 如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。 任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作

在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。

另外,通过标明一个Character类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:

let exclamationMark: Character = "!"  // 不能用'',必须双引号,而且必须指定为Character类型,否则会推断为string类型

注意: 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果您正在处理一个长字符串,需要注意characters属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
另外需要注意的是通过characters属性返回的字符数量并不总是与包含相同字符的NSString的length属性相同。NSString的length属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。作为佐证,当一个NSString的length属性被一个Swift的String值访问时,实际上是调用了utf16Count。

字符串索引

每一个String值都有一个关联的索引(index)类型,String.Index,它对应着字符串中的每一个Character的位置。

前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道Character的确定位置,就必须从String开头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。

使用startIndex属性可以获取一个String的第一个Character的索引。使用endIndex属性可以获取最后一个Character的后一个位置的索引。因此,endIndex属性不能作为一个字符串的有效下标。如果String是空串,startIndex和endIndex是相等的。

let greeting = "Guten Tag!"
greeting[greeting.startIndex] // G
greeting[greeting.endIndex.predecessor()]  // !
greeting[greeting.startIndex.successor()]  // u
let index = greeting.startIndex.advancedBy(7) // 这个比较常用,()里面可以是负值
greeting[index]   // a

如果字符串索引越界,则运行时错误

字符串/字符相等

字符串/字符可以用等于操作符(==)和不等于操作符(!=)

如果两个字符串(或者两个字符)的可扩展的字形群集是标???相等的,那就认为它们是相等的。在这个情况下,即使可扩展的字形群集是有不同的 Unicode 标量构成的,只要它们有同样的语言意义和外观,就认为它们标准相等。

相反,英语中的LATIN CAPITAL LETTER A(U+0041,或者A)不等于俄语中的CYRILLIC CAPITAL LETTER A(U+0410,或者A)。两个字符看着是一样的,但却有不同的语言意义

前缀/后缀相等 (Prefix and Suffix Equality)

通过调用字符串的hasPrefix(_:)/hasSuffix(_:)方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个String类型的参数,并返回一个布尔值.

let astring = "123456"
astring.hasPrefix("123") // true
astring.hasSuffix("456") // true

Arrays、Sets和Dictionaries三种集合

Swift 语言提供Arrays、Sets和Dictionaries三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集

Swift 语言中的Arrays、Sets和Dictionaries中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信

对于无序集合 可以使用sort()来进行排序

数组(Arrays)

数组使用有序列表存储同一类型的多个值,可选类型与一般类型不能同一存储(隐式解析可选类型可以当做一般类型使用)。相同的值可以多次出现在一个数组的不同位置中。如果初始化时,所有内容类型一致,择数组中保存的是该类型的内容
如果初始化时,所有内容类型不一致,则数组中保存的是 NSObject,类型一旦确定则不能改变

var someInts = [Int]() // 定义
someInts = []   // someInts 现在是空数组,但是仍然是 [Int] 类型的。

如果数组定义为let,则数组的内容不能改变,数组本身也不能变。如果是var则两者都可变,另外

创建一个带有默认值的数组

Swift 中的Array类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法

var threeDoubles = [Double](count: 3, repeatedValue:0.0)
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]

通过两个数组相加创建一个数组

我们可以使用加法操作符(+)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:

var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5]

var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

使用加法赋值运算符(+=)也可以直接在数组后面添加一个或多个拥有相同类型的数据项:

shoppingList += ["Baking Powder"]
// shoppingList 现在有四项了
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList 现在有七项了

还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把"Chocolate Spread","Cheese",和"Butter"替换为"Bananas"和 "Apples":

shoppingList[4...6] = ["Bananas", "Apples"]

如果我们只想把数组中的最后一项移除,可以使用removeLast()方法

不可以用下标访问的形式去在数组尾部添加新项。

如果我们同时需要每个数据项的值和索引值,可以使用enumerate()方法来进行数组遍历。enumerate()返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:

for (index, value) in shoppingList.enumerate() {
    print("Item \(String(index + 1)): \(value)")
}

集合(Sets)

集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。

集合类型的哈希值

一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是Int类型的,相等的对象哈希值必须相同,比如a==b,因此必须a.hashValue == b.hashValue。

集合类型的哈希值

一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是Int类型的,相等的对象哈希值必须相同,比如a==b,因此必须a.hashValue == b.hashValue。

var letters = Set<Character>()  // 创建和构造一个空的集合
letters = []  // letters 现在是一个空的 Set, 但是它依然是 Set<Character> 类型
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]

一般操作

一个Set类型不能从数组字面量中被单独推断出来,因此Set类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个Set并且该数组字面量中的所有元素类型相同,那么你无须写出Set的具体类型

var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

你可以通过调用Set的insert(_:)方法来添加一个新元素,你可以通过调用Set的remove(_:)方法去删除一个元素,如果该值是该Set的一个元素则删除该元素并且返回被删除的元素值,否则如果该Set不包含该值,则返回nil。另外,Set中的所有元素可以通过它的removeAll()方法删除。使用contains(_:)方法去检查Set中是否包含一个特定的值,你可以在一个for-in循环中遍历一个Set中的所有值。

集合比较

  • 使用intersect(_:)方法根据两个集合中都包含的值创建的一个新的集合。
  • 使用exclusiveOr(_:)方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
  • 使用union(_:)方法根据两个集合的值创建一个新的集合。
  • 使用subtract(_:)方法根据不在该集合中的值创建一个新的集合。

字典

字典是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序

字典类型快捷语法

Swift 的字典使用Dictionary定义,我们也可以用[Key: Value]这样快捷的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。

和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。

创建一个空字典

我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典:

var namesOfIntegers = [Int: String]()  // namesOfIntegers 是一个空的 [Int: String] 字典

这个例子创建了一个[Int: String]类型的空字典来储存整数的英语命名。它的键是Int型,值是String型。

如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作[:](中括号中放一个冒号):

namesOfIntegers[16] = "sixteen"
// namesOfIntegers 现在包含一个键值对
namesOfIntegers = [:]
// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典

用字典字面量创建字典
我们可以使用字典字面量来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作Dictionary集合的快捷途径。

一个键值对是一个key和一个value的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割:

[key 1: value 1, key 2: value 2, key 3: value 3]

iocoresurfaceroot

控制流

Swift 的switch语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了break,这个 case 就会贯穿至下一个 case,Swift 无需写break,所以不会发生这种贯穿的情况,但你仍然可以在末尾写入break,来正常或提前结束。case 还可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述。switch的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由where分句描述更复杂的匹配条件。

For-In 循环

如果你不需要知道区间序列内每一项的值,你可以使用下划线(_)替代变量名来忽略对值的访问:

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}

repeat-while循环合其他语言中的do-while循环是类似的

switch语句必须是完备的,即必须有default分支,除非是字面常量,否则报错,如下:

switch 10 {
case 10:
    print("10")
}
/// 或者是穷举枚举类型也可以不用default

另外,可以有多个匹配,如果有多个匹配则只执行第一个,break语句是跳出当前循环,或者是跳出switch分支,因此在其它地方使用swift中会报错,而continue只允许在循环里面使用。每个分支必须至少有一条执行语句,包括default分支

如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用fallthrough关键字,将自动执行下一个分支.

标签语句

在 Swift 中,你可以在循环体和switch代码块中嵌套循环体和switch代码块来创造复杂的控制流结构。然而,循环体和switch代码块两者都可以使用break语句来提前结束整个方法体。因此,显式地指明break语句想要终止的是哪个循环体或者switch代码块,会很有用。类似地,如果你有许多嵌套的循环体,显式指明continue语句想要影响哪一个循环体也会非常有用。

labelName: while condition { statements }

gameLoop: while square != finalSquare {
    switch square + diceRoll {
    case finalSquare:
        break gameLoop // 跳出while循环
    case let newSquare where newSquare > finalSquare:
        continue gameLoop // 开始新的while循环
    }
}

检测 API 可用性

if #available(iOS 9, OSX 10.10, *) {
    // 在 iOS 使用 iOS 9 的 API, 在 OS X 使用 OS X v10.10 的 API
} else {
    // 使用先前版本的 iOS 和 OS X 的 API
}

以上可用性条件指定在 iOS,if段的代码仅仅在 iOS 9 及更高可运行;在 OS X,仅在 OS X v10.10 及更高可运行。最后一个参数,*,是必须的并且指定在任何其他平台上,if段的代码在最小可用部署目标指定项目中执行。


评论: