Swift学习笔记

在线swift编辑器:IBM Swift Sandbox

基础知识

常量和变量

let,var分别声明常量,变量

代码中有不需要改变的值,使用 let 关键字将它声明为常量

使用 let 修饰 view 并且赋值,表示该常量的内存地址不允许修改,但是可以修改其内部的属性

字面量

  • 字面量是指像特定的数字,字符串或者是布尔值这样能够直接了当地指出自己的类型并对变量进行赋值的值。

    1
    2
    3
    4
    5
    6
    let aNumber = 3
    let astring = "Hello"
    let aBool = true

    let anArray = [1,2,3]
    let aDictionary = ["key1": "value1", "key2": "value2"]
  • 数值型字面量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let a = 11
    let b = 0b11
    let c = 0o11
    let d = 0x11
    let e = 2e2 //200
    let f = 2e-2 //0.02
    let g = 0xFp2 //60
    let h = 0xFp-2 //3.75
    let i = 1_000_000

类型标注

很少需要标注,swift会进行类型推断,根据右边的代码,推导出变量的准确类型

Option + Click 可以查看变量的类型

1
let a:Float = 3.0

注释

多行注释可嵌套

分号

swift语句,最后分号可写可不写

打印日志

1
2
print("item1","item2","item3", separator: ",,,", terminator: "...")
//item1,,,item2,,,item3...

类型转换

值永远不会被隐式转换为其他类型,如果要对不同类型的数据进行计算,必须要显式的转换

  • 与字面量的加减无关
    1
    2
    3
    let a = "abc"
    let b = 9
    let ab = a + String(b)

类型别名

1
typealias Example = Int

元组

  • 元组可通过变量名或下标访问
  • 可在定义元组的时候给元素命名
  • 不需要访问的元素可用_代替
  • 元组可作为函数返回值返回多个值
    1
    2
    3
    4
    5
    6
    7
    8
    let (color,price,_) = ("red",80,true)
    print(color)
    let pen = ("red",80,true)
    let (color,price,_) = pen
    print(color)
    print(pen.0)
    let some = (color:"red",price:80)
    print(some.color)

可选类型

可以是任何类型值的缺失
显式类型转换结果是可选类型

1
2
3
4
var s: String = "hello"
//s = nil //错误
var s1: String? = "hello"
s1 = nil

nil

nil是一个确定的值,表示值缺失,任何类型的可选状态都可以被设置为nil
不可用于非可选的常量和变量
可选常量或变量不赋值,自动设值为nil

  • 在OC中nil是指针,在swift中nil是可选类型。

if 语句以及强制解析

if语句中的条件必须是一个布尔表达式
当一个可选常量或变量确定包含值时,可用!强制解析

() 可以省略,但是 {} 不能省略

  • 当 if 的嵌套层次很深,可用where
    1
    if let u = url where u.host == "www.baidu.com" {}

解包:
(1)!强行解包,如果 变量 为空,运行时会崩溃
(2)判断是否为空

1
2
3
if str != nil {
print(str!)
}

(3)if let可选绑定

可选绑定

  • 需要改变,用if var
    可以用在if或while语句中
    可以判断多个可选项是否为空,用,隔开
  • 一旦进入if分支,变量就不再是可选项

    if let 不能与使用 &| 等条件判断,可以使用 where 子句

    1
    2
    3
    if let constantName = someOptional {
    //constantName
    }

隐式解析可选类型

可选类型被第一次赋值之后就可以确定之后一直有值,这种类型的可选状态被定义为隐式解析可选类型,可使用!直接声明,之后不需要使用!取值。
隐式解析可选类型就是可以自动解析的可选类型。
仍然可以可选绑定。

错误处理

抛出错误:

1
2
3
func canThrowAnError() throws {
// 这个函数有可能抛出错误
}

捕获错误:

1
2
3
4
5
6
do {
try canThrowAnError()
// 没有错误消息抛出
} catch {
// 有一个错误消息抛出
}

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum LoginError : Error {
case UserNotFound, UserPasswordNotMatch
}
func login(user: String, password: String) throws {

if 2 > 1 {
throw LoginError.UserPasswordNotMatch
}
if 1 < 2 {
throw LoginError.UserNotFound
}
print("Login successfully.")
}

do {
try login(user: "onevcat", password: "123")
} catch LoginError.UserNotFound {
print("UserNotFound")
} catch LoginError.UserPasswordNotMatch {
print("UserPasswordNotMatch")
} catch{
//加入一个空的catch,用于关闭catch。否则会报错:Errors thrown from here are not handled because the enclosing catch is not exhaustive
}

断言

当前面表达式的结果为false时显示后面的消息,应用终止。后面的断言消息也可省略。
release配置时,断言被禁用。在代码发布时,我们也不需要刻意去把这些断言手动清除。

  • 一般以下情况使用:
    • 下标越界
    • 传递给函数的参数不符合类型
    • 解析可选类型:一个可选值当前为 nil ,但随后的代码就需要非空的值才能成功执行。
1
2
3
let age = 17
assert(age>=18,"未成年人")
//Assertion failed: 未成年人

fatalError

  • 因为断言只会在 Debug 环境中有效,而在 Release 编译中所以变得断言都将被禁用。所以我们会考虑以产生致命错误(fatalError)的方式来种植程序。
    1
    2
    3
    required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyClass {
func methodMustBeImplementedInSubclass() {
fatalError("这个方法必须在子类中被重写") }
}
class YourClass: MyClass {
override func methodMustBeImplementedInSubclass() {
print("YourClass 实现了该方法")
}
}
class TheirClass: MyClass {
func someOtherMethod() {
}
// override func methodMustBeImplementedInSubclass() {
// print("TheirClass 实现了该方法")
// }
}
YourClass().methodMustBeImplementedInSubclass()

基本运算符

1
2
3
let a = 3
let b = 3.3
let c = Double(a)*b //或 let c = a*Int(b)

不可自增自减

取余

1
2
3
10 % 6  //4
10 % -6 //4
-10 % 6 //-4

空值合并(空合)运算符??

可对可选类型a进行空判断,如果包含值就解封,否则返回默认值b,此默认值b也为可选类型

1
2
3
let a = Int("aaa")
let b = 2
let c = a ?? b //等同于 let c = a != nil ? a! : b

区间运算符

  • 闭区间:…
  • 半开区间:..<

字符与字符串

初始化空字符串

1
2
let s1 = ""
let s2 = String()

字符

str.characters

1
2
3
4
5
6
7
let c = "a"

let chars:[Character] = ["a","b","c"]
let str = String[chars]
for char in are{
print(char)
}

字符串可变性

变量可变,常量不可变

连接字符串和字符

  • +:只能连接字符串,不能连接字符
  • append
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var str1 = "12"
    let str2 = "34"
    let char1:Character = "5"
    str1 += str2
    //str1 += char1 //+:只能连接字符串,不能连接字符
    str1.append(char1)
    str1.append(str2)
    //str1.append(contentsOf: char1) //contentsOf:只能连接字符串,不能连接字符
    str1.append(contentsOf: str2)

插入和删除

以下方法也可使用在Array,Dictionary,Set中。

  • str.characters.indices所有索引
  • 在某个索引值之前插入字符:insert(_:at:),不能越界
  • 插入字符串:insert(contentsOf:at:)

    1
    2
    var hello = "hello";
    hello.insert(contentsOf:" world!".characters, at: hello(before: welcome.endIndex))
  • 删除字符:remove(at:),越界返回nil

  • removeFirst(2),removeLast()
  • 删除字符串removeSubrange(_:)
    1
    2
    3
    var hello = "hello world!"
    let range = hello.index(hello.endIndex, offsetBy: -7)..<hello.endIndex
    hello.removeSubrange(range)

计算字符数量

str.characters.count

字符串索引

  • startIndex,endIndex,index(before:),index(after:),index(_:offsetBy:),也可以使用在Array,Dictionary,Set中
  • str.characters.indices所有索引

比较字符串

  • 字符串相等:==
  • 前缀相等:hasPrefix(_:)
  • 后缀相等:hasSuffix(_:)

插值

值转换成字符串:()

如果变量是可选项,拼接的结果中会有 Optional?? 操作符用于检测可选项是否为 nil, 如果不是 nil,使用当前值,如果是 nil,使用后面的值替代

格式化字符串

  • 在实际开发中,如果需要指定字符串格式,可以使用 String(format:...) 的方式
    1
    2
    3
    4
    5
    let h = 8
    let m = 23
    let s = 9
    //后面的参数需要放在一个数组中
    let timeString = String(format: "%02d:%02d:%02d", arguments: [h, m, s])
1
2
3
let name = "Tom"
let date = NSDate()
let string = NSString(format: "Hello %@. Date: %@", name, date)

swift4新增

  • 多行字符串字面量

    1
    2
    3
    4
    5
    6
    let str1 = """
    12\n34
    56
    789
    """
    print(str1)
  • 去掉 characters

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //swift3
    let values = "one,two,three..."
    print(values.characters.count)
    var i = values.characters.startIndex

    while let comma = values.characters[i...<values.characters.endIndex].index(of: ",") {
    if values.characters[i..<comma] == "two" {
    print("found it!")
    }
    i = values.characters.index(after: comma)
    }

    //swift4
    let values = "one,two,three..."
    print(values.count)
    var i = values.startIndex

    while let comma = values[i...<values.endIndex].index(of: ",") {
    if values[i..<comma] == "two" {
    print("found it!")
    }
    i = values.index(after: comma)
    }
  • String 当做 Collection 来用

    1
    2
    let abc: String = "abc"
    print(String(abc.reversed())) // cba

集合类型

集合的可变性

变量可变,常量不可变

数组

  • 数组的类型
    • 如果初始化时,所有内容类型一致,择数组中保存的是该类型的内容
    • 如果初始化时,所有内容类型不一致,择数组中保存的是 NSObject
  • 数字可以直接添加到集合,不需要再转换成 NSNumber
  • 如果将结构体对象添加到集合,仍然需要转换成 NSValue
    1
    2
    3
    4
    //var array2 = ["zhangsan", 18]  //会报错:Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
    let array2 = ["zhangsan", 18] as [Any]
    array2.append(100)
    array2.append(NSValue(CGPoint: CGPoint(x: 10, y: 10)))

最后一个元素后面允许有个逗号

  • 定义数组

    1
    2
    var arr1:[Int]
    let a:Array<Int>
  • 创建空数组

    1
    2
    3
    var arr2 = [Int]()
    //var arr3 = [] //错误
    var a2:[String] = Array<String>()
  • 给数组赋空值

    1
    2
    3
    4
    5
    arr1 = []
    arr = [Int](repeatElement(3, count: 10))
    arr = Array(repeatElement(2, count: 10))
    var ints = [Int](repeating: "2", count: 10)
    var ints = Array(repeating: "2", count: 10)
  • 判断是否是空数组:isEmpty

  • 不可以用下标访问的形式为数组添加新项

  • 遍历数组
    可以使用元组返回索引和元素

    1
    for (index, value) in arr.enumerated() {}
  • 必须是相同类型的数组才能够合并

集合

  • 存储在集合中的数据必须是可哈希化的。
    可哈希化必须满足三个条件:
    • a == a(自反性)
    • a == b意味着b == a(对称性)
    • a == b && b == c意味着a == c(传递性)
1
2
var a = 1
print(a.hashValue)
  • 创建空集合

    1
    var set1 = Set<String>()
  • 集合转数组

    1
    let arr = set1.sorted()
  • 集合的交差并补

    1
    2
    3
    4
    5
    6
    let x:Set = [1,2,3]
    let y:Set = [3,4,5]
    x.intersection(y)
    x.subtract(y)
    x.union(y)
    x.symmetricDifference(y)
  • 集合相等:有完全相同的元素

    1
    2
    3
    let set1:Set = [1,2,3]
    let set2:Set = [3,2,1]
    set1 == set2
  • 子集(可以相等),严格子集

    1
    2
    3
    4
    let set1:Set = [1,2,3,4]
    let set2:Set = [3,4]
    set2.isSuperset(of: set1)
    set2.isStrictSuperset(of: set1)
  • 父集(可以相等),严格父集

    1
    2
    set1.isSubset(of: set2)
    set1.isStrictSubset(of: set2)
  • 无交集

    1
    2
    3
    let set1:Set = [1,2]
    let set2:Set = [3,4]
    set1.isDisjoint(with: set2)

字典

  • 存储在集合中的key必须是可哈希化的。
  • 创建

    1
    2
    3
    var dict = ["a":"b"]
    var dict1 : Dictionary<String,String>
    var dict2 : [String:String]
  • updateValue(_:forKey:):设置或更新值,但是返回更新值之前的原值

    1
    let oldValue = dic.updateValue("lisi", forKey: "name")
  • dic["akey"] = nildic.removeValue(forKey: "akey"):移除键值对

  • dic.keys,dic.values

    1
    2
    var keyArr = [String](dict.keys)
    var valueArr = Array(dict.values)
  • 遍历字典时候需要明确指明数组中的数据类型
    for dict in dictArray as! [[String:String]]{}

其他

生成器(Generator)

  • 生成器允许遍历所有元素
    1
    2
    3
    4
    protocol GeneratorType {
    typealias Element
    mutating func next() -> Element?
    }

序列(Sequence)

  • 一种可以对其元素进行连续,迭代访问的类型。
    1
    2
    3
    4
    protocol SequenceType {
    typealias Generator: GeneratorType
    func generate() -> Generator
    }

集合(Collection)

控制流

For-In 循环

  • index 可直接使用,无序声明,不需要的时候可用_忽略
  • 省略下标
    • _ 能够匹配任意类型
    • _ 表示忽略对应位置的值
      1
      for index in 1...5 {}

while/repeat-while

switch

  • switch支持任意类型的数据比较
  • switch不会隐式贯穿,默认有break,不用加
  • 每一个 case 分支都必须包含至少一条语句,没有可用break;必须有default语句,要保证处理所有可能的情况,不然编译器直接报错
  • 每一个 case 中定义的变量仅在当前 case 中有效
  • 复合匹配,可以用逗号隔开匹配多个值
  • 区间匹配:case 1...10:
  • 元组匹配:case (_,0):
  • 值绑定:case (let x,0):
  • where添加额外条件:case let(x,y) where x==y:

控制转移语句

  • continue
  • break:可用于switch语句或循环体中
  • label

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var i = 1
    myLabel: while i<100{
    switch i {
    case 10:
    print("case-10")
    break myLabel
    case 20:
    print("case-20")
    default:
    print("default")
    }
    i+=1
    }
  • fallthrough:贯穿到下一个case中的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let i = 3
    switch i {
    case 1:
    print("1")
    case 2:
    print("2")
    case 3:
    print("3")
    fallthrough
    case 4:
    print("4")
    fallthrough
    default:
    print("default")
    }
  • return

  • throw
  • guard:提前退出,后面必须有一个else语句,else中必须包含控制转移语句
    防止代码嵌套过多,if满足条件需要return时就可以用guard,相当于if let

    1
    2
    3
    4
    //只有numOfMyFriend >0 的时候,才可以执行下面的程序。否则(else)就抛出一个异常。
    guard numOfMyFriend > 0 else {
    throw errorGame.noFriendToGether
    }
  • label标签语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    where labName{
    if(){
    break labName
    }else if(){
    continue labName
    }else{
    ///
    }
    }

检测API可用性

*是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。

1
2
3
4
5
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}

函数

  • swift中MARK的使用
    1
    // MARK: - mark something

函数定义与调用

1
2
3
4
5
func hello(name:String) -> String{
let str = "Hello,"+name+"!";
return str;
}
hello(name:"Anna");

指定参数标签

1
func someFunction(argumentLabel parameterName: Int) {}

忽略参数标签

如果一个参数有一个标签,那么在调用的额是很好必须使用标签来标记这个参数,如果不希望为某个参数添加标签,可以使用_来代替一个明确的参数标签。

1
2
3
4
func hello(name:String) -> String{}
//调用 hello(name:"Anna");
func hello(_ name:String) -> String{}
//调用 hello("Anna");

默认参数值

1
2
3
4
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
}
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12

可变参数

1
func getMiddle(_ numbers: Double...) -> Double{}

输入输出参数

函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误

  • 输入输出参数:可以在函数中修改的参数,并且这些修改在参数调用的时候仍然存在
    只能传递变量给输入输出参数,不能传入常量或者字面量,并且在传入的时候在参数名前添加&
1
2
3
4
5
6
7
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
//可以不使用额外空间而使用多元组特性直接交换 a 和 b 的值
(a, b) = (b, a)
}
var a = 5, b = 6
swapTwoValues(&a, &b)
print(a, b)

函数类型

  • 可作为参数类型或返回值类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func calculate(x: Int,y: Int,method: (Int,Int)->Int) -> Int{
    return method(x,y)
    }

    func add(x: Int,y: Int)->Int{
    return x+y
    }
    func multiply(x: Int,y: Int)->Int{
    return x*y
    }
    calculate(x: 3, y: 4, method: add)
    calculate(x: 3, y: 4, method: multiply)

嵌套函数

1
2
3
4
5
6
7
8
9
10
11
12
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = 4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")

柯里化

把接受多个参数的方法变换成接受第一个参数的方法,并且返回接受余下的参数并且返回结果的新方法。

-------------本文结束感谢您的阅读-------------