热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Swift语法01

Swift简介查看Swift当前版本$xcrunswift--version简介Swift语言由苹果公司在2014年推出,用来撰写OSX和iOS应用程序2014年,在AppleWW
Swift 简介

查看Swift当前版本

$ xcrun swift --version

简介

  • Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序
  • 2014 年,在 Apple WWDC 发布
  • 2016.3月 Apple WWDC 支持 Linux系统,国外已经有人开始用Swift 写服务端

历史

  • 2010 年 7 月,苹果开发者工具部门总监克里斯·拉特纳开始着手 Swift 编程语言的设计
  • 用一年时间,完成基本架构
  • Swift 大约历经 4 年的开发期,2014 年 6 月发布
  • 在 2015.12.3 开源, 现在GitHub上超过30000个星星

从发布至今,苹果的每一个举措都彰显其大力推广 Swift 的决心

版本

  • 正式版 Swift 3.0, Xcode 8.0

Swift 特色

  • Swfit3.0 是变化最大的一个版本,全面的去OC化了,Foundation框架的NS前缀去掉了
  • 可以使用现有的 CocoaCocoa Touch 框架
  • 苹果宣称 Swift的特点是:快速、现代、安全、互动,而且明显优于 Objective-C 语言
  • 取消了预编译指令包括宏
  • Swift 取消了 Objective-C 的指针及其他不安全访问的使用
  • 舍弃 Objective-C 早期应用 Smalltalk 的语法,全面改为句点表示法
  • 提供了类似 Java 的名字空间(namespace)、泛型(generic)、运算对象重载(operator overloading
  • Swift 被简单的形容为 “没有 C 的 Objective-C”(Objective-C without the C)

Swift 现状

  • 目前国内有些公司的新项目已经直接采用 Swift 开发
  • 目前很多公司都在做 Swift 的人才储备
  • 应聘时,会 Swift 开发无疑会增加自身筹码
  • 很多公司现在已经深陷Swfit无法自拔了,包括唐巧的猿题库

为什么要学习 Swift?

  1. 从4月份开始,苹果提供的资料已经没有 OC 的了,这说明苹果推动 Swift 的决心
  2. OC 源自于 smalltack-c,迄今已经有 40 多年的历史,虽然 OC 的项目还会在未来持续一段时间,但是更换成 Swift 是未来必然的趋势
  3. 现在很多公司都注重人才储备,如果会Swift,就业会有很大的优势,简历中如果写上会 Swift,虽然面试中虽然不会怎么被问到,但对于薪资提升有很大帮助,同时可以从另外一个侧面证明我们是有自学能力的人,这是所有企业都需要的
  4. Swift 里面融合了很多其他面向对象语言的思想,不像OC那么封闭,学会 Swift,再转其他语言会轻松很多
  5. Swift 毕竟也是出身自苹果,整体程序开发思路和 OC 是一样的,等 Swift 项目讲完后,大家完全可以用同样的思路写出 OC 的来,而且在翻写的过程中,能够对很多原本忽略的 OC 基本功有很大的加强和改善
Swift 开发快速体验

目标

  • playground 快速体验 & 学习资源分享
  • 项目开发快速体验,了解 Swift 基本程序结构

学习资源

  • 苹果官方博客 https://developer.apple.com/swift/blog/
  • 苹果官方 Swift 2.0 电子书 https://itunes.apple.com/us/book/id1002622538
  • 2.0 中文版 http://wiki.jikexueyuan.com/project/swift/
  • 100个Swift必备tips,作者王巍,建议购买实体书 http://onevcat.com
基本语法

目标

  • 熟悉 Swift 基本语法
    • 常量 & 变量
    • 可选项
    • 控制流
      • if
      • 三目
      • if let
      • guard
      • switch
    • 字符串
    • 循环
    • 集合
      • 数组
      • 集合
变量和常量

需要掌握

定义

  • let 定义常量,一经赋值不允许再修改
  • var 定义变量,赋值之后仍然可以修改

//: # 常量
//: 定义常量并且直接设置数值
let x = 20
//: 常量数值一经设置,不能修改,以下代码会报错
// x = 30
//: 使用 `: 类型`,仅仅只定义类型,而没有设置数值
let x1: Int
//: 常量有一次设置数值的机会,以下代码没有问题,因为 x1 还没有被设置数值
x1 = 30
//: 一旦设置了数值之后,则不能再次修改,以下代码会报错,因为 x1 已经被设置了数值
// x1 = 50
//: # 变量
//: 变量设置数值之后,可以继续修改数值
var y = 200
y = 300

自动推导

  • Swift能够根据右边的代码,推导出变量的准确类型
  • 通常在开发时,不需要指定变量的类型
  • 如果要指定变量,可以在变量名后使用:,然后跟上变量的类型

重要技巧:Option + Click 可以查看变量的类型

[图片上传失败…(image-1c3941-1536302339170)]

没有隐式转换!!!

  • Swift 对数据类型要求异常严格
  • 任何时候,都不会做隐式转换

如果要对不同类型的数据进行计算,必须要显式的转换

let x2 = 100
let y2 = 10.5
let num1 = Double(x2) + y2
let num2 = x2 + Int(y2)

let & var 的选择

  • 应该尽量先选择常量,只有在必须修改时,才需要修改为 var
  • 在 Xcode 7.0 中,如果没有修改变量,Xcode 会提示修改为 let
Optional 可选值

需要掌握

  • 理解可选项的概念
  • 理解可选项的概念 –> 要么有值,要么为nil
  • 知道可选项的规则 –> 参与计算需要强制解包
  • 知道两个符号
    1. ‘?’ 定义可选项
    2. ‘!’ 对可选类型进行强制解包, 程序员一定要对解包负责
  • 常量的可选项使用前需要设置初始值
  • 变量的可选项默认是nil

介绍

  • Optional 是 Swift 的一大特色,也是 Swift 初学者最容易困惑的问题
  • 定义变量时,如果指定是可选的,表示该变量可以有一个指定类型的值,也可以是 nil
  • 定义变量时,在类型后面添加一个 ?,表示该变量是可选的
  • 变量可选项的默认值是 nil
  • 常量可选项没有默认值,主要用于在构造函数中给常量设置初始数值

//: num 可以是一个整数,也可以是 nil,注意如果为 nil,不能参与计算
let num: Int? = 10

  • 如果 Optional 值是 nil,不允许参与计算
  • 只有解包(unwrap)后才能参与计算
  • 在变量后添加一个 !,可以强行解包

注意:必须要确保解包后的值不是 nil,否则会报错

//: num 可以是一个整数,也可以是 nil,注意如果为 nil,不能参与计算
let num: Int? = 10
//: 如果 num 为 nil,使用 `!` 强行解包会报错
let r1 = num! + 100
//: 使用以下判断,当 num 为 nil 时,if 分支中的代码不会执行
if let n = num {
let r = n + 10
}

常见错误

unexpectedly found nil while unwrapping an Optional value

翻译

在[解包]一个可选值时发现 nil

?? 运算符

  • ?? 运算符可以用于判断 变量/常量 的数值是否是 nil,如果是则使用后面的值替代
  • 在使用 Swift 开发时,?? 能够简化代码的编写

let num: Int? = nil
let r1 = (num ?? 0) + 10
print(r1)
控制流

if

  • Swift 中没有 C 语言中的非零即真概念
  • 在逻辑判断时必须显示地指明具体的判断条件 true / false
  • if 语句条件的 () 可以省略
  • 但是 {} 不能省略

let num = 200
if num <10 {
print("比 10 小")
} else if num > 100 {
print("比 100 大")
} else {
print("10 ~ 100 之间的数字")
}

三目运算

  • Swift 中的 三目 运算保持了和 OC 一致的风格

var a = 10
var b = 20
let c = a > b ? a : b
print(c)

适当地运用三目,能够让代码写得更加简洁

可选项判断

  • 由于可选项的内容可能为 nil,而一旦为 nil 则不允许参与计算
  • 因此在实际开发中,经常需要判断可选项的内容是否为 nil

单个可选项判断

let url = NSURL(string: "http://www.baidu.com")
//: 方法1: 强行解包 - 缺陷,如果 url 为空,运行时会崩溃
let request = NSURLRequest(URL: url!)
//: 方法2: 首先判断 - 代码中仍然需要使用 `!` 强行解包
if url != nil {
let request = NSURLRequest(URL: url!)
}

可选项条件判断

//: 1> 初学 swift 一不小心就会让 if 的嵌套层次很深,让代码变得很丑陋
if let u = url {
if u.host == "www.baidu.com" {
let request = NSURLRequest(URL: u)
}
}
//: 2> 使用 ',' 可以获取前面赋值对象
if let u = url , u.host == "www.baidu.com" {
let request = NSURLRequest(URL: u)
}

  • 小结
    • if let 不能与使用 &&|| 等条件判断

多个可选项判断

//: 3> 可以使用 `,` 同时判断多个可选项是否为空
let oName: String? = "张三"
let oNo: Int? = 100
if let name = oName {
if let no = oNo {
print("姓名:" + name + " 学号: " + String(no))
}
}
if let name = oName, let no = oNo {
print("姓名:" + name + " 学号: " + String(no))
}

判断之后对变量需要修改

let oName: String? = "张三"
let oNum: Int? = 18
if var name = oName, num = oNum {
name = "李四"
num = 1
print(name, num)
}

guard

  • guard 是与 if let 相反的语法,Swift 2.0 推出的

let oName: String? = "张三"
let oNum: Int? = 18
guard let name = oName else {
print("name 为空")
return
}
guard let num = oNum else {
print("num 为空")
return
}
// 代码执行至此,name & num 都是有值的
print(name)
print(num)

  • 在程序编写时,条件检测之后的代码相对是比较复杂的
  • 使用 guard 的好处
    • 能够判断每一个值
    • 在真正的代码逻辑部分,省略了一层嵌套

switch

  • switch 不再局限于整数
  • switch 可以针对任意数据类型进行判断
  • 不再需要 break
  • 每一个 case后面必须有可以执行的语句
  • 要保证处理所有可能的情况,不然编译器直接报错,不处理的条件可以放在 default 分支中
  • 每一个 case 中定义的变量仅在当前 case 中有效,而 OC 中需要使用 {}

let score = "优"
switch score {
case "优":
let name = "学生"
print(name + "80~100分")
case "良": print("70~80分")
case "中": print("60~70分")
case "差": print("不及格")
default: break
}
for 循环

  • OC 风格的循环 &#8216;++&#8217;语法已经废弃

var sum = 0
for var i = 0; i <10; i++ {
sum += I
}
print(sum)

  • for-in,0..<10 表示从0到9

sum = 0
for i in 0..<10 {
sum += I
}
print(sum)

  • 范围 0&#8230;10 表示从0到10

sum = 0
for i in 0...10 {
sum += I
}
print(sum)

  • 省略下标
    • _ 能够匹配任意类型
    • _ 表示忽略对应位置的值

for _ in 0...10 {
print("hello")
}
字符串

在 Swift 中绝大多数的情况下,推荐使用 String 类型

  • String 是一个结构体,性能更高
    • String 目前具有了绝大多数 NSString 的功能
    • String 支持直接遍历
  • NSString 是一个 OC 对象,性能略差
  • Swift 提供了 StringNSString 之间的无缝转换

字符串演练

  • 遍历字符串中的字符

for s in str.characters {
print(s)
}

  • 字符串长度

// 返回以字节为单位的字符串长度,一个中文占 3 个字节
let len1 = str.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
// 返回实际字符的个数
let len2 = str.characters.count

  • 字符串拼接
    • 直接在 &#8220;&#8221; 中使用 \(变量名) 的方式可以快速拼接字符串

let str1 = "Hello"
let str2 = "World"
let i = 32
str = "\(i) 个 " + str1 + " " + str2

我和我的小伙伴再也不要考虑 stringWithFormat 了 :D

  • 可选项的拼接
    • 如果变量是可选项,拼接的结果中会有 Optional
    • 为了应对强行解包存在的风险,苹果提供了 ?? 操作符
    • ?? 操作符用于检测可选项是否为 nil
      • 如果不是 nil,使用当前值
      • 如果是 nil,使用后面的值替代

let str1 = "Hello"
let str2 = "World"
let i: Int? = 32
str = "\(i ?? 0) 个 " + str1 + " " + str2

  • 格式化字符串
    • 在实际开发中,如果需要指定字符串格式,可以使用 String(format:...) 的方式

let h = 8
let m = 23
let s = 9
let timeString = String(format: "%02d:%02d:%02d", arguments: [h, m, s])
let timeStr = String(format: "%02d:%02d:%02d", h, m, s)

String & Range 的结合

  • 在 Swift 中,StringRange连用时,语法结构比较复杂
  • 如果不习惯 Swift 的语法,可以将字符串转换成 NSString 再处理

let helloString = "我们一起飞"
(helloString as NSString).substringWithRange(NSMakeRange(2, 3))

  • 使用 Range 的写法 前面不要试图去掌握这段代码,每一个版本都在变化,没用

let str = "爱笑的人运气不会太差哟"
let startIndex = str.index(str.startIndex, offsetBy: 2)
let endIndex = str.index(str.endIndex, offsetBy: -1)
let subStr1 = str.substring(to: startIndex)
print(subStr1)
let subStr2 = str.substring(from: endIndex)
print(subStr2)
let subStr3 = str.substring(with: startIndex.. print(subStr3)
//String 和 NSString 之间可以相互转换 可以转换为 NSString 来截取子串
let NStr = (str as NSString).substring(with: NSRange(location: 1, length: 2))
print(NStr)
集合

数组

  • 数组使用 [] 定义,这一点与 OC 相同

//: [Int]
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  • 遍历

for num in numbers {
print(num)
}

  • 通过下标获取指定项内容

let num1 = numbers[0]
let num2 = numbers[1]

  • 可变&不可变
    • let 定义不可变数组
    • var 定义可变数组

let array = ["zhangsan", "lisi"]
//: 不能向不可变数组中追加内容
//array.append("wangwu")
var array1 = ["zhangsan", "lisi"]
//: 向可变数组中追加内容
array1.append("wangwu")

  • 数组的类型
    • Swift中数组建议存放相同类型的元素
    • 如果初始化时,所有内容类型不一致,需要手动将数组的类型转换为[Any]

//: array1 仅允许追加 String 类型的值
//array1.append(18)
var array2: [Any] = ["zhangsan", 18]
//: 在 Swift 中,数字可以直接添加到集合,不需要再转换成 `NSNumber`
array2.append(100)

  • 数组的定义和实例化
    • 使用 : 可以只定义数组的类型
    • 实例化之前不允许添加值
    • 使用 [类型]() 可以实例化一个空的数组

var array3: [String]
//: 实例化之前不允许添加值
//array3.append("laowang")
//: 实例化一个空的数组
array3 = [String]()
array3.append("laowang")

  • 数组的合并
    • 必须是相同类型的数组才能够合并
    • 开发中,通常数组中保存的对象类型都是一样的!

array3 += array1
//: 必须是相同类型的数组才能够合并,以下两句代码都是不允许的
//array3 += array2
//array2 += array3

  • 数组的删除

//: 删除指定位置的元素
array3.removeAtIndex(3)
//: 清空数组
array3.removeAll()

字典

  • 定义
    • 同样使用 [] 定义字典
    • let 不可变字典
    • var 可变字典
    • [String : Any] 是最常用的字典类型

//: [String : Any] 是最常用的字典类型
var dict = ["name": "zhangsan", "age": 18]

  • 赋值
    • 赋值直接使用 dict[key] = value 格式
    • 如果 key 不存在,会设置新值
    • 如果 key 存在,会覆盖现有值

//: * 如果 key 不存在,会设置新值
dict["title"] = "boss"
//: * 如果 key 存在,会覆盖现有值
dict["name"] = "lisi"
dict

  • 遍历
    • kv 可以随便写
    • 前面的是 key
    • 后面的是 value

//: 遍历
for (k, v) in dict {
print("\(k) ~~~ \(v)")
}

  • 合并字典
    • 如果 key 不存在,会建立新值,否则会覆盖现有值

//: 合并字典
var dict1 = [String: NSObject]()
dict1["nickname"] = "大老虎"
dict1["age"] = 100
//: 如果 key 不存在,会建立新值,否则会覆盖现有值
for (k, v) in dict1 {
dict[k] = v
}
print(dict)
错误处理

需要了解

  1. 知道错误处理机制的三种方式
  2. 开发中通常使用第二种 try?
  3. 在 Swfit 3.0 中 Foundation 的部分类去除了 NS 前缀
代码演练,网络访问反序列化数据
  • 网络访问代码

let url = URL(string: "http://www.weather.com.cn/data/sk/101010100.html")!
URLSession.shared.dataTask(with: url, completionHandler: { (data, _, error) in
if error != nil {
print(error)
return
}
}).resume()

  • JSON反序列化

// 方式1:强try.加载失败直接崩溃
let dict = try! JSONSerialization.jsonObject(with: data!, options: [])
print(dict)
// 方式2:可选try.反序列化失败返回nil
let dict = try? JSONSerialization.jsonObject(with: data!, options: [])
print(dict)
// 方式3:默认try.返回序列化失败可以捕捉详细信息
do {
let dict = try JSONSerialization.jsonObject(with: data!, options: [])
print(dict)
}catch {
print(error)
}
函数

目标

  • 函数
    • 定义格式
    • 外部参数
    • 无返回值的三种情况
  • 闭包
    • 闭包的定义
    • 尾随闭包
    • 循环引用
    • OC Block复习
函数

目标

  • 掌握函数的定义
  • 掌握外部参数的用处
  • 掌握无返回类型的三种函数定义方式

代码实现

  • 函数的定义
    • 格式 func 函数名(行参列表) -> 返回值 {代码实现}
    • 调用 let result = 函数名(值1, 参数2: 值2...)

//Swfit 3.0
func sum(a: Int, b: Int) -> Int {
return a + b
}
let result = sum(a:10, b: 20)
//Swfit 2.0
func sum(a: Int, b: Int) -> Int {
return a + b
}
let result = sum(10, b: 20)

  • 没有返回值的函数,一共有三种写法
    • 省略
    • ()
    • Void

func demo(str: String) -> Void {
print(str)
}
func demo1(str: String) -> () {
print(str)
}
func demo2(str: String) {
print(str)
}
demo("hello")
demo1("hello world")
demo2("olleh")

  • 外部参数
    • 在形参名前再增加一个外部参数名,能够方便调用人员更好地理解函数的语义
    • 格式:func 函数名(外部参数名 形式参数名: 形式参数类型) -> 返回值类型 { // 代码实现 }
    • Swift 2.0 中,默认第一个参数名省略

func sum1(num1 a: Int, num2 b: Int) -> Int {
return a + b
}
sum1(num1: 10, num2: 20)

  • 函数的内部函数

func demo() {
//该内部函数的范围只能在 demo函数中可以被访问
func sum() {
print("哈哈哈")
}
//必须先声明内部函数才能调用调用
sum()
}

函数格式小结

// 格式:func 函数名(形参1: 类型 = 默认值, _ 形参2: 类型 = 默认值...) -> 返回值 { // 代码实现 }
// 说明:包含默认值的函数可以不用传递,并且可以任意组合
//
// 格式:func 函数名(形参1: 类型, _ 形参2: 类型...) -> 返回值 { // 代码实现 }
// 说明:_ 可以忽略外部参数,与其他语言的函数风格更加类似
//
// 格式:func 函数名(外部参数1 形参1: 类型, 外部参数2 形参2: 类型...) -> 返回值 { // 代码实现 }
// 说明:外部参数名供外部调用使用,形参 在函数内部使用
//
// 格式:func 函数名(形参列表) -> 返回值 { // 代码实现 }
闭包

与 OC 中的 Block 类似,闭包主要用于异步操作执行完成后的代码回调,网络访问结果以参数的形式传递给调用方

目标

  • 掌握闭包的定义
  • 掌握闭包的概念和用法
  • 了解尾随闭包的写法
  • 掌握解除循环引用的方法

OC 中 Block 概念回顾

  • 闭包类似于 OC 中的 Block
    • 预先定义好的代码
    • 在需要时执行
    • 可以当作参数传递
    • 可以有返回值
    • 包含 self 时需要注意循环引用
闭包的定义
  • 函数实际上是一个特殊的闭包, 函数知识闭包的一种新式
  • 可以结合函数的使用 来学习闭包
  • 定义一个函数在函数内部定义一个没有参数也没有返回值的内部函数

func demo1A() {
func sum() {
print("哈哈哈")
}
sum()
}

  • 定义一个闭包 实现函数的内部函数一样的效果
    • 闭包 = { (行参) -> 返回值 in // 代码实现 }
    • in 用于区分函数定义和代码实现

//使用闭包实现
func demo1B() {
//定义代码块 () -> ()
//没有参数没有返回值的闭包
let closure = { () -> Void in
print("OK")
}
//执行代码块
closure()
}

  • 定义一个函数,内部函数有参数也有返回值

func demo3A() {
func sum(num1 a: Int, num2 b: Int) -> Int{
return a + b
}
let result = sum(num1: 10, num2: 20)
print(result)
}

  • 定义一个函数,实现上述函数的内部函数

func demo3B() {
let closure = { (num1 a: Int, num2 b: Int ) -> Int in
return a + b
}
let result = closure(num1: 100, num2: 200)
print(result)
}

  • 定义一个闭包 实现函数的内部函数一样的效果
    • 闭包 = { (行参) -> 返回值 in // 代码实现 }
    • in 用于区分函数定义和代码实现
  • 最简单的闭包,如果没有参数/返回值,则 参数/返回值/in 统统都可以省略
    • { 代码实现 }

let closure = {
print("hello")
}

尾随闭包

  • 当闭包是函数的最后一个参数时,函数的参数的小括号可以提前关闭,闭包可以写在小括号后面当做尾随闭包

闭包的三种简写形式只需要了解即可,在实际开发中做到看到不陌生即可,直接使用系统提示的类型

基本使用

GCD 异步

  • 模拟在后台线程加载数据

func loadData() {
DispatchQueue.global().async {
//假装耗时
Thread.sleep(forTimeInterval: 2)
}
}

  • 尾随闭包,如果闭包是最后一个参数,可以用以下写法

自定义闭包参数,实现主线程回调

  • 添加没有参数,没有返回值的闭包

override func viewDidLoad() {
super.viewDidLoad()
loadData {
print("完成回调")
}
}
//异步请求网络数据没在主队列中回调 更新UI
//TODO: 待会解释该关键词
//@escaping
func loadData(a: Int,finished: @escaping (String) -> () ) {
DispatchQueue.global().async {
//假装耗时
Thread.sleep(forTimeInterval: 2)
//获取结果
let res = "老王"
//在主队列中回调数据
DispatchQueue.main.async {
//回调数据 只负责请求数据 不负责更新UI
//从外界传递闭包
finished(res)
}
}
}

  • 添加回调参数

override func viewDidLoad() {
super.viewDidLoad()
//定义一个有参数没有返回值的闭包当做参数传递
let closure = { (res: String) -> () in
print("我是请求结果: \(res)")
}
loadData(a:20,finished: closure)
//使用尾随闭包的方式滴啊用
loadData(a: 20) { (res) in
print(res)
}
}
循环引用

  • 建立 NetworkTools 对象

class NetworkTools: NSObject {
/// 加载数据
///
/// - parameter finished: 完成回调
func loadData(finished: () -> ()) {
print("开始加载数据...")
// ...
finished()
}
deinit {
print("网络工具 88")
}
}

  • 实例化 NetworkTools 并且加载数据

class ViewController: UIViewController {
var tools: NetworkTools?
override func viewDidLoad() {
super.viewDidLoad()
tools = NetworkTools()
tools?.loadData() {
print("come here \(self.view)")
}
}
/// 与 OC 中的 dealloc 类似,注意此函数没有()
deinit {
print("控制器 88")
}
}

运行不会形成循环引用,因为 loadData 执行完毕后,就会释放对 self 的引用

  • 修改 NetworkTools,定义回调闭包属性

/// 完成回调属性
var finishedCallBack: (()->())?
/// 加载数据
///
/// - parameter finished: 完成回调
func loadData(finished: () -> ()) {
self.finishedCallBack = finished
print("开始加载数据...")
// ...
working()
}
func working() {
finishedCallBack?()
}
deinit {
print("网络工具 88")
}

运行测试,会出现循环引用

解除循环引用

  • 与 OC 类似的方法

/// 类似于 OC 的解除引用
func demo() {
weak var weakSelf = self
tools?.loadData() {
print("\(weakSelf?.view)")
}
}

  • Swift 推荐的方法

loadData { [weak self] in
print("\(self?.view)")
}

  • 还可以

loadData { [unowned self] in
print("\(self.view)")
}

闭包(Block) 的循环引用小结

  • Swift

    • [weak self]
      • self是可选项,如果self已经被释放,则为nil
    • [unowned self]
      • self不是可选项,如果self已经被释放,则出现野指针访问
  • Objc

    • __weak typeof(self) weakSelf;
      • 如果self已经被释放,则为nil
    • __unsafe_unretained typeof(self) weakSelf;
      • 如果self已经被释放,则出现野指针访问
面相对象

目标

  • 构造函数
    • 构造函数的基本概念
    • 构造函数的执行顺序
    • KVC 在构造函数中的使用及原理
    • 便利构造函数
    • 析构函数
    • 区分 重载重写
  • 懒加载
  • 只读属性(计算型属性)
  • 设置模型数据(didSet
构造函数基础

构造函数是一种特殊的函数,主要用来在创建对象时初始化对象,为对象成员变量设置初始值,在 OC 中的构造函数是 initWithXXX,在 Swift 中由于支持函数重载,所有的构造函数都是 init

构造函数的作用

  • 分配空间 alloc
  • 设置初始值 init

必选属性

  • 自定义 Person 对象

class Person: NSObject {
/// 姓名
var name: String
/// 年龄
var age: Int
}

提示错误 Class 'Person' has no initializers -> 'Person' 类没有实例化器s

原因:如果一个类中定义了必选属性,必须通过构造函数为这些必选属性分配空间并且设置初始值

  • 重写 父类的构造函数

/// `重写`父类的构造函数
override init() {
}

提示错误 Property 'self.name' not initialized at implicitly generated super.init call -> 属性 'self.name' 没有在隐式生成的 super.init 调用前被初始化

  • 手动添加 super.init() 调用

/// `重写`父类的构造函数
override init() {
super.init()
}

提示错误 Property 'self.name' not initialized at super.init call -> 属性 'self.name' 没有在 super.init 调用前被初始化

  • 为比选属性设置初始值

/// `重写`父类的构造函数
override init() {
name = "张三"
age = 18
super.init()
}

小结

  • 非 Optional 属性,都必须在构造函数中设置初始值,从而保证对象在被实例化的时候,属性都被正确初始化
  • 在调用父类构造函数之前,必须保证本类的属性都已经完成初始化
  • Swift 中的构造函数不用写 func

子类的构造函数

  • 自定义子类时,需要在构造函数中,首先为本类定义的属性设置初始值
  • 然后再调用父类的构造函数,初始化父类中定义的属性

/// 学生类
class Student: Person {
/// 学号
var no: String
override init() {
no = "001"
super.init()
}
}

小结

  • 先调用本类的构造函数初始化本类的属性
  • 然后调用父类的构造函数初始化父类的属性
  • Xcode 7 beta 5之后,父类的构造函数会被自动调用,强烈建议写 super.init(),保持代码执行线索的可读性
  • super.init() 必须放在本类属性初始化的后面,保证本类属性全部初始化完成

Optional 属性

  • 将对象属性类型设置为 Optional

class Person: NSObject {
/// 姓名
var name: String?
/// 年龄
var age: Int?
}

  • 可选属性不需要设置初始值,默认初始值都是 nil
  • 可选属性是在设置数值的时候才分配空间的,是延迟分配空间的,更加符合移动开发中延迟创建的原则
重载构造函数
  • Swift 中支持函数重载,同样的函数名,不一样的参数类型

/// `重载`构造函数
///
/// - parameter name: 姓名
/// - parameter age: 年龄
///
/// - returns: Person 对象
init(name: String, age: Int) {
self.name = name
self.age = age
super.init()
}

注意事项

  • 如果重载了构造函数,但是没有实现默认的构造函数 init(),则系统不再提供默认的构造函数
  • 原因,在实例化对象时,必须通过构造函数为对象属性分配空间和设置初始值,对于存在必选参数的类而言,默认的 init() 无法完成分配空间和设置初始值的工作

调整子类的构造函数

  • 重写父类的构造函数

/// `重写`父类构造函数
///
/// - parameter name: 姓名
/// - parameter age: 年龄
///
/// - returns: Student 对象
override init(name: String, age: Int) {
no = "002"
super.init(name: name, age: age)
}

  • 重载构造函数

/// `重载`构造函数
///
/// - parameter name: 姓名
/// - parameter age: 年龄
/// - parameter no: 学号
///
/// - returns: Student 对象
init(name: String, age: Int, no: String) {
self.no = no
super.init(name: name, age: age)
}

注意:如果是重载的构造函数,必须 super 以完成父类属性的初始化工作

重载重写

  • 重载,函数名相同,参数名/参数类型/参数个数不同
    • 重载函数并不仅仅局限于构造函数
    • 函数重载是面相对象程序设计语言的重要标志
    • 函数重载能够简化程序员的记忆
    • OC 不支持函数重载,OC 的替代方式是 withXXX...
  • 重写,子类需要在父类拥有方法的基础上进行扩展,需要 override 关键字
KVC 字典转模型构造函数

/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: Any]) {
setValuesForKeysWithDictionary(dict)
}

  • 以上代码编译就会报错!

  • 原因:

    • KVC 是 OC 特有的,KVC 本质上是在运行时,动态向对象发送 setValue:ForKey: 方法,为对象的属性设置数值
    • 因此,在使用 KVC 方法之前,需要确保对象已经被正确实例化
  • 添加 super.init() 同样会报错

  • 原因:

    • 必选属性必须在调用父类构造函数之前完成初始化分配工作
  • 讲必选参数修改为可选参数,调整后的代码如下:

/// 个人模型
class Person: NSObject {
/// 姓名
var name: String?
/// 年龄
var age: Int?
/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
}

运行测试,仍然会报错

错误信息:this class is not key value coding-compliant for the key age. -> 这个类的键值 age 与 键值编码不兼容

  • 原因:
    • 在 Swift 中,如果属性是可选的,在初始化时,不会为该属性分配空间
    • 而 OC 中基本数据类型就是保存一个数值,不存在可选的概念
  • 解决办法:给基本数据类型设置初始值
  • 修改后的代码如下:

/// 姓名
var name: String?
/// 年龄
var age: Int? = 0
/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: Any]) {
super.init()
setValuesForKeysWithDictionary(dict)
}

提示:在定义类时,基本数据类型属性一定要设置初始值,否则无法正常使用 KVC 设置数值

KVC 函数调用顺序

init(dict: [String: Any]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
override func setValue(value: Any?, forKey key: String) {
print("Key \(key) \(value)")
super.setValue(value, forKey: key)
}
// `NSObject` 默认在发现没有定义的键值时,会抛出 `NSUndefinedKeyException` 异常
override func setValue(value: Any?, forUndefinedKey key: String) {
print("UndefinedKey \(key) \(value)")
}

  • setValuesForKeysWithDictionary 会按照字典中的 key 重复调用 setValue:forKey 函数
  • 如果没有实现 forUndefinedKey 函数,程序会直接崩溃
    • NSObject 默认在发现没有定义的键值时,会抛出 NSUndefinedKeyException 异常
  • 如果实现了 forUndefinedKey,会保证 setValuesForKeysWithDictionary 继续遍历后续的 key
  • 如果父类实现了 forUndefinedKey,子类可以不必再实现此函数

子类的 KVC 函数

/// 学生类
class Student: Person {
/// 学号
var no: String?
}

  • 如果父类中已经实现了父类的相关方法,子类中不用再实现相关方法
convenience 便利构造函数
  • 条件判断,只有满足条件,才实例化对象,可以防治造成不必要的内存开销
  • 简化对象的创建
  • 本身不负责属性的创建和初始化工作
特点
  • 默认情况下,所有的构造方法都是指定构造函数 Designated
  • convenience 关键字修饰的构造方法就是便利构造函数
  • 便利构造函数具有以下特点:
    • 可以构造失败 返回 nil,
    • 只有便利构造函数中可以调用 self.init()
    • 便利构造函数不能被重写或者 super

/// `便利构造函数`
///
/// - parameter name: 姓名
/// - parameter age: 年龄
///
/// - returns: Person 对象,如果年龄过小或者过大,返回 nil
convenience init?(name: String, age: Int) {
if age <20 || age > 100 {
return nil
}
self.init(dict: ["name": name, "age": age])
}

注意:在 Xcode 中,输入 self.init 时没有智能提示

/// 学生类
class Student: Person {
/// 学号
var no: String?
convenience init?(name: String, age: Int, no: String) {
self.init(name: name, age: age)
self.no = no
}
}

便利构造函数应用场景

  • 根据给定参数判断是否创建对象,而不像指定构造函数那样必须要实例化一个对象出来
  • 在实际开发中,可以对已有类的构造函数进行扩展,利用便利构造函数,简化对象的创建

构造函数小结

  • 指定构造函数必须调用其直接父类的的指定构造函数(除非没有父类)
  • 便利构造函数必须调用同一类中定义的其他指定构造函数或者用 self. 的方式调用父类的便利构造函数
  • 便利构造函数可以返回 nil
  • 便利构造函数可以被继承
懒加载

在 iOS 开发中,懒加载是无处不在的

  • 懒加载的格式如下:

lazy var person: Person = {
print("懒加载")
return Person()
}()

  • 懒加载本质上是一个闭包
  • 懒加载的简单写法

lazy var demoPerson: Person = Person()

推荐阅读
  • GPT-3发布,动动手指就能自动生成代码的神器来了!
    近日,OpenAI发布了最新的NLP模型GPT-3,该模型在GitHub趋势榜上名列前茅。GPT-3使用的数据集容量达到45TB,参数个数高达1750亿,训练好的模型需要700G的硬盘空间来存储。一位开发者根据GPT-3模型上线了一个名为debuid的网站,用户只需用英语描述需求,前端代码就能自动生成。这个神奇的功能让许多程序员感到惊讶。去年,OpenAI在与世界冠军OG战队的表演赛中展示了他们的强化学习模型,在限定条件下以2:0完胜人类冠军。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 众筹商城与传统商城的区别及php众筹网站的程序源码
    本文介绍了众筹商城与传统商城的区别,包括所售产品和玩法不同以及运营方式不同。同时还提到了php众筹网站的程序源码和方维众筹的安装和环境问题。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • GAMETECH腾讯云游戏行业技术沙龙成都站圆满落幕
    11月13日,由腾讯云主办、游戏茶馆协办的2020年首场GAME-TECH腾讯云游戏行业技术沙龙在成都圆满落幕。本次沙龙邀请了腾讯云游戏行业解决方案总监宋永周、腾讯云游戏行业高级解决方案架构师曾梓恩、腾讯云游戏行业高级产品架构师郑晓曦、腾讯云游戏行业高级解决方案架构师温球良和天美L1(王者荣耀)服务器技术副总监杨光,为参会同行们带来了干货满满的技术建议。本文介绍了腾讯云游戏云的优势和为不同游戏研运场景提供的服务。腾讯云在中国游戏云服务市场领跑,成为众多游戏开发者的合作伙伴。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 2016 linux发行版排行_灵越7590 安装 linux (manjarognome)
    RT之前做了一次灵越7590黑苹果炒作业的文章,希望能够分享给更多不想折腾的人。kawauso:教你如何给灵越7590黑苹果抄作业​zhuanlan.z ... [详细]
author-avatar
仇顺嵐
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有