Swift初见

Swift初见

一些Swift的学习记录

类型 Type

隐式转换

  1. Swift不允许类型的隐式转换,如果你需要修改一个变量的类型,请显示转换。
1
2
3
let label = "The width is "
let width = 94
let widthLabel = label + String(width)

假如将第三句的String类型去掉,则会报错:

1
2
3
let label = "The width is "
let width = 94
let widthLabel = label + width

类型定义 Type

  1. Swift定义的类型都是以大写开头的,这是Swift的一个习惯,所以在自定义类型的时候,需要把第一个字母大写

字符串 String

  1. OC中。NSString是继承于NSObject,是一个对象,而在SwiftString是一个结构体,所以它的性能要比NSString更好。
  2. 字符串遍历
1
2
3
4
let hello : String = "Hello World!"
for letter in hello.characters{
print(letter)
}

字符串常见用法

1
2
3
4
5
6
7
8
9
hello.substring(to: hello.characters.count - 1)
hello.substring(from: 0)
//截取中间4位,从第2位开始
let start = hello.index(hello.startIndex, offsetBy: 2)
let end = hello.index(hello.endIndex, offsetBy: -(length - 6))
var subStr = hello.substring(with: start..<end)

var hasSuf = hello.hasSuffix("World!")
var hasPre = hello.hasPrefix("Hello")

条件判断 Condition

  1. if判断条件中,可以省略条件的”()”,
  2. 判断条件只能为Bool
1
2
3
4
5
if num == 1{
print(num)
}else {
...
}

控制流 Flow

  1. 使用switch语句,可以添加falltrough直接跳转到下个case的代码;
  2. case判断可以使用字符串;
  3. case如果没有以break结束,在swift中也不会穿透;
  4. 可以在case中定义变量,而无需添加{}作用域;
  5. default语句放最后,大部分情况不能省略。

元组 Tuple

  1. 元组可以组合不同的数据类型,比如StringIntCGFloat
  2. 元组的数据是有顺序的,从0开始
  3. 元组的数据可以使用命名,类似字典
  4. 使用二维元组遍历字典时候,元组第一个元素代表字典的key,第二个元素代表字典的value
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let infoTuple = (name: "FFur", age: "18", height: 1.88)
    print(infoTuple.name);
    print(infoTuple.age);
    print(infoTuple.height);
    //另一种赋值
    let (name, age, height) = ("FFur", "18", 1.88)
    //遍历
    for (k, v) in dict{
    }

可选类型Optionals

可选类型是Swift对值缺失问题的解决方案。可选类型的变量允许指向值或nil

  1. 针对变量的安全性: Optionals类型
  2. 顾名思义,只有可选类型才能赋值nil
  3. Optionals将变量的默认值设置为nil
  4. 例子:
1
2
3
4
5
var  result: Int? = 30
print(result)

var name : String? = nil
name = Optional("FFur")

Optionals解绑

  1. Use ! - NOT RECOMMENDED, IF NIL VALUE YOUR APP WILL CRASH,强制解包,非常危险。
  2. Use an "If Let",使用可选绑定
  3. Use a "Guard Statement" - Typically used in methods/functions

if let语法

可选绑定: 去掉了可选类型,如果可选类型中包含值,该值便会赋值给解绑的变量,然后执行if代码块,在里面你可以安全的使用已经解绑的变量; 如果不包含值,则执行else代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//if let会判断varName是否有值,如有值,就会把varName赋值给tempName
if let tempName = varName{
print(tempName)
}
if let varName = varName{
print(varName)
}

if let tempName = dict[“key”] as? String {
print(tempName)
}

//可以同时解绑多个可选类型
if let authorName = authorName,
let authorAge = authorAge {
print("The author is \(authorName) who is \(authorAge) years old.")
} else {
print("No author or no age.")
}

guard语法

有时你只想检查的一些条件,只当条件为真时执行对应的逻辑, 则guard提供了很好的解决方式

1
2
3
4
5
6
7
8
9
10
11
12
guard 判断条件 else{
//执行条件
return
}
//语法
guard let varName = varName else{
return
}
//example
guard age >= 18 else{
print("未成年")
}

简便操作符

  • Swift 中,有一个非常有用的操作符,可以用来快速地对 nil 进行条件判断,那就是 ??。这个操作符可以判断输入并在当左侧的值是非 nilOptional 值时返回其 value,当左侧是 nil 时返回右侧的值,比如:
    一种简便解包的写法,使用“??
1
2
3
let num1 = 2
let num = num1 ?? 0
print(num)

可选链

  • 它的可选性体现于请求或调用的目标当前可能为空(nil)
    • 如果可选的目标有值,那么调用就会成功;
    • 如果选择的目标为空(nil),则这种调用将返回空(nil)
  • 多次调用被链接在一起形成一个链,如果任何一个节点为空(nil)将导致整个链失效。
  • 可选链的使用
    • 在可选类型后面放一个问号,可以定义一个可选链。
    • 这一点很像在可选值后面放一个叹号来强制拆得其封包内的值
      • 它们的主要的区别在于当可选值为空时可选链即刻失败
      • 然而一般的强制解析将会引发运行时错误。
    • 因为可选链的结果可能为nil,可能有值.因此它的返回值是一个可选类型.
      • 可以通过判断返回是否有值来判断是否调用成功
      • 有值,说明调用成功
      • 为nil,说明调用失败
1
2
3
4
5
6
7
8
9
10
// 2.创建对象,并且设置对象之间的关系
// 2.1.创建对象
let person = Person(name: "小明")
let dog = Dog(color: UIColor.yellow)
let toy = Toy()
toy.price = 100.0

// 2.2.设置对象之间的关系
person.dog = dog
dog.toy = toy
  • 需求:获取小明的大黄宠物的玩具价格
    • 取出的值为可选类型,因为可选链中有一个可选类型为nil,则返回nil
    • 因此结果可能有值,可能为nil.因此是一个可选类型
1
2
let price = person.dog?.toy?.price
print(price) // Optional(100.0)\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//取出why的狗的玩具的价格
/*
强制解包: 该写法非常危险:
let dog= p.dog
let toy = dog!.toy
let price toy!.price
*/

/*
使用多层可选绑定:该写法非常麻烦
if let dog = p.dog {
if let toy = dog.toy {
let price = toy.price


*/
// "?."就是可选链:系统会自动判断该可选类型是否有值
//如果有值,则解包,如果没有值,则赋值为nil
//注意:可选链条获取的值,一定是一个可选类型
if let price = p.dog?.toy.price{ //Double/nil54
print(price)
}

//给可选链赋值,如果其中有一个可选类型没有值,则该语句不执行
p.dog?.toy.price = 50

闭包 Closure

闭包是一种没有名字的函数,用一个常量/变量来将它赋值。
闭包可以获取,存放,修改代码块作用域内的任何变量和常量

1. 闭包的定义:

1
2
3
4
5
6
7
8
9
10
11
var closureVariable: (parameters) -> returnType; // 声明一个闭包类型的变量closureVariable
// 将一个闭包赋值给closureVariable
closureVariable = { (parameters: paraType) -> returnType in
statements
}

// 完整写法
var closureVariable: (parameters) -> returnType = { (parameters: paraType) -> returnType in
statements
}
closureVariable(parameters)
  • 闭包的定义看起来类似于函数声明,但有细微的区别。虽然闭包和函数都有相同的参数列表,-> 符号和返回类型。但在在闭包下,这些都是在闭包括号内的,第二个是返回类型后面有一个in关键字。

2. 闭包的格式

  • 闭包的设计是比函数轻量级一些的,更加方便,有一些方法可以简写
  • 2.1闭包的基本格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//in 用于区分形成返回值和执行代码
{
(形参列表) -> (返回类型)
in
// 闭包内需要执行的代码
}
```

- 2.2 闭包作为函数的最后一个参数时候,可以写在"()"后面

```swift
func loadData(finished: () -> ()) {
print("耗时操作")
finished()
}

loadData (){ () -> () in
print("闭包被执行了")
}
  • 2.3 闭包作为函数的参数(尾随闭包),如果只有这一个闭包参数,则函数的“()”可以省略,如果闭包没有参数,没有返回值,可以省略”in”
1
2
3
4
5
6
7
8
9
10
11
12
    func loadData(finished: () -> ()) {
print("耗时操作")
finished()
}

loadData { () -> () in
print("闭包被执行了")
}//已省略“()”

loadData {
print("刷新UI")
} //闭包没有参数,没有返回值,可以省略"in"

几种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 完整写法
let say:(String) -> Void = {
(name: String) -> Void in
print("hi \(name)")
}
say("lnj")

// 没有返回值写法
let say2:(String) ->Void = {
(name: String) in
print("hi \(name)")
}
say2("lnj")

// 没有参数没有返回值写法
let say3:() ->Void = {
print("hi lnj")
}
say3()
  1. 例子
1
2
3
4
5
6
7
8
9
10
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
loadData {
print("刷新UI")
}
}
//闭包作为函数参数
func loadData(finished: () -> ()) {
print("耗时操作")
finished()
}

循环引用

  1. 闭包中属性必须要使用self强引用,但是直接用self很容易会引起循环引用,解决这个问题,需要使用weak var weakSelf = self改成在闭包内对属性的弱引用。如果弱引用,一般是可选类型,在执行闭包内的代码的时候,需要确认weakSelf是否存在。
1
2
3
4
5
weak var weakSelf = self
loadData { () -> () in
print("回到主线程更新UI")
weakSelf!.view.backGroudColor = .red
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// MyViewController
var myClass : MyClass
myClass = MyClass()
//方式一
/*
weak var weakSelf : UIViewController? = self
myClass?.loadData({ () in
weakSelf?.view.backgroudColor = UIColor.red
})
*/

//方式二://尾随闭包
myPerson.loadMyData(){[weak self] () in
self?.view.backgroundColor = UIColor.red
}

//方式三: unowned ——> unsafe unretain(需要保证对象会销毁,不然易以产生野指针)
myClass?.loadData({[unowned self] () in
self.view.backgroundColor = UIColor.red
})
  • weak在修饰对象销毁后会指向nil, 只有可选类型才能指向nil,所以这边的weakSelf是可选类型。

关键字@escaping

escaping表示逃逸的,
这边在闭包内部执行,闭包的回调如果在其他的闭包内,需要添加关键字@escaping,“逃出”了控制范围。

  • swift要求闭包内的参数都是内部参数,前面加上”_”
1
2
3
4
5
6
7
8
9
10
11
func loadPersonalData(callBackBlock: @escaping (_ jsonData: String) -> ()){

DispatchQueue.global().async {
print("Thread is \(Thread.current)")

DispatchQueue.main.sync {
print("Thread is \(Thread.current)")
callBackBlock()
}
}
}
  • swift闭包默认持有变量的reference
  • swift闭包默认在执行时才计算捕获变量的值
  • 可在swift闭包中修改捕获变量的值
  • 使用capture list,做变量的constant copy捕获。

参考:Swift闭包实战 - 简书


构造方法

方法重载

方法名称相同,形参可以不同.
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Person.swift
class Person: NSObject {

var name:String?
var age:Int?

override init() {
self.name = "mzn"
self.age = 27
}


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

1
2
3
4
5
6
7
//ViewController.swift
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let per = Person()
print("\(per.age!) \(per.name!)")
let p1 = Person(name: "del", age: 12)
print("\(p1.age!) \(p1.name!)")
}
  1. 如果自定义构造方法,但是没有重写(override)父类的构造方法,则默认的构造方法失效,会被替换成自定义的构造方法。比如将override init注释后,Person类原来的构造方法将失效:

  2. Swift中如果需要在构造方法里使用KVC给属性赋值,如setValuesForKeys,调用之前需要调用父类的初始化:super.init(),用以给属性(对象)分配存储空间。但是如果是基本数据类型,比如Int类型是不会的,如下图:

    所以解决的方式就是,基本类型需要赋值,比如直接将Person的属性赋值var age:Int = 0

函数 Function

函数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.没有参数,没有返回值
func myFunction() -> Void {
print("myFunction")
}
//2.有参数,没有返回值
func myFunction(phoneNum: String) -> Void {
print("打电话给\(phoneNum)")
}
//3.没有有参数,有返回值
func readMsg() -> String {
return "吃饭了吗?"
}
//4.有参数,有返回值
func sum(num1: Int, num2: Int) -> Int {
return num1 + num2
}

内部参数&外部参数

  • 内部参数: 只能在函数内部使用参数
  • 看到的标识符名称,该标识符就是内部参数
  • 外部参数: 在函数外部能看到的标识符名称,该标识符就是外部参数,只能在函数外部使用
  • 默认情况下,所有的参数都是内部参数,也是外部参数
  • 修改外部参数: 在标识符前加上外部名称即可
  • 如果不希望显示外部参数,可以在标识符前加上“_”.
  • 在参数前面加上#相当于该参数即是内部参数, 也是外部参数
1
2
3
4
5
6
7
8
9
10
11
12
func sum(externalPara num1 : Int, num2 : Int) -> Int{
return num1 + num2 //num1 和 num2为函数的内部参数,externalPara为外部参数
}
let result = sum(num1: 10, num2: 20)//num1 和 num2 也是为函数的外部参数
let result1 = sum(externalPara: 1, num2: 2)//externalPara 和 num2是为函数的外部参数
func sum1(_ num1 : Int, _ num2 : Int) -> Int{
return num1 + num2 //num1 和 num2为函数的内部参数,externalPara为外部参数
}
func sum1(_ num1 : Int, _ num2 : Int) -> Int{
return num1 + num2 //num1 和 num2为函数的内部参数,忽略外部参数
}
let result3 = sum1(2, 3)

可变参数

  • swift中函数的参数个数可以变化,它可以接受不确定数量的输入类型参数
  • 它们必须具有相同的类型
  • 我们可以通过在参数类型名后面加入(…)的方式来指示这是可变参数
1
2
3
4
5
6
7
8
9
10
11
// 2. 可变参数
func sum3(nums : Int ...) -> Int{
var total = 0
for n in nums{
total += n
}
return total
}

let fub = sum3(nums: 20, 30, 40)
print(fub)

默认参数

  • 某些情况,如果没有传入具体的参数,可以使用默认参数
1
2
3
4
5
func makeCoffee(coffeeName : String = "雀巢") -> String{
return ("制作了一杯\(coffeeName)")
}
makeCoffee()
makeCoffee(coffeeName: "猫屎")

指针参数

  • 引用类型(指针的传递)
  • 默认情况下,函数的参数是值传递.如果想改变外面的变量,则需要传递变量的地址
  • 必须是变量,因为需要在内部改变其值
  • Swift提供的inout关键字就可以实现
  • 对比下列两个函数
1
2
3
4
5
6
7
8
9
10
11
12
// 4.指针参数
var m = 20
var n = 30

func swapNum(num1 : inout Int, num2 : inout Int) {
let temp = num1
num1 = num2
num2 = temp
}

swap(&m, &n)
print("m:\(m) n:\(n)")

类 Class

  • 定义一个类后,初始化类时,需要对该类的所有属性初始化,一般,值类型(String, Int)等,给一个默认控制,如果是对象,则改成可选类型,后面加

Deinit

  1. 相当于Objective-Cdealloc方法

访问权限

  • Swift 中的访问控制模型基于模块和源文件这两个概念
    • internal : 在本模块中都可以进行访问
    • fileprivate : 在当前源文件中可以访
    • private : 在当前class中可以访问(但是extension中不可以访问)
    • open : 在其他模块中可以访问

访问级别

Swift提供了3种不同访问级别,对应的访问修饰符为:publicinternalprivate。这些访问修饰符可以修饰类、结构体、枚举等面向对象的类型,还可以修饰变量、常量、下标、元组、函数、属性等内容。
(为了便于描述,我们把类、结构体、枚举、变量、常量、下标、元组、函数、属性等内容统一称为“实体”。)

  • public。可以访问自己模块中的任何public实体。如果使用import语句引入其他模块,我们可以访问其他模块中的public实体。

  • internal。只能访问自己模块的任何internal实体,不能访问其他模块中的internal实体。internal可以省略,换句话说,默认访问限定是internal。

  • private。只能在当前源文件中使用的实体,称为私有实体。使用private修饰,可以用作隐藏某些功能的实现细节。

注意:

  1. 按钮的事件点击是由Runloop监听,并以消息机制进行传递的,因此,按钮点击事件不要设置为private

懒加载

  1. 使用lazy关键字
  2. lazy用于修饰变量var

例子:

1
2
3
4
5
6
7
8
9
10
11
12
let exBlock = {
(num: Int) -> Int in
print("exBlock is excuted.")
return num
}

lazy var number : Int = self.exBlock(123)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(number)
print(number)
print(number)
}

打印结果:

1
2
3
4
5
6
7
8
9
exBlock is excuted.
123
123
123
----
123
123
123
----

可以发现,在用lazy修饰number这个变量后,多次打印,exBlock闭包只在第一次调用,实现了懒加载的目的。**

#selector

例子

  • #selector需要声明方法的作用域,在方法名前面加上所属的类
1
2
3
4
5
6
7
8
9
10
11
12
13
@IBOutlet weak var cyanButton: UIButton!

override func viewDidLoad() {
super.viewDidLoad()

cyanButton.addTarget(self,
action: #selector(ViewController.cyanButtonClick),
for: .touchUpInside)
}

func cyanButtonClick() {
print(#function)
}

#selector()的好处是不再需要使用字符串来构造。因为当使用字符串构造时,若传入的字符串没有对应的方法名,那么程序在执行时就会直接崩溃:「unrecognized selector sent to instance」

若当前作用域构造 Selector 的方法名唯一时,可以直接使用方法名,而省略作用域。

1
2
3
cyanButton.addTarget(self,
action: #selector(cyanButtonClick),
for: .touchUpInside)
  • 当遇到上述存在歧义的相同方法名时,也可以使用强制类型转换来解决:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@IBOutlet weak var cyanButton: UIButton!
@IBOutlet weak var anotherCyanButton: UIButton!

override func viewDidLoad() {
super.viewDidLoad()

let methodA = #selector(cyanButtonClick as () -> ())
let methodB = #selector(cyanButtonClick as (UIButton) -> ())

cyanButton.addTarget(self,
action: methodA,
for: .touchUpInside)
anotherCyanButton.addTarget(self,
action: methodB,
for: .touchUpInside)
}

func cyanButtonClick() {
print(#function)
}

@objc private func cyanButtonClick(_ button: UIButton) {
let btnLabel = button.titleLabel?.text ?? "nil"
print(btnLabel)
print(#function)
}

协议 Protocol

协议的格式

  • 协议的定义方式与类,结构体,枚举的定义都非常相似
1
2
3
protocol SomeProtocol {
// 协议方法
}
  • 遵守协议的格式
1
2
3
4
class SomeClass: SomeSuperClass, FirstProtocol,   AnotherProtocol {
// 类的内容
// 实现协议中的方法
}

协议的基本使用

  • 定义协议和遵守协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.定义协议
protocol SportProtocol {
func playBasketball()
func playFootball()
}

// 2.遵守协议
// 注意:默认情况下在swift中所有的协议方法都是必须实现的,如果不实现,则编译器会报错
class Person : SportProtocol {
var name : String?
var age : Int = 0

// 实现协议中的方法
func playBasketball() {
print("人在打篮球")
}

func playFootball() {
print("人在踢足球")
}
}
  • 协议之间的继承
1
2
3
4
5
6
7
8
protocol CrazySportProtocol {
func jumping()
}

protocol SportProtocol : CrazySportProtocol {
func playBasketball()
func playFootball()
}

协议在代理模式中的使用

  • 定义协议时,协议后面最好跟上: class,表明是类的协议,避免和枚举类型混淆。
  • delegate代理的属性最好用weak修饰,避免循环引用。
  • Swift建议协议的方法都是必须实现的,如果需要可选方法Optional,则需要在protocal和可选方法前面加上@objc,在可选方法前加上Optional

循环 Loop

  1. Swift 3.0 去掉 C 风格循环后怎么办?
    关于 C 风格循环, 就是类似这样的语句:
1
2
3
let numberList = [1, 2, 3, 4, 5]
for var i = 0; i < numberList.count; i++ {
}
  1. 循环中如果不关心索引参数,可以使用_进行忽略
1
2
3
for _ in array{
//excute loop code .
}

for .. in 语法

第一个替代方案, 我们可以使用 for .. in 这样的语法:

1
2
3
4
5
let numberList = [1, 2, 3, 4, 5]
var result = ""
for num in numberList {
result += "\(num) "
}

这样就完成了对数组的遍历了, 但是还有另一个情况, 如果我们想知道每次遍历的索引怎么办呢, 还有一种方法:

1
2
3
for num in numberList.enumerate() {
result += "[\(num.index)]\(num.element) "
}

可以使用这个集合类型的 enumerate 方法,将这个数组的索引和对应的元素都取了出来,然后在循环中就可以对索引项进行引用了, num.indexnum.element 分别代表对应的索引和元素。

上面这个循环还可以再改写一下:

1
2
3
for (index, item) in numberList.enumerate() {
result += "[\(index)]\(item) "
}

不难看出,其实循环中的每一项都是一个元组(Tuple),这个元组的第一项是当前的索引, 第二项是当前的数组元素。 那么就可以推理出, enumerate 函数其实就是对 numberList 数组做了一个变换,原来它是一个 Int 类型的数组,经过变换后,成为了(Int, Int)元组类型的数组。

仔细看下, 它只不过是对集合类的一个集成, 这个集合每一项是一个元组(n, x) , n 代表索引, x 代表数组元素。

那么,还可以做点更有意思的事情:

1
2
3
for (index, item) in numberList.enumerate().reverse() {
result += "[\(index)]\(item) "
}

调用 enumerate, 之后再调用 reverse 方法, 就可以对一个数组进行反向遍历。

1
2
3
for (index, item) in numberList.enumerate().reverse() {
result += "[\(index)]\(item) "
}

还可以:

1
2
3
for (index, item) in numberList.enumerate().filter({ (index, item) in index % 2 == 0}) {
result += "[\(index)]\(item) "
}

调用 filter 函数,过滤某些索引, 只遍历符合条件的那些元素。

区间(Range)循环

除了刚才说的这些, Swift 还提供了更方便的循环语法, 叫做 Range 循环。 比如这样:

1
2
3
4
5
var rs = "";
for i in 0...10 {
rs += "\(i)"
}
print(rs)

这个语句会输出 0 到 10 之间的所有数字, 0…10 这个表示 Range 区间的范围。 当然,对于刚才的数组遍历来说, 一般数组索引都是数组长度减去 1, 用这个区间处理起来就会比较麻烦, 不过好在 Swift 给我们提供了另外一种 Range 方法:

1
2
3
for i in 0..<numberlist.count {  
rs +=" "\(i)""
}

这次换成了 0..<numberlist.count
这种形式会排除闭区间最后那个数组,然后就可以在循环中用索引进行访问啦(注意符号=”” ..<=”” 两边不要有空格)。

数组 Array

三种遍历方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let arr = ["1", "2", "3"]
////方式1
for item in arr{
print(item)
}
//方式2
for i in 0..<arr.count{
print(arr[i])
}
//方式3
for (index, item) in arr.enumerated(){
print("index = \(index)")
print("item = \(item)")
}
  1. 相同类型的数组才能用“+”合并。

map()映射函数的介绍

1
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

map()Array 提供的方法,它接收一个函数作为参数,旧数组中的每个元素都会被拿去执行这个函数而变成新数组的对应元素,这是一种让数组从 [X] 转化为 [Y] 的方式,你需要提供的就是 X -> Y 的转化方式,而不必新建一个临时数组。

transform参数:一个映射闭包。转换接受这个序列的一个元素作为它的参数,并返回一个相同类型或不同类型的转换值。

速记闭包语法一开始就很难做到这一点。map函数有一个参数,它是一个闭包(函数),当它在集合上循环时调用它。此闭包从集合中获取元素作为参数并返回结果。map函数返回数组中的这些结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
return jsonItems.map { (itemDesc: NSDictionary) -> ListItem in
let item = ListItem()
if let icon = itemDesc["icon"] as? String {
item.icon = UIImage(named: icon)
}
if let title = itemDesc["title"] as? String {
item.title = title
}
if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) {
item.url = url
}
return item
}

可以看出,可以用$0、$1、$2来表示调用闭包中参数,$0指代第一个参数,$1指代第二个参数,$2指代第三个参数,以此类推$n+1指代第n个参数,$后的数字代表参数的位置,一一对应。

但是我们现在 jsonItems.map 里面传入的参数函数的类型为 NSDictionary -> ListItem?,最后我们得到的是一个 [ListItem?] 数组,那些原来是不可用 NSDictionary 的位置就被我们替换成了 nil。比原来要好一些了,但还不够。

使用flatMap()
这个时候就轮到 flatMap() 来救场了。

flatMap() 与 map() 相似,但 flatMap() 用的是 T->U? 转化而不是 T->U 转化,而且如果转化出的数组元素是 nil 的话,就不会被添加到最后的结果数组里面。

在语法上,你可以这么理解,flatMap 就是你先使用 map 然后把结果数组“压平”(毕竟函数名就是这个意思),也就是从输出数组里去掉那些 nil。

字典 Dictionary

Swift字典的类型发现

  1. 使用let修饰不可变字典,可var修饰可变字典
  2. 定义一个空字典 empty collection literal requires an explicit type

    1
    var emptyDict: [String : Int] = [:]
  3. 如果字典中key对应的值不存在就会新增

1
2
3
var dict = ["name" : "Sam", "age" : 12] as [String : Any]
dict["gender"] = "man"
print(dict) //"["name": "Sam", "age": 12, "gender": "man"]\n"

所以合并2个字典,可以通过对遍历另个字典的key复制来合并字典

  1. 如果字典是多种混合类型的话,需要指定说明类型
1
var dict = ["name" : "Sam", "age" : 12]

这样会报以下错误:

1
2
3
4
 heterogeneous collection literal could only be inferred to '[String : Any]'; add explicit type annotation if this is intentional
var dict = ["name" : "Sam", "age" : 12]
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
as [String : Any]

指明类型:

1
var dict : [String : Any] = ["name" : "Sam", "age" : 12]

字典操作方法

1
2
3
4
5
6
7
8
9
    var dict = ["key" : "value"]
//修改方法
dict.updateValue("myValue", forKey: "key")
//移除方法
dict.removeValue(forKey:"key")
dict.removeAll()
let index = dict.index(forKey: "key")
dict.remove(at: index!)
print(dict)

字典遍历

1
2
3
4
5
6
7
8
9
10
11
12
//遍历key
for key in dict.keys{
print(key)
}
//遍历value
for value in dict.values{
print(value)
}
//遍历key & value
for (key, value) in dict{
print(key, value)
}

字典合并

1
2
3
4
5
6
var mergeDict = ["key1" : "value1"] as [String : Any]
let anotherDict = ["key2" : "value2"] as [String : Any]
for (key, value) in anotherDict{
mergeDict[key] = anotherDict[key]
}
print(mergeDict)

枚举类型 Enum

  • 概念介绍
    • 枚举定义了一个通用类型的一组相关的值,使你可以在你的代码中以一个安全的方式来使用这些值。
    • 在 C/OC 语言中枚举指定相关名称为一组整型值
    • Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值.也可以提供一个值是字符串,一个字符,或是一个整型值或浮点值
  • 枚举类型的语法

    • 使用enum关键词并且把它们的整个定义放在一对大括号内
      1
      2
      3
      enum SomeEnumeration {
      // enumeration definition goes here
      }
  • 枚举类型的定义

    • case关键词表明新的一行成员值将被定义
    • 不像 C 和 Objective-C 一样,Swift 的枚举成员在被创建时不会被赋予一个默认的整数值
      1
      2
      3
      4
      5
      6
      enum CompassPoint {
      case North
      case South
      case East
      case West
      }
  • 定义方式二:多个成员值可以出现在同一行上

1
2
3
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

给枚举类型赋值

  • 枚举类型赋值可以是字符串/字符/整型/浮点型
    • 注意如果有给枚举类型赋值,则必须在枚举类型后面明确说明具体的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1.枚举类型的赋值
enum CompassPoint : Int {
case North = 1
case South = 2
case East = 3
case West = 4
}

enum Planet {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}


// 2.枚举类型的使用
let p = Planet(rawValue: 3)

if let p = p {
switch p {
case .Mercury:
print("Mercury")
case .Venus:
print("Venus")
case .Earth:
print("Mercury")
case .Mars:
print("Mars")
case .Jupiter:
print("Jupiter")
case .Saturn:
print("Saturn")
case .Uranus:
print("Uranus")
case .Neptune:
print("Neptune")
}
}

结构体 Struct

结构体的介绍

  • 概念介绍
    • 结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合
    • 结构体(struct)指的是一种数据结构
    • 结构体是值类型,在方法中传递时是值传递
  • 结构的定义格式
1
2
3
struct 结构体名称 {
// 属性和方法
}

结构体的使用

  • 定义&使用结构体
1
2
3
4
5
6
7
8
// 初始化结构体
struct Location {
var x : Double
var y : Double
}

// 创建结构体
let location = Location(x: 90, y: 90)

结构体的增强

  • 扩充构造函数
    • 默认情况下创建Location时使用Location(x: x值, y: y值)
    • 但是为了让我们在使用结构体时更加的灵活,swift还可以对构造函数进行扩充
    • 扩充的注意点
      • 在扩充的构造函数中必须保证成员变量是有值的
      • 扩充的构造函数会覆盖原有的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Location {
var x : Double
var y : Double

init(x : Double, y : Double) {
self.x = x
self.y = y
}

init(xyString : String) {
let strs = xyString.componentsSeparatedByString(",")
x = Double(strs.first!)!
y = Double(strs.last!)!
}
}

let location = Location(x: 100, y: 100)
let location1 = Location(xyString: "100,100")
  • 为结构体扩充方法
    • 为了让结构体使用更加灵活,swift的结构体中可以扩充方法
    • 例子:为了Location结构体扩充两个方法
      • 向水平方向移动的方法
      • 向垂直方向移动的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Location {
var x : Double
var y : Double

init(x : Double, y : Double) {
self.x = x
self.y = y
}

init(xyString : String) {
let strs = xyString.componentsSeparatedByString(",")
x = Double(strs.first!)!
y = Double(strs.last!)!
}

mutating func moveH(x : Double) {
self.x += x
}

mutating func moveV(y : Double) {
self.y += y
}
}
  • 注意:
    • 如果我们使用的Location不是自己定义的,但是我们仍旧希望在自己的项目里扩展Location的操作
    • Swift也能帮我们达成,这个机制,叫做extension
    • Struct 出来的变量是 Immutable 的,用一个方法去改变变量里面的值的时候必须要加上一个关键词 mutatingmutating会创建一个可变类型指针,和给定的不可变指针指向同一块内存。

####s Swift中mutating关键字
Swift中protocol的功能比OC中强大很多,不仅能再class中实现,同时也适用于struct、enum。
使用 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,在设计接口的时候,也要考虑到使用者程序的扩展性。所以要多考虑使用mutating来修饰方法。

  • mutating可使用到的地方
  1. 结构体,枚举类型中的方法�声明为mutating
  2. extension中的方法声明为mutating
  3. protocol方法声明为mutating
1
2
3
4
5
6
7
8
9
extension Location {
mutating func moveH(x : Double) {
self.x += x
}

mutating func moveV(y : Double) {
self.y += y
}
}

Swift Tips

  1. 开发中,优先使用常量let, 减少变量所指向的内存地址被修改的可能。
  2. 少写self对当前类的引用,减少在闭包或者方法块中循环引用的概率。
  3. 在某个类定义属性的时候,如果是基本数据类型,最好要初始化。(KVC的赋值问题)
1
2
var finished: () -> ()?//错误写法,代表闭包的返回值为可选类型
var finished: (() -> ())?//正确写法,代表闭包为可选类型
  1. 官方建议在写代理方法或者协议时候,可以使用extension的形式(相当于Objective-C中的category),将代码整理到一起,比如常见的UITableView代理:
1
2
3
4
5
6
7
8
9
10
11
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as! UITableViewCell
return cell
}

//MARK: UITableViewDelegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}

例子 Sample

网络请求

  1. 封装Alamofire网络请求类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import Alamofire

// MARK:- 类的声明
class HomeViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

// 1. 设置导航栏
setupNaigationBar()

// 2. 请求数据
loadData()
}
}

// MARK:- 设置UI界面
extension HomeViewController {
fileprivate func setupNaigationBar() {
// 1.设置背景图片
navigationController?.navigationBar.setBackgroundImage(UIImage(named: "navigation_background"), for: .default)

// 2. 设置标题
navigationItem.titleView = UIImageView(image: UIImage(named: "navigation_logo"))

// 3. 设置右侧的搜索按钮
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "navigation_logo"), style: .plain, target: self, action: #selector(searchItemClick))
}
}

// MARK:- 事件监听函数
extension HomeViewController {
//@objc ——> 保留OC的特性
@objc fileprivate func searchItemClick() {
print("\(#function)")
}
}
  1. 调用网络请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// MARK:- 网络数据的请求
extension HomeViewController {
fileprivate func loadData() {
// 1.设置背景图片
NetworkTools.requestData(URLString: "http://www.xxxx.com/article/0-21.html", type: .get){
(result : Any) in
// 1. 将Any类型转为字典类型
guard let resultDict = result as? [String : Any] else { return }

// 2. 根据value的key取出内容
guard let dataArray = resultDict["key"] as? [[String : Any]] else{ return }

// 3. 遍历字典,将字典转成模型对象
for dict in dataArray {
self.newsModels.append(NewsModel(dict : dict))
}

// 4. 刷新表格
}
}
}

3.请求和解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import UIKit
import Alamofire

// MARK:- 类的声明
class HomeViewController: UIViewController {

// MARK: 懒加载熟悉
fileprivate lazy var newsModels : [NewsModel] = [NewsModel]()

// MARK: 系统回调函数
override func viewDidLoad() {
super.viewDidLoad()
// 1. 设置导航栏
setupNaigationBar()

// 2. 请求数据
loadData()
}
}

参考 Refer

  1. Beginning Swift 3 - Part 2: Variables | Ray Wenderlich
  2. Intermediate Swift 2 - Part 1: Introduction | Ray Wenderlich
  3. Programming in Swift - Part 1: Introduction | Ray Wenderlich
文章作者: MichaelMao
文章链接: http://frizzlefur.com/2018/01/01/iOS_Swift初见/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 MMao
我要吐槽下