Swift4 学习笔记——基础篇

示例代码来源于 《iOS 11 Programming Fundamentals with Swift》

概览

语句分隔符

Swift的语句是可以通过分析断句的,如果一个语句结束后换行就开始下一个语句,如果写了分号也表示结束这个语句,但是有了换行就不需要分号了,写上也没有问题。如果一个语句没有结束,换行没有实际效果。下边的代码都是合法的:

print("hello")
print("world")

print("hello"); print("world")

print("hello");
print("world");

print(
    "world")

注释

依旧是://和//,其中/* … */可以嵌套

对象的类型

Swift中一切都是对象。按照类型划分,有6种:

  • struct, Bool,Int, Double, String, Array,Dictionary等
  • enum, Optional类型
  • class, 用户自定义类型。这篇文章提到了Swift中有3个预定义的class类型,但是没有指出是哪个。

  • protocol,ExpressibleByIntegerLiteral

  • tuple,函数返回多值时使用。
  • function,print,自定义函数。

举个例子:

字面变量是对象:

let s = 1.description

#### 值类型和引用类型

按照内存管理来划分,Swift对象有值类型和引用类型,值类型在赋值的时候是copy的(不考虑Swift优化),引用类型是共享内存的。

官方文档对值类型和引用类型的解释:

Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class.

在Swift中,class和function是引用类型,struct,enum,tuple都是值类型。protocol本身不允许有实例,但是采用protocol的可以是struct, enum或者class。

数据类型

变量与常量

let one = 1
var two = 2

使用let声明的是常量,使用var声明的是变量

类型推断

从上面例子可以看出,如果在声明变量的时候就赋值,有时候是可以不写类型的,让编译器推断。上文中one和two都是Int类型。

那么什么时候需要些类型呢?

  • 只声明,不初始化。
var x : Int
  • 想要的类型和推断的类型不符合
let separator : CGFloat = 2.0
  • 不能推断出类型
let opts : UIViewAnimationOptions = [.autoreverse, .repeat]
  • 还有一种情况是提醒自己这个变量是啥类型
let duration : CMTime = track.timeRange.duration

基本类型用法

Bool

  • Bool是一个struct类型
  • 只有true和false两个值,不能做它解释。

Int

  • Int是struct类型
  • Int的取值在Int.max和Int.min之间,平台相关

Double

  • Double是struct类型
  • 64位架构处理器上,Double的精度是15位
  • Double的边界是Double.infinity,还有Double.pi等
  • 使用isZero来判断Double是否为0

数字类型转换

只有字面变量可以被隐式转换!

let d : Double = 10

将字面变量10转换成了Double类型,但是变量就不可以,下列的代码不能通过编译:

let i = 10
let d : Double = i // compile error

正确的写法是:

let i = 10
let d : Double = Double(i)

String

let str = "Hello World" //欧耶,终于不用写@了

多行字面变量的写法:

func f() {    
    let s = """ Line 1 Line 2 Line 3 """    
    // ...
}

func f() {    
    let s = """ Line "1" Line 2 \ and this is still Line 2 """    
    // ...
}

在String字面变量中使用(…)来计算表达式

let n = 5
let s = "You have \(n) widgets."

String支持+号和+=号

let s = "hello"
let s2 = " world"
let greeting = s + s2

String的utf8编码:

let s = "\u{BF}Qui\u{E9}n?"for i in s.utf8 {   
    print(i) // 194, 191, 81, 117, 105, 195, 169, 110, 63

}

String和数值的转化:

let i = 7
let s = String(i) // "7"

let i = 31
let s = String(i, radix:16) // "1f"

Range

Range是一个struct。 字面变量: a…b表示区间[a, b] a..< b表示区间[a, b)

最常见的就是在for循环中使用:

for ix in 1...3 {    
    print(ix) // 1, then 2, then 3
}

Range 有实例方法:

let ix = // ... an Int ...
if (1...3).contains(ix) { // ...

let s = "hello"
let ix2 = s.index(before: s.endIndex)
let s2 = s[.."hell"

Tuple

tuple是一个有序的轻量级的collection。

tuple的声明:

var pair : (Int, String)

初始化:

var pair : (Int, String) = (1, "Two")
var pair = (1, "Two")

tuple可以同时给多个变量赋值:

let ix: Int
let s: String
(ix, s) = (1, "Two")

tuple在for-in中的应用:

let s = "hello"
for (ix,c) in s.enumerated() { print("character \(ix) is \(c)") }

对Tuple中值的引用:

let pair = (1, "Two")
let ix = pair.0 // now ix is 1

如果在声明的时候给值一个label,可以通过label引用:

let pair : (first:Int, second:String) = (1, "Two")
//or: let pair = (first:1, second:"Two")

var pair = (first:1, second:"Two")
let x = pair.first // 1
pair.first = 2
let y = pair.0 // 2

还可以给Tuple起一个别名

typealias Point = (x:Int, y:Int)

func piece(at p:Point) -> Piece? {    
    let (i,j) = p    
    // ... error-checking goes here ...    
    return self.grid[i][j]

}

可选类型

Swift中变量如果不初始化是不能使用的。这点和OC不同,OC中值类型会有一个默认值,引用类型默认为nil。Swift中如何表示nil呢?答案就是Optional(可选类型)

Optional类型的底层是enum类型,可以包装一个其他类型,具体内部实现这里不讨论。
比如:

var stringMaybe = Optional("howdy")

就定义了一个包装了String的Optional类型。包装不同类型的Opational也是不同的类型,不能互相赋值。Optional(String)类型可以简写为String?

如果没有给Optional的变量装箱一个值,那么它就是空的,空的Optional变量可以和nil比较:

var stringMaybe : String? = "Howdy"
print(stringMaybe) // Optional("Howdy")
if stringMaybe == nil {    
    print("it is empty") // does not print
}
stringMaybe = nilprint(stringMaybe) // nil
if stringMaybe == nil {    
    print("it is empty") // prints
}

在Swift中nil是一个关键字,不是一个值,可以将nil赋值给Optional的类型。

自动装箱,将一个值直接值给包装它的Optional类型。

var stringMaybe: String? = "farewell

根据自动装箱机制,可以在任何需要Optional类型的地方传入原始类型,但是反过来不行。

let stringMaybe : String? = "howdy"
let upper = stringMaybe.uppercased() // compile error

不能给Optional类型直接发送消息,需要拆箱得到原始数据。

拆箱

let stringMaybe : String? = "howdy"
let upper = stringMaybe!.uppercased()

在变量后边加上叹号,就拆箱得到原始类型。

自动拆箱,在定义变量的时候使用!而不是?就定义了一个自动拆箱的Opational变量,在需要使用原始类型的地方,直接传入自动解包的Opational变量即可。

func realStringExpecter(_ s:String) {}
var stringMaybe : String! = "howdy"
realStringExpecter(stringMaybe) // no problem

注意,如果自动解包的Optional是nil,会引起Crash。不能给一个是nil的Optional类型解压,这是Swift最重要的规则之一。 所以,如果不是必须,最好不要使用这个特性,因为这样就失去了Swift中可选类型的安全特性。

!定义的Optional和?定义的Optional是同一个类型,比如self.view是一个UIView!,但是如下代码却产生编译错误。

var stringMaybe : String! = "howdy"
var anotherStr = stringMaybe //ok
var pureStr: String = stringMaybe //ok
var errStr: String = anotherStr // compile error

stringMaybe是自动拆箱的String?,所以赋值给String类型是可以的;但是anotherStr却没有自动拆箱的标志,仅仅是一个String?,所以不能赋值给String类型。

Optianal Chain是Swift中很重要的一个概念。

拆箱nil会引起Crash,那么如果每次拆箱都得判断是否为nil,代码就会很难看。于是Swift提供了语法糖:

var stringMaybe : String?
// ... stringMaybe might be assigned a real value here ...
let upper = stringMaybe?.uppercased()

在拆箱的时候,不用!而是用?,这叫做选择性拆箱。英文很有意思:unwarp the Optional optionally。

选择性拆箱实际上替你做了判断工作,就是如果stringMaybe是nil,那么什么也不做,如果不是nil,拆箱得到String,然后发送uppercased消息。

这很好,但是如果“什么也不做”返回值upper是啥?答案是nil。那么nil是不能赋值给String类型的,于是又引入一个规则:

如果一个Optional Chain上有一个可能的Optional的类型(选择性拆包才有),那么返回值就是Optional的

也就是说虽然uppercased方法返回的是String类型,但是因为它在一个Optional Chain中,所以返回值自动被装箱,成为String?类型。一个Optional Chain返回一个Optional的类型也合情合理。

因为自动装箱,给一个Opational Chain赋值会比较简单。

// self is a UIViewController
self.navigationController?.hidesBarsOnTap = true

同样,如果navigationController是nil,什么也不会发。那么如何知道赋值成功了呢?

let ok : Void? = self.navigationController?.hidesBarsOnTap = true

如果ok不是nil,就是赋值成功。

Optional类型是可以和原始类型直接比较的。下边的代码没有问题。

let s : String? = "Howdy"
if s == "Howdy" { // ... they _are_ equal!

如果s是nil,返回false,如果s不是nil,拆箱之后再和”Howdy”比较。

但是不能比较不等关系,下边的代码是不能通过编译的:

let i : Int? = 2
if i < 3 { // compile error

因为Swift不能确定如果是nil,结果是什么。

函数

函数的定义

func sum (_ x:Int, _ y:Int) -> Int {
    let result = x + y
    return result
}
  • func 是keyword,sum是函数名。
  • 括号内部是参数,参数标签,变量名,冒号后是类型。和OC结构一样。
  • -> Int表示返回值是Int类型。如果函数返回Void,可以写成->()
  • 函数体在大括号内部。
  • 参数前边的”_”符号表示忽略参数的标签。

参数标签

func echoString(_ s:String, times:Int) -> String {
    var result = ""
    for _ in 1...times { result += s }
 return result
}

times就是参数的外部名字(external name),也可以叫参数标签。这是和OC语言的参数名字和变量名字分开是一致的。

调用的代码应该是这样:

let s = echoString("hi", times:3)
  • 默认的,参数的变量名(internal name)就是参数标签(external name)。
  • 如果使用了_,表示没有标签,调用方也不能使用标签调用了。
  • 具有相同的函数签名,但是参数标签不同的函数,是两个不同的函数。

函数参数默认是不可变的。意思是,不能在函数中给函数参数再次赋值。对于引用类型,是可以改变内部属性的。

func say(_ s:String, times:Int, loudly:Bool) {
    loudly = true // compile error
}

如果想要重新给参数赋值需要满足以下几个条件:
- 给函数参数添加intout关键字
- 传入的变量应该是var而不是let的
- 传入变量的地址。

func removeCharacter(_ c:Character, from s: inout String) -> Int {
    var howMany = 0
    while let ix = s.index(of:c) {
        s.remove(at:ix)
        howMany += 1
    }
    return howMany
}

调用

var s = "hello"
let result = removeCharacter("l", from:&s)

Swift中函数是first-class object,意思是函数可以赋值给变量,可以作为函数的参数和返回值。

func doThis(_ f:() -> ()) { f() } func whatToDo() { print("I did it") } doThis(whatToDo) 

函数是first-class object这一特点可以衍生出很多编程模式,装饰器,偏函数,函数工厂等等。

class, struct & enum

概览

enum,struct在Swift中和class很像,都可以定义方法,初始化函数等,但是有两个重大的区别:
- enum,struct是值类型,class是引用类型
- enum,struct不能继承

在这3种类型中,可以有的结构是:
- 初始化函数。
- 属性,分为成员属性和类属性。对于struct和enum用static关键字,对于class用class关键字。
- 方法,成员方法和类方法。
- 下标(subscripts)
- 嵌套定义(值类型的不能嵌套自己的类型)。

在Swift中没有一个像NSObject那样的公共基类。

class

初始化方法

由于Swift中不允许使用未经初始化的变量,并且想在编译阶段强制的保证这一点。于是对于class类型的初始化,引入了很多规则。虽然规则条数很多,但都是围绕这一个原则:从初始化函数中返回的对象的所有属性也是初始化的,并且在初始化完成之前不能使用这个对象

  • 初始化函数必须初始化所有未初始化的属性
class Dog {
    let name : String
    let license : Int
    init(name:String = "", license:Int = 0) {
        self.name = name
        self.license = license
    }
}

如果删除self.license = license,将会产生编译错误,因为license没有初始化。

  • 在初始化所有属性之前,不能使用self
class Cat {    
    var name : String    
    var license : Int    
    init(name:String, license:Int) {        
        self.name = name        
        meow() // too soon - compile error 
        self.license = license    
    }    

    func meow() {        
        print("meow")    
    }

}

meow()实际上隐式的使用了self,即self.meow()。应该将meow()的调用放到最后。

如果初始化函数之间发生调用关系,初始化函数就分成了两类:designated initializer 和convenience initializer。

designated initializer就是能独立完成对象的初始化的初始化函数,而convenience initializer必须直接或者间接的调用designated initializer来完成初始化工作。

class Dog{
    var name: String
    var age: Int

    init(){
        self.name = "test"
        self.age = 10
    }

    convenience init(name:String){
        self.init(name: name, age: 10)
    }

    init(name: String, age: Int){
        self.name = name
        self.age = age
    }
}

在class中designated initializer不需要特别指明,但是convenience initializer必须使用convenience关键字。(这一条只是对class来讲,如果把class换成struct就不需要使用convenience,这和class是能继承有关系,稍后会介绍到继承)

这又有一条规则: convenience initializer在使用self之前,必须调用designated initializer

举个例子:

class Dog{
    var name: String
    var age: Int

    init(){
        self.name = "test"
        self.age = 10
    }

    convenience init(name:String){
        self.age = 11 
        self.name = "haha"
        self.init(name: name, age: 10)
    }

    init(name: String, age: Int){
        self.name = name
        self.age = age
    }
}

上边的代码会发生编译错误,因为convenience初始化函数中在self被designated initializer初始化之前就使用了self。从这一点上看,convenience initializer并不是一个真正的初始化函数,只是能提供初始化功能的一般函数。

在高级篇介绍的继承体系中,会有更复杂的初始化规则。不过如果你违反了这些规则,编译器都会提示的很清楚。只要理解这些规则的目的都是确保对象被完全初始化即可。

属性(对struct和class都适用)

在类的属性全部被初始化完毕之前,不能使用self。

class Moi {
    let first = "Matt"
    let last = "Neuburg"
    let whole = self.first + " " + self.last // compile error

}

对于静态属性的使用,在非静态函数中应该使用类名.属性,在静态函数中可以使用self.属性或者类名.属性

class Greeting {
    static let friendly = "hello there"
    static let hostile = "go away"

    static var ambivalent : String {
        return self.friendly + " but " + self.hostile
    }
}

下标(对struct和class都适用)

下标是一种调用实例方法的方式。一般在通过整数参数或者String类型的key获取元素的时候使用下标。

struct Digit {    
    var number : Int    

    init(_ n:Int) {
        self.number = n    
    }   

    subscript(ix:Int) -> Int {
        get {             
            let s = String(self.number)  
            return Int(String(s[s.index(s.startIndex, offsetBy:ix)]))!        
        }   
    }

}

上述代码定义了一个通过位数取数字的下标方法,只读。

var d = Digit(1234)
let aDigit = d[1] // 2

嵌套定义

class Dog {   
    struct Noise {        
        static var noise = "woof"    
    }    

    func bark() {        
        print(Dog.Noise.noise)  
    }
}

注意:struct不能直接或者间接嵌套自己的类型。

Struct

struct大部分特性都和class一致,可以看做是没有继承特性的值类型的class。

一些不同:
- 改变struct属性的方法需要标记为mutating,在enum章节中会有例子。
- 默认的初始化函数可以提供逐一赋值功能(memberwise),只要能保证所有属性都初始化。

struct Digit {
    var number = 42
    var number2
}

var d = Digit(number: 3, number2: 34)
var f = Digit() //compile error
  • struct 和enum的类方法或者类属性使用static关键字,class可以使用static或者class,static = final class。

enum

enum Filter {    
    case albums    
    case playlists    
    case podcasts    
    case books
}

let type = Filter.albums

在能根据上下文推断出enum的类型的时候,可以简写成:

let type : Filter = .albums

RawValue

可以给enum指定一个存储类型,存储类型只能是数字或者String

enum PepBoy : Int { 
    case manny    
    case moe    
    case jack
}

enum Filter : String {
    case albums
    case playlists
    case podcasts
    case books
}

PepBoy中默认从0开始,Filter中默认值就是case的名字。

要获取enum中相应case的值,使用rawValue属性

let type = Filter.albums
print(type.rawValue) // albums

可以通过rawValue初始化enum

let type = Filter(rawValue:"Albums")

Swift中的enum可以有初始化方法

enum Filter : String {
    case albums = "Albums"
    case playlists = "Playlists"
    case podcasts = "Podcasts"
    case books = "Audiobooks"
    static var cases : [Filter] = [.albums, .playlists, .podcasts, .books]
    init(_ ix:Int) {
        self = Filter.cases[ix]
    }
}

上边的代码就可以通过一个Int来初始化一个存储类型是String的enum。

enum可以有实例方法和类方法

enum Shape {
    case rectangle
    case ellipse
    case diamond
    func addShape (to p: CGMutablePath, in r: CGRect) -> () {
        switch self {
        case .rectangle:
            p.addRect(r)
        case .ellipse:
            p.addEllipse(in:r)
        case .diamond:
            p.move(to: CGPoint(x:r.minX, y:r.midY))
            p.addLine(to: CGPoint(x: r.midX, y: r.minY))
            p.addLine(to: CGPoint(x: r.maxX, y: r.midY))
            p.addLine(to: CGPoint(x: r.midX, y: r.maxY))
            p.closeSubpath()
        }
    }
}

上边的代码能根据这个enum实际的值,来创建一个图形。

如果一个enum的实例方法能够修改这个enum的值,那需要将方法声明为mutating

enum Filter : String {
    case albums = "Albums"
    case playlists = "Playlists"
    case podcasts = "Podcasts"
    case books = "Audiobooks"
    static var cases : [Filter] = [.albums, .playlists, .podcasts, .books]
    mutating func advance() {
        var ix = Filter.cases.index(of:self)!
        ix = (ix + 1) % 4
        self = Filter.cases[ix]
    }
}

原理是这样的,enum是一个值类型,值类型是不可变的,要改变enum的值,只有再创建一个enum。这个动作在Swift中是需要开发人员显示指定的。这一条也适用于struct。

Associated Value

在Swift中enum还可以作为C语言中的Union使用。

enum MyError {
    case number(Int)
    case message(String)
    case fatal
}
  • 在MyError中不声明任何存储类型
  • 在每个case后边用tuple定义类型

MyErrorj就是一个可能保存Int或者String的数据类型。

let num = 4
let err : MyError = .number(num)

因为Associated Value是动态赋值的,所以Associated Value类型的enum不能使用enum比较。

if err == MyError.fatal { // compile error

因为Swift不知道如何比较,两个实例的fatal可能关联了不同的值,那么到底是相同还是不相同?

集合数据类型

Array

  • Array只能保存一种数据类型,是指声明为同一种的数据类型,不是实际类型。
  • 如果想保存混合类型的数据,使用[Any],Any是为了和OC交互定义的数据类型。
  • 保存不同类型的Array属于不同的数据类型。
  • Array是值类型,是struct。

保存Int类型的Array有两种写法:

let arr1 = Array<Int>()
let arr2 = [Int]()

可以使用Range:

let arr3 = Array(1...3)

Array有很多初始化函数,比如还可以接受一个集合类型,创建出一个Array

let arr4 = Array("hey".characters)

有一个初始化函数需要注意:init(repeating:count),如果参数是引用类型,那么Array中的所有元素将指向同一个元素。

class Person {
    var name = "123"
}

var p = Person()
let arr5 = Array(repeatElement(p, count: 3)) 
//[{name "123"}, {name "123"}, {name "123"}]

arr5[1].name = "555"  
//[{name "555"}, {name "555"}, {name "555"}]

Array作为一个整体可以类型转换:

let dog1 : Dog = NoisyDog()
let dog2 : Dog = NoisyDog()
let arr = [dog1, dog2]
let arr2 = arr as! [NoisyDog]

NoisyDog 是 Dog的子类, arr是[Dog]类型,可以时间用as!或者as?转换为[NoisyDog]类型。

两个Array相等的条件是Array中的每一个元素相等(注意并没有要求两个Array的类型是一样的)。和其他语言类似,可以自己提供比较函数。

let nd1 = NoisyDog()
let d1 = nd1 as Dog
let nd2 = NoisyDog()
let d2 = nd2 as Dog
if [d1,d2] == [nd1,nd2] { // they are equal!

Array的下标是支持切片的(slicing),切片仅仅是原来Array的一个映像,底层还是引用的是原来的Array

let arr = ["manny", "moe", "jack"]
let slice = arr[1...2] // ["moe", "jack"]
print(slice[1]) // moe

slice是arr的从1到2闭区间的切片,下标也是从1开始,到2结束。==如果引用了下标0,则会产生运行时错误==。如果改变了切片中的元素(前提是可以改变),则原来的数组也会受到影响。

但是,Array不支持负数下标。

Array有一些常用的属性:

let arr = ["manny", "moe", "jack"]

arr.count
arr.isEmpty
arr.first
arr.last
arr.startIndex
arr.endIndex
//...

判断元素是否存在:

let arr = [1,2,3]
let ok = arr.contains(2) // true
let ok2 = arr.contains {$0 > 3} // false
let arr = [1,2,3]
let ok = arr.starts(with:[1,2]) // true
let ok2 = arr.starts(with:[1,-2]) {abs($0) == abs($1)} // true

改变Array元素:

var arr = [1,2,3]
arr.append(4)
arr.append(contentsOf:[5,6])
arr.append(contentsOf:7...8) // arr is now [1,2,3,4,5,6,7,8]
var arr = ["manny", "moe", "jack"]
arr.insert("333", at: 1) //["manny", "333", "moe", "jack"]
arr.remove(at: 1) //arr is ["manny", "moe", "jack"]
let arr = [[1,2], [3,4], [5,6]]
let joined = Array(arr.joined(separator:[10,11]))
// [1, 2, 10, 11, 3, 4, 10, 11, 5, 6]
let arr = [1,2,3,4,5,6]
let arr2 = arr.split {$0 % 2 == 0} // split at evens: [[1], [3], [5]]

遍历Array元素

let pepboys = ["Manny", "Moe", "Jack"]
for pepboy in pepboys {
    print(pepboy) // prints Manny, then Moe, then Jack
}
let pepboys = ["Manny", "Moe", "Jack"]
pepboys.forEach {print($0)} // prints Manny, then Moe, then Jack
let pepboys = ["Manny", "Moe", "Jack"]
for (ix,pepboy) in pepboys.enumerated() {
    print("Pep boy \(ix) is \(pepboy)") // Pep boy 0 is Manny, etc.
}
// or:
pepboys.enumerated().forEach {print("Pep boy \($0.0) is \($0.1)")}
let pepboys = ["Manny", "Jack", "Moe"]
let arr1 = pepboys.filter{$0.hasPrefix("M")} // ["Manny", "Moe"]
let arr2 = pepboys.prefix{$0.hasPrefix("M")} // ["Manny"]
let arr3 = pepboys.drop{$0.hasPrefix("M")} // ["Jack", "Moe"]

Array和OC的关系

如果一个NSArray没有任何额外信息则转化为[Any],NSArray中的对象都是class类型。把一个Array转化为NSArray没有额外的工作要做。

let arr = [UIBarButtonItem(), UIBarButtonItem()]
self.navigationItem.leftBarButtonItems = arr

在Array上调用NSArray的方法需要转换:

let arr = ["Manny", "Moe", "Jack"]
let s = (arr as NSArray).componentsJoined(by:", ")
// s is "Manny, Moe, Jack"

不能把一个Array转化成一个NSMutableArray。如果需要调用NSMutabelArray的方法,使用NSMutableArray的构造函数创建一个。

var arr = ["Manny", "Moe", "Jack"]
let arr2 = NSMutableArray(array:arr)
arr2.remove("Moe")
arr = arr2 as! [String]

在Xcode7以后,有些OC的API提供了额外的类型信息,比如:

+ (NSArray<NSString *> *)fontNamesForFamilyName:(NSString *)familyName;

这时候返回的值就能直接转换为String。

Dictionary

Dictionary的语法:

var d : [String:String] = [:]
var d = [String:String]()
var d = ["CA": "California", "NY": "New York"]

两个Array,一个保存Key,一个保存Value,初始化一个Dictionary

let abbrevs = ["CA", "NY"] 
let names = ["California", "New York"]

let tuples = zip(abbrevs, names) 
let d = Dictionary(uniqueKeysWithValues: tuples)

如果两个Array长度不同,zip自动忽略额外的部分,保证成对。

从Dictionary中取出来的值是Opational的,因为如果不存在的话会返回nil。可以使用有默认值的方式获取

let d = ["CA": "California", "NY": "New York"] 
let state = d["MD", default:"N/A"] // state is a String (not an Optional)

使用了default关键字返回的就是String而不是String?

Dictionary的遍历:

遍历key:

var d = ["CA": "California", "NY": "New York"] 

for s in d.keys { 
    print(s) // NY, then CA 

}

遍历key和value:

var d = ["CA": "California", "NY": "New York"] 

for (abbrev, state) in d { 
    print("\(abbrev) stands for \(state)") 

}

可以将Dictionary变成一个Tuple的Array:

var d = ["CA": "California", "NY": "New York"] 
let arr = Array(d) 
// [(key: "NY", value: "New York"), (key: "CA", value: "California")]

和NSDictionary的关系:

NSDictionary对应[AnyHashable: Any],NSDictionary向Swift转换:

let prog = n.userInfo?["progress"] as? Double 

if prog != nil { 
    self.progress = prog!

}

Swift中使用Cocoa接口:

UINavigationBar.appearance().titleTextAttributes = [
    .font: UIFont(name: "ChalkboardSE-Bold", size: 20)!, 
    .foregroundColor: UIColor.darkText, 
    .shadow.: {
        let shad = NSShadow()
        shad.shadowOffset = CGSize(width:1.5,height:1.5)
        return shad 

    }()
]

Set

let set : Set<Int> = [1, 2, 3, 4, 5]

在Swift中Set没有字面变量,但是可以用Array构建。

在Array中去重:

let arr = [1,2,1,3,2,4,3,5] 
let set = Set(arr) 
let arr2 = Array(set) // [5, 2, 3, 1, 4], perhaps

insert 和 update,假设Dog的比较函数是name相等。

var set : Set = [Dog(name:"Fido", license:1)] let d = Dog(name:"Fido", license:2) set.insert(d) // [Dog(name: "Fido", license: 1)] set.update(with:d) // [Dog(name: "Fido", license: 2)] 

当已经存在的时候,insert不会改变set,update更新set。

两个set可以使用==比较,相等的条件是每一个元素相等。

求两个Set的交集:

intersection(_:)formIntersection(_:)

求两个Set的并集:

union(_:)formUnion(_:)

求两个Set的异或:

symmetricDierence(_:), formSymmetricDierence(_:)

求两个Set的差集:

subtracting(_:), subtract(_:)

还有几个集合的函数,判断是不是子集,判断有没有相交等。

Optional Set

Optional Set是和NS_OPTIONS对应的。

typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {

    UIViewAnimationOptionLayoutSubviews = 1 << 0,
    UIViewAnimationOptionAllowUserInteraction = 1 << 1, 
    UIViewAnimationOptionBeginFromCurrentState = 1 << 2, 
    UIViewAnimationOptionRepeat = 1 << 3, 
    UIViewAnimationOptionAutoreverse = 1 << 4, 
    // ...

};

对应Swift中:

UIViewAnimationOptions.layoutSubviews 
UIViewAnimationOptions.allowUserInteraction 
UIViewAnimationOptions.beginFromCurrentState 
UIViewAnimationOptions.repeat 
UIViewAnimationOptions.autoreverse

UIViewAnimationOptions被定义为一个Set,这样就可以模拟bitmask了。

var opts = UIViewAnimationOptions.autoreverse 
opts.insert(.repeat)

也可以使用运算符操作:

let val = UIViewAnimationOptions.autoreverse.rawValue | UIViewAnimationOptions.repeat.rawValue 
let opts = UIViewAnimationOptions(rawValue: val)

控制结构

if 语句

if condition { statements } else if condition { statements } else { statements }

条件语句不需要用括号括起来。

条件绑定(conditional binding),这个在Optional变量的使用中非常常见

if let prog = n.userInfo?["progress"] as? Double {
    self.progress = prog
}

等号后边是一个Optional Chain,可能返回nil,或者一个Double?,如果Optional Chain返回的是nil,则条件不成立,不会执行大括号内内容。如果Optional Chain不是nil,则==自动拆箱==,然后把拆箱后的值赋给prog,注意,==prog是Double而不是Double?==,prog的作用域在条件语句内部

Switch 语句

不用写break,自动break

switch i {
case 1:
    print("You have 1 thingy!")
case 2:
    print("You have 2 thingies!")
default:
    print("You have \(i) thingies!")
}

但是Switch的case语句必须覆盖所有情况,否则会发生编译错误。case语句也不能为空,至少要写一句break

可以在case中定义变量

switch i {
case 1:
    print("You have 1 thingy!")
case let n:
    print("You have \(n) thingies!")
}

如果i不是1,就将i赋值给n(好像并没有什么卵用)

可以使用Range 匹配:

switch i {
case 1:
    print("You have 1 thingy!")
case 2...10:
    print("You have \(i) thingies!")
default:
    print("You have more thingies than I can count!")
}

Switch的另外一种用法:

func position(for bar: UIBarPositioning) -> UIBarPosition {
    switch true {
    case bar === self.navbar:  return .topAttached
    case bar === self.toolbar: return .bottom
    default:                   return .any
    }
}

在switch中先指定结果(只能是true或者false),然后在case中判断表达式的结果是否和switch中相同。

case语句中还可以加filter:

switch i {
case let j where j < 0:
    print("i is negative")
case let j where j > 0:
    print("i is positive")
case 0:
    print("i is 0")
default:break
}

上述代码等价于:

switch i {
case ..<0:
    print("i is negative")
case 1...:
    print("i is positive")
case 0:
    print("i is 0")
default:break
}

还可以判断对象类型:

switch d {
case is NoisyDog:
    print("You have a noisy dog!")
case _:
    print("You have a dog.")
}
switch d {
case let nd as NoisyDog:
    nd.beQuiet()
case let d:
    d.bark()
}

注意:第二段代码中是as而不是as?,如果d是NoisyDog,nd才会被赋值,如果不是就不走这一个分支了。

Switch还可以比较tuple:

switch (d["size"], d["desc"]) {
case let (size as Int, desc as String):
    print("You have size \(size) and it is \(desc)")
default:break
}

如果switch的type是enum,那么还可以有很多花样:

enum MyError {
    case number(Int)
    case message(String)
    case fatal
}
switch err {
case .number(let theNumber):
    print("It is a number: \(theNumber)")
case let .message(theMessage):
    print("It is a message: \(theMessage)")
case .fatal:
    print("It is fatal")
}
switch err {
case .number(1...):
    print("It's a positive error number")
case .number(..<0):
    print("It's a negative error number")
case .number(0):
    print("It's a zero error number")
default:break
}

因为Optional本身是一个enum,所以可以这样写:

switch i {
case .none: break
case .some(1):
    print("You have 1 thingy!")
case .some(let n):
    print("You have \(n) thingies!")
}

fallthrough关键字:

switch pep {
case "Manny": fallthrough
case "Moe": fallthrough
case "Jack":
    print("\(pep) is a Pep boy")
default:
    print("I don't know who \(pep) is")
}

if case

if case let .number(n) = err {
    print("The error number is \(n)")
}

这是一个switch语句的简写,直接将err enum的Associated value取出来。

条件赋值:

let title : String = {
    switch type {
    case .albums:
        return "Albums"
    case .playlists:
        return "Playlists"
    case .podcasts:
        return "Podcasts"
    case .books:
        return "Books"
    }
}()

??

func tableView(_ tv: UITableView, numberOfRowsInSection sec: Int) -> Int {
    return self.titles?.count ?? 0
}

self.titles是[String]?类型,如果不是nil,拆箱,获取count属性;否则,返回0

这段代码是什么意思?

let someNumber = i1 as? Int ?? i2 as? Int ?? 0

while

两种方式:

while condition {
    statements
}

repeat {
    statements
} while condition

for循环

for…in

for i in 1...5 {
    print(i) // 1, 2, 3, 4, 5
}

可以加filter:

for i in 0...10 where i % 2 == 0 {
    print(i) // 0, 2, 4, 6, 8, 10
}

可以使用case简写:

let arr : [MyError] = [
    .message("ouch"), .message("yipes"), .number(10), .number(-1), .fatal
]

for case let .number(i) in arr {
    print(i) // 10, -1
}

stride

for i in stride(from: 10, through: 0, by: -2) {
    print(i) // 10, 8, 6, 4, 2, 0
}

从10开始到0,步幅为-2,迭代。

sequence

sequence是一个函数,有两个参数,一个是初始值,一个是生成函数。sequence返回的是一个生成器,只有用的时候才会计算并返回下一个值。

sequence用法:

let seq = sequence(first:1) {$0 >= 10 ? nil : $0 + 1}
for i in seq { print(i) // 1,2,3,4,5,6,7,8,9,10 }

或者:

let seq = sequence(first:1) {$0 + 1}
for i in seq.prefix(5) {
    print(i) // 1,2,3,4,5
}

jumping

几个跳转的关键字:
fallthrough,是switch case中执行下一个case的意思。
continue,循环中,结束当前循环,从判断条件开始进行下一个循环。
break,在循环中,跳出当前循环,在switch…case中,跳出switch语句。

Swift中的循环可以带label,这样嵌套循环中的break和continue可以指定跳出哪一个循环。

outer: for i in 1...5 {
    for j in 1...5 {
        print("\(i), \(j);")
        break outer
    }
}
// 1, 1;

Error

Swift采用throw…catch的方式来管理错误。

error定义:

enum MyFirstError : Error {
    case firstMinorMistake
    case firstMajorMistake
    case firstFatalMistake
}
enum MySecondError : Error {
    case secondMinorMistake(i:Int)
    case secondMajorMistake(s:String)
    case secondFatalMistake
}

使用:

do {
    // throw can happen here
} catch MyFirstError.firstMinorMistake {
    // catches MyFirstError.firstMinorMistake
} catch let err as MyFirstError {
    // catches all other cases of MyFirstError
} catch MySecondError.secondMinorMistake(let i) where i < 0 {
    // catches e.g. MySecondError.secondMinorMistake(i:-3)
} catch {
    // catches everything else
}

抛出错误的函数需要在参数后边上throws关键字

enum NotLongEnough : Error {
    case iSaidLongIMeantLong
}
func giveMeALongString(_ s:String) throws {
    if s.characters.count < 5 {
        throw NotLongEnough.iSaidLongIMeantLong
    }
    print("thanks for the string")
}

throws也是函数签名的一部分,giveMeALongString的函数签名就是(String) throws -> ()

含有throws的函数必须使用try调用,try语句必须在do…catch中,或者是另外一个throws的函数中。

try!,这个try!的意思是这个函数虽然被标记为throws,但是我知道它肯定不会throw错误,try!是不需要do…catch或者throws函数中使用的。但是如果真的throw的错误,程序就会Crash。

同样有一个try?的作用在try和try!之间。try?可以在任何地方调用,但是会吞掉error。如果函数返回一个Optional,则返回nil。

rethrows关键字

一个参数中有接受throws函数的函数,如果自己本身不会throw error,则可以标记为rethrows。标记了rethrows关键字的函数,可以接受throw函数或者非throw的函数,如果调用者传入的参数是非throw的函数,那么可以不使用try来调用这个函数。擦!

func receiveThrower(_ f:(String) throws -> ()) rethrows {
    try f("ok?")
}
func callReceiveThrower() { // no throws needed
    receiveThrower { s in // no try needed
        print("thanks for the string!")
    }
}

Swift和OC的错误处理转换:

在OC中NSString有一个初始化函数:

- (instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;

需要传入一个NSError的地址,如果初始化失败,则返回nil,错误信息在NSError中。

在Swift中这个初始化函数变成了throw的:

init(contentsOfFile path: String, encoding enc: String.Encoding) throws

所以OC中的传入NSError地址的函数,全都被Swift中的throw函数替代。

do {
    let f = // path to some file, maybe
    let s = try String(contentsOfFile: f)
    // ... if successful, do something with s ...
} catch CocoaError.fileReadNoSuchFile {
    print("no such file")
} catch {
    print(error)
}

由Swift的Error转换陈NSError的时候,domain属性不变,code是enum的case的index

do … break

给do语句块加一个label,在语句块内部使用break label的时候就能跳出语句块。

out: do {
    // ...
    if somethingBadHappened {
        break out
    }
    // we won't get here if somethingBadHappened }

defer

defer的语句在离开当前的大括号之前一定会执行。比如一个释放资源的代码,在函数的任何一个退出分支都需要写,很容易遗忘。使用defer语句可以避免这个麻烦。

func doSomethingTimeConsuming() {
    defer {
        UIApplication.shared.endIgnoringInteractionEvents()
    }
    UIApplication.shared.beginIgnoringInteractionEvents()
    // ... do stuff ...
    if somethingHappened {
        return
    }
    // ... do more stuff ...
}

defer语句要写在尽可能靠前的位置,如果在return之后,那么defer语句是不会执行的。

abort

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

就是直接让程序挂掉,应该是一种调试手段。

assert还是可以使用的。

guard

guard是为了解决if false return 这样的嵌套问题的。是一个简写。

guard let s = optionalString else {return}
// s is now a String (not an Optional)

相当于

let s = optionalString
if s == nil {
    return
}

guard可以和try?合起来使用:

let f = // path to some file, maybe
guard let s = try? String(contentsOfFile: f) else {return}
// s is now a String (not an Optional

和case合起来使用:

guard case let .number(n) = err else {return}
// n is now the extracted number

和表达式一起使用:

guard howMany() > 10 else {return}

你可能感兴趣的:(ios开发)