Swift是一门开源的编程语言,苹果于2014WWDC发布,用于开发iOS、OS X和watchOS应用程序
注释
// 这是单行注释
/* 这是多行注释 */
/* 这是第一个多行注释的开头
/* Swift的多行注释可以嵌套在别的多行注释之中 */
这是第一个多行注释的结尾 */
分号
Swift不要求每行语句的结尾使用分号(;),也可以使用;但当在同一行书写多条语句时,需要分号分隔(;)
var str = "Hello World"; print(str)
标识符
如果一定要使用关键字作为标识符,可以在关键字前后添加重音符(`)
let `class` = "try"
Swift空格
运算符不能直接跟在变量或常量后面
let a= 1 + 2
=> 报错
let a = 1+ 2
==> 报错
let a = 1 + 2 => 推荐
let a = 1+2 => 也可,不报错
打印输出
Swift使用print函数打印输出
print("Hello World")
完整:
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single
/// space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
print函数是一个全局函数
for n in 0...10{
print("\(n)", terminator: "")
}
=> 012345678910
接收用户的输入: readLine()函数
let input = readLine()
数据类型
编程语言 -> 使用各种数据类型来存储不同信息
所有变量都具有数据类型,以决定能够存储哪种数据。
Int: 整型
UInt: 无符号整型
尽量使用Int,统一使用Int可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断。
浮点数: Float、Double
布尔值: true、false,Swift的布尔值仅有两个值true或false,而OC则是非真则是假
字符串: String,字符串是字符的序列集合
字符: Character,字符指的是单个字母
可选类型: Optional,使用可选类型来处理值可能缺失的情况。可选类型表示有值或没有值。
类型别名
类型别名对当前的类型定义了另一个名字,类型别名通过使用 typealias 关键字来定义
typealias newname = type
=>
typealias Feet = Int
let ten: Feet = 10
print(ten)
类型安全
Swift是一个类型安全的语言,会在编译时进行类型检查,并把不匹配的类型标记为错误
类型推断
如果没有显式指定类型,Swift会使用类型推断来选择合适的类型
当推断浮点数的类型时,Swift 总是会选择Double而不是Float。
变量声明
变量声明意思是告诉编译器在内存中的哪个位置上为变量创建多大的存储空间。
使用var关键字声明
var varA = 2
var varB: Int
varB = 3
变量命名
变量名可以由字母,数字和下划线组成。
变量名需要以字母或下划线开始。
Swift 是一个区分大小写的语言,所以字母大写与小写是不一样的
var 你好 = "Hello"
print(你好)
变量输出
可以使用print函数来输出
在字符串中可以使用括号与反斜线来插入变量
var name = "百度"
var url = "https://www.baidu.com"
print("\(name)的地址为\(url)")
=> 百度的地址为https://www.baidu.com
Swift可选类型
Swift的可选类型,用于处理值缺失的情况。可选表示"那儿有一个值,并且它等于x"或者"那儿没有值"
Swift语言定义后缀?作为命名类型Optional的简写
var optionalA: Int?
var optionalB: Optional<Int>
optionalA = 10
optionalB = 20
print(optionalA ?? 0)
print(optionalB ?? 0)
Optional 是一个含有两种情况的枚举,None 和 Some(T),用来表示可能有或可能没有值
当声明一个可选类型的时候,要确保用括号给 ? 操作符一个合适的范围
当你声明一个可选变量或者可选属性的时候没有提供初始值,它的值会默认为 nil
如果一个可选类型的实例包含一个值,你可以用后缀操作符 !来访问这个值
使用操作符!去获取值为nil的可选变量会有运行时错误
可选类型类似于Objective-C中指针的nil值,但是nil只对类(class)有用,而可选类型对所有的类型都可用,并且更安全
强制解析
确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个感叹号表示"我知道这个可选有值,请使用它。"这被称为可选值的强制解析
使用!来获取一个不存在的可选值会导致运行时错误。使用!来强制解析值之前,一定要确定可选包含一个非nil的值
可选绑定
使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断并把值赋给一个常量或者变量
var myString:String?
myString = "Hello Swift"
if let str = myString {
print(str)
} else {
print("字符串没有值")
}
Swift常量
常量一旦设定,在程序运行时就无法改变其值。
常量可以是任何的数据类型如:整型常量,浮点型常量,字符常量或字符串常量。同样也有枚举类型的常量:
常量类似于变量,区别在于常量的值一旦设定就不能改变,而变量的值可以随意更改
使用关键字let声明
类型标注
声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型
在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
var constA: Int = 10
let constB: Float = 20
PS: 常量定义时必须给初始值
布尔型字面量有三个值: true、false、nil,它们是Swift的保留关键字
- true 表示真
- false 表示假
- nil 表示没有值
Swift3取消了++和--
区间运算符
Swift提供了两个区间运算符:
- 闭区间运算符: 1...5 => 1、2、3、4、5
- 开区间运算符: 1..<5 => 1、2、3、4
for idx in 1...5 {
print(idx)
}
=> 1
2
3
4
5
for idx in 6..<10 {
print(idx)
}
=> 6
7
8
9
合并空值运算符??
- 合并空值运算符a ?? b,如果可选项a有值则展开,如果没有值,是nil,则返回默认值b
- 表达式a必须是一个可选类型,表达式b必须与a的存储类型相同
- 合并空值运算符,实际上是三元运算符作用到 Optional 上的缩写 a != nil ? a! : b
for循环在Swift3中被弃用
循环控制语句:
- continue语句: 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
- break语句: 中断当前循环。
- fallthrough: 如果在一个case执行完后,继续执行下面的case,需要使用fallthrough(贯穿)关键字
// 使用字符串字面量
var str1 = "Hello World"
// String实例化
var str2 = String("Hello Swift")
print("\(str1), \(str2)")
=> Hello World, Hello Swift
var emptyStr1 = ""
var emptyStr2 = String()
if emptyStr1.isEmpty {
print("空字符串")
}
if emptyStr2.isEmpty {
print("空字符串")
}
// 字符串中插入值 => 构建新字符串的一种方式
var insertStr = "\(str1) -> \(str2)"
print(insertStr)
可以通过+连接字符串
var str1 = "Hello"
var str2 = "World"
var string = str1 + str2
字符串长度: str.count
字符串比较: 实用==比较两个字符串是否相等
hasPrefix(prefix: String): 检查字符串是否拥有特定前缀
hasSuffix(suffix: String): 检查字符串是否拥有特定后缀。
let result1 = str1.hasPrefix("H")
let result2 = str1.hasSuffix("t")
print(result1) true
print(result2) false
字符串分割为数组:
var str = "Hello World"
var array = str.split(separator: " ")
print(array.first!)
print(array[1])
=>
Hello
World
字符串操作
// 截取前n个字符
let res1 = str.prefix(5) // Hello
// 截取后n个字符
let res2 = str.suffix(5) // World
// 截取指定范围内字符
let range = str.index(str.startIndex, offsetBy: 7)..<str.endIndex
let res3 = str[range] // orld
// 截取从某个位置到末尾的字符
let index = str.index(str.startIndex, offsetBy: 6)
let res4 = str[index...] // World
// 从最后一个指定.字符开始截取,到字符串结束
let url = "https://www.baidu.com/image/xx/test.png"
if let dotIndex = url.lastIndex(of: ".") {
let index = url.index(dotIndex, offsetBy: 1)
let subStr = url[index..<url.endIndex]
print(subStr) // png
}
字符
Swift 的字符是一个单一的字符字符串字面量,数据类型为 Character。
let ch1: Character = "A"
print(ch1)
Swift 中不能创建空的 Character(字符) 类型变量或常量
创建一个数组,并赋值给一个变量,则创建的集合就是可以修改的。这意味着在创建数组后,可以通过添加、删除、修改的方式改变数组里的项目。
如果将一个数组赋值给常量,数组就不可更改,并且数组的大小和内容都不可以修改
创建数组推荐:
// 创建数组
let arr: [String] = []
let array: [String: Int] = [:]
Swift字典
Swift 字典用来存储无序的相同类型数据的集合,Swift 字典会强制检测元素的类型,如果类型不同则会报错。
Swift 字典每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。
Swift 字典的key没有类型限制可以是整型或字符串,但必须是唯一的。
创建一个字典,并赋值给一个变量,则创建的字典就是可以修改的。这意味着在创建字典后,可以通过添加、删除、修改的方式改变字典里的项目。
如果将一个字典赋值给常量,字典就不可修改,并且字典的大小和内容都不可以修改
// 创建字典
var dict: [Int: String] = [1: "Swift", 2: "OC", 3: "Flutter"]
print(dict) // [3: "Flutter", 1: "Swift", 2: "OC"] => 是无序的
// 访问字典
let two = dict[2] ?? ""
print(two) // OC
// 修改字典
// 使用 updateValue(forKey:) 增加或更新字典的内容。如果 key 不存在,则添加值,如果存在则修改 key 对应的值
let twoOld = dict.updateValue("vue", forKey: 2) ?? ""
print(twoOld) // OC
print(dict) // [3: "Flutter", 1: "Swift", 2: "vue"]
// 通过指定key修改
dict[2] = "Objective-C"
print(dict) // [1: "Swift", 3: "Flutter", 2: "Objective-C"]
// 移除key-value
// 使用 removeValueForKey() 方法来移除字典 key-value 对。如果 key 存在该方法返回移除的值,如果不存在返回 nil
dict.removeValue(forKey: 2)
print(dict) // [1: "Swift", 3: "Flutter"]
// 指定key的值为nil
dict[2] = "OC"
dict[2] = nil
print(dict) // [1: "Swift", 3: "Flutter"]
// 遍历字典
// 使用 for-in 循环来遍历某个字典中的键值对
for (key, value) in dict {
print("key \(key) - value \(value)")
/**
key 1 - value Swift
key 3 - value Flutter
**/
}
// 字典转数组
let keys = dict.keys
let values = dict.values
print(keys) // [1, 3]
print(values) // ["Swift", "Flutter"]
// 使用只读的 count 属性来计算字典有多少个键值对
let count = dict.count
print(count) // 2
// 通过只读属性 isEmpty 来判断字典是否为空,返回布尔值
print(dict.isEmpty) // false
Swift函数
Swift 函数用来完成特定任务的独立的代码块
函数定义
Swift定义函数使用关键字func
定义函数的时候,可以指定一个或多个输入参数和一个返回值类型
每个函数都有一个函数名来描述它的功能。通过函数名以及对应类型的参数值来调用这个函数。函数的参数传递的顺序必须与参数列表相同
func funcname(形参) -> returntype {
Statement1
Statement2
……
Statement N
return parameters
}
func printSelf(name: String) -> String {
return name
}
print(printSelf(name: "Hello World"))
函数参数
函数可以接受一个或者多个参数,这些参数被包含在函数的括号之中,以逗号分隔
func printSelf(name: String, site: String) -> String {
return name + site
}
print(printSelf(name: "百度一下:", site: "https://www.baidu.com"))
// 百度一下:https://www.baidu.com
元组作为函数返回值
元组与数组类似,不同的是,元组中的元素可以是任意类型,使用的是圆括号
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty {
return nil
}
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
if let bounds = minMax(array: [1, 5, -2, 9, 10, 20, 6]) {
print("最小值为: \(bounds.min), 最大值为: \(bounds.max)")
}
可变参数
可变参数可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数,其数量是不确定的
可变参数通过在变量类型名后面加入(...)的方式来定义
func vari<N>(members: N...) {
for value in members {
print(value)
}
}
vari(members: 1, 3, 5, 7, 9)
Swift枚举
枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合
Swift 中使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内
enum enumname {
// 枚举定义放在这里
}
enum DaysofWeek {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
case Saturday
case Sunday
}
var weekDay = DaysofWeek.Thursday
weekDay = .Friday
switch weekDay {
case .Monday:
print("星期一")
case .Tuesday:
print("星期二")
case .Wednesday:
print("星期三")
case .Thursday:
print("星期四")
case .Friday:
print("星期五")
case .Saturday:
print("星期六")
case .Sunday:
print("星期日")
}
=> 星期五
原始值
原始值可以是字符串,字符,或者任何整型值或浮点型值
每个原始值在它的枚举声明中必须是唯一的
在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值
当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
enum Month: Int {
case January = 1, February, March, April, May, June, July, August, September, October, November, December
}
let month = Month.May.rawValue
print("数字月份为: \(month)")
=> 数字月份为: 5
Swift结构体
Swift 结构体是构建代码所用的一种通用且灵活的构造体
可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能
与 C 和 Objective C 不同的是:
- 结构体不需要包含实现文件和接口
- 结构体允许我们创建一个单一文件,且系统会自动生成面向其它代码的外部接口
结构体总是通过被复制的方式在代码中传递,因此它的值是不可修改的
通过struct关键字来定义结构体:
struct structname {
definition1
definition2
...
definitionN
}
struct MarkStruct {
var mark1 = 99
var mark2 = 90
}
let mark = MarkStruct()
print("mark1: \(mark.mark1), mark2: \(mark.mark2)")
=> mark1: 99, mark2: 90
结构体应用
可以使用结构体来定义你的自定义数据类型
结构体实例总是通过值传递来定义你的自定义数据类型
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
- 结构体的主要目的是用来封装少量相关简单数据值
- 有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用
- 任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用
- 结构体不需要去继承另一个已存在类型的属性或者行为
举例来说,以下情境中适合使用结构体:
- 几何形状的大小,封装一个width属性和height属性,两者均为Double类型。
- 一定范围内的路径,封装一个start属性和length属性,两者均为Int类型
- 三维坐标系内一点,封装x,y和z属性,三者均为Double类型
结构体实例是通过值传递而不是通过引用传递
struct MarkStruct {
var mark1: Int
var mark2: Int
var mark3: Int
init(mark1: Int, mark2: Int, mark3: Int) {
self.mark1 = mark1
self.mark2 = mark2
self.mark3 = mark3
}
}
print("好成绩:")
let mark = MarkStruct(mark1: 98, mark2: 99, mark3: 96)
print(mark.mark1)
print(mark.mark2)
print(mark.mark3)
print("坏成绩:")
let fail = MarkStruct(mark1: 30, mark2: 38, mark3: 23)
print(fail.mark1)
print(fail.mark2)
print(fail.mark3)
Swift类
Swift 类是构建代码所用的一种通用且灵活的构造体
可以为类定义属性(变量和常量)和方法
Swift 并不要求你为自定义类去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类,系统会自动生成面向其它代码的外部接口
类和结构体对比
Swift中类和结构体有很多共同点:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义附属脚本用于访问值
- 定义构造器用于生成初始化值
- 通过扩展以增加默认实现的功能
- 符合协议以对某类提供标准功能
与结构体相比,类还有如下的附加功能:
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 解构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
语法:
class classname {
Definition 1
Definition 2
...
Definition N
}
类定义:
class student {
var studentName: String
var mark: Int
}
实例化:
let s = student()
恒等运算符
因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例
为了能够判定两个常量或者变量是否引用同一个类实例,Swift 内建了两个恒等运算符
===: 如果两个常量或者变量引用同一个类实例则返回 true
!==: 如果两个常量或者变量引用不同一个类实例则返回 true
Swift属性
Swift 属性将值跟特定的类、结构或枚举关联
属性可分为存储属性和计算属性
存储属性: 存储常量或变量作为实例的一部分,用于类和结构体
计算属性: 计算(而不是存储)一个值,用于类、结构体和枚举
简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量
存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)
- 可以在定义存储属性的时候指定默认值
- 也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值
延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性
在属性声明前使用 lazy 来标示一个延迟存储属性
注意: 必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
延迟存储属性一般用于:
- 延迟对象的创建(懒加载)
- 当属性的值依赖于其他未知类
class sample {
var no1 = 0.0, no2 = 0.0
var length = 300.0, breadth = 150.0
var middle: (Double, Double) {
get {
return (length/2, breadth/2)
}
set(axis) {
no1 = axis.0 - length/2
no2 = axis.1 - breadth/2
}
// 或
// 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue
// set {
// no1 = newValue.0 - length/2
// no2 = newValue.1 - breadth/2
// }
}
}
let sample = sample()
print(sample.middle) // (150.0, 75.0)
sample.middle = (0.0, 10.0)
print(sample.no1) // -150.0
print(sample.no2) // -65.0
只读计算属性
只有 getter 没有 setter 的计算属性就是只读计算属性
只读计算属性总是返回一个值,可以通过点(.)运算符访问,但不能设置新的值
class film {
var name = ""
var duration = 0.0
var info: [String:String] {
return ["name": self.name, "duration": "\(self.duration)"]
}
}
let film = film()
film.name = "Swift"
film.duration = 10.0
print(film.info["name"]!) // Swift
print(film.info["duration"]!) // 10.0
注意: 必须使用var关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let关键字只用来声明常量属性,表示初始化后再也无法修改的值。
属性观察器
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器
注意: 不需要为无法重载的计算属性添加属性观察器,因为可以通过 setter 直接监控和响应值的变化
可以为属性添加如下的一个或全部观察器:
- willSet在设置新的值之前调用
- didSet在新的值被设置之后立即调用
- willSet和didSet观察器在属性初始化过程中不会被调用
class didSetSample {
var count: Int = 0 {
willSet(newVal) {
print("willSet: \(newVal)")
}
didSet { // oldValue为内部属性 旧值
if count > oldValue {
print("didSet 变化: \(count - oldValue)")
}
}
}
}
let set = didSetSample()
set.count = 100
set.count = 800
=>
willSet: 100
didSet 变化: 100
willSet: 800
didSet 变化: 700
类型属性
属性也可以直接用于类型本身,这种属性称为类型属性。
类型属性作为类型定义的一部分,写在类型最外层的花括号{}内
使用关键字static定义值类型的类型属性,关键字class来为类定义类型属性
Swift方法
Swift方法是与某些特定类型相关联的函数
在Objective-C中,类是唯一能定义方法的类型。
在Swift中,不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在创建的类型(类/结构体/枚举)上定义方法
实例方法
在 Swift 语言中,实例方法是属于某个特定类、结构体或者枚举类型实例的方法
实例方法提供以下方法:
- 可以访问和修改实例属性
- 提供与实例目的相关的功能
实例方法要写在它所属的类型的前后大括号({})之间。
实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。
实例方法只能被它所属的类的某个特定实例调用。
实例方法不能脱离于现存的实例而被调用。
class Counter {
var count = 0
func increment() {
count += 1
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
=>
// 初始值为0
let counter = Counter()
print(counter.count) // 0
// 加1
counter.increment()
print(counter.count) // 1
// 加10
counter.incrementBy(amount: 10)
print(counter.count) // 11
// 清0
counter.reset()
print(counter.count) // 0
提供外部参数名 在内部参数名前隔一个空格
不提供外部参数名: 使用_
class area {
var result = 0
func getArea(width w: Int, _ h: Int) {
result = w * h
print(result)
}
}
self属性
类型的每一个实例都有一个隐含属性叫做self,self完全等同于该实例本身
可以在一个实例的实例方法中使用这个隐含的self属性来引用当前实例
class calcator {
let a: Int
let b: Int
let res: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
res = a + b
}
func tot(c: Int) -> Int {
return res - c
}
func result() {
print("结果为:\(tot(c: 20))")
print("结果为:\(tot(c: 50))")
}
}
let cal = calcator(a: 600, b: 300)
cal.result()
=>
结果为:880
结果为:850
在实例方法中修改值类型
Swift 语言中结构体和枚举是值类型
一般情况下,值类型的属性不能在它的实例方法中被修改
确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中
方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例
struct area {
var length = 1
var breadth = 1
func area() -> Int {
return length * breadth
}
mutating func scaleBy(res: Int) {
length *= res
breadth *= res
print(length)
print(breadth)
}
}
var area = area(length: 3, breadth: 5)
area.scaleBy(res: 3)
area.scaleBy(res: 30)
area.scaleBy(res: 300)
=>
9
15
270
450
81000
135000
在可变方法中给 self 赋值
可变方法能够赋给隐含属性 self 一个全新的实例
struct area {
var length = 1
var breadth = 1
func area() -> Int {
return length * breadth
}
mutating func scaleBy(res: Int) {
self.length *= res
self.breadth *= res
print(length)
print(breadth)
}
}
var area = area(length: 3, breadth: 5)
area.scaleBy(res: 13)
=>
39
65
类型方法
实例方法是被类型的某个实例调用的方法,你也可以定义类型本身调用的方法,这种方法就叫做类型方法
声明结构体和枚举的类型方法,在方法的func关键字之前加上关键字static
类可能会用关键字class来允许子类重写父类的实现方法。
类型方法和实例方法一样用点号(.)语法调用
class MathClass {
class func abs(number: Int) -> Int {
if number < 0 {
return -number
} else {
return number
}
}
}
struct MathStruct {
static func abs(number: Int) -> Int {
if number < 0 {
return -number
} else {
return number
}
}
}
let no1 = MathClass.abs(number: -35)
let no2 = MathStruct.abs(number: -5)
print(no1)
print(no2)
=>
35
5
Swift继承
继承我们可以理解为一个类获取了另外一个类的方法和属性
当一个类继承其它类时,继承类叫子类,被继承类叫超类(或父类)
在 Swift 中,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写它们
也可以为类中继承来的属性添加属性观察器
没有继承其它类的类,称之为基类(Base Class)
防止重写
可以使用 final 关键字防止它们被重写。
可以通过在关键字class前添加final特性(final class)来将整个类标记为 final 的,这样的类是不可被继承的,否则会报编译错误
Swift构造过程
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程
这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。
Swift 构造函数使用 init() 方法。
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化
存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值
存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器
存储属性在构造器中赋值流程:
- 创建初始值
- 在属性定义中指定默认属性值
- 初始化实例,并调用 init() 方法
构造过程中修改常量属性
只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
struct Rectangle {
let length: Double?
init(fromBreadth breadth: Double) {
length = breadth * 10
}
init(_ area: Double) {
length = area
}
// //Cannot assign to property: 'length' is a 'let' constant
// func changeLength(val: Double) {
// length = val
// }
}
let rec = Rectangle(180)
let rec1 = Rectangle(300)
print(rec.length)
print(rec1.length)
=>
Optional(180.0)
Optional(300.0)
默认构造器
默认构造器将简单的创建一个所有属性值都设置为默认值的实例
class ShoppingListItem {
var name: String?
var quantity: Int = 1
var purchased = false
}
let shopping = ShoppingListItem()
print(shopping.name)
print(shopping.quantity)
print(shopping.purchased)
=>
nil
1
false
结构体的逐一成员构造器
如果结构体为所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器
在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值
struct Rectangle {
var length = 10.0, breadth = 20.0
}
let rec = Rectangle(length: 100.0, breadth: 200.0)
print(rec.length)
print(rec.breadth)
=>
100.0
200.0
构造器的继承和重载
Swift 中的子类不会默认继承父类的构造器
但是如果满足特定条件,父类构造器是可以被自动继承的。事实上,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
当你重写一个父类指定构造器时,你需要写override修饰符
class SuperClass {
var corners = 4
var des: String {
return "\(corners) 边"
}
}
class SonClass: SuperClass {
override init() {
super.init()
corners = 5
}
}
let supC = SuperClass()
print(supC.des)
let sonC = SonClass()
print(sonC.des)
=>
4 边
5 边
结构体的可失败构造器
如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器
可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。
其语法为在init关键字后面加添问号(init?)
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
let animal = Animal(species: "老虎")
if let tiger = animal {
print(tiger.species)
}
=>
老虎
枚举的可失败构造器
可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
let tem = TemperatureUnit(symbol: "F")
if tem != nil {
print("这是一个已存在的温度单位,所以初始化成功")
}
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("这不是一个已定义的温度单位,所以初始化失败")
}
=>
这是一个已存在的温度单位,所以初始化成功
这不是一个已定义的温度单位,所以初始化失败
类的可失败构造器
值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。
但是,类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为
class StudDetail {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
let stud1 = StudDetail(name: "Tom")
if let stud = stud1 {
print(stud.name)
}
let stud2 = StudDetail(name: "")
if let stud = stud2 {
print(stud.name)
} else {
print("构造失败")
}
=>
Tom
构造失败
覆盖一个可失败构造器
1.可以用子类的可失败构造器覆盖基类的可失败构造器
2.也可以用子类的非可失败构造器覆盖一个基类的可失败构造器
3.可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通
4.一个非可失败的构造器永远也不能代理调用一个可失败构造器
可失败构造器 init!
通常来说我们通过在init关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!)
struct StudRecord {
let sName: String
init!(sName: String) {
if sName.isEmpty {
return nil
}
self.sName = sName
}
}
let stud = StudRecord(sName: "Tom")
if let stu = stud {
print("\(stu.sName)")
}
let nullStud = StudRecord(sName: "")
if nullStud == nil {
print("学生名字为空")
}
=>
Tom
学生名字为空
Swift的初始化需要保证类型的所有属性都被初始化。所以初始化的顺序就需要注意。
在某个类的子类中,初始化方法里语句的顺序并不是随意的,我们需要保证在当前子类实例的成员初始化完成后才能调用父类的初始化方法
class Cat {
var name: String
init() {
name = "cat"
}
}
class Tiger: Cat {
let power: Int
override init() {
power = 10
super.init()
name = "tiger"
}
}
Swift析构过程
在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。析构函数只适用于类类型
析构过程原理
Swift 会自动释放不再需要的实例以释放资源
Swift 通过自动引用计数(ARC)处理实例的内存管理
通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。
例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件
在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号
deinit {
// 执行析构过程
}
Swift可选链
可选链(Optional Chaining)是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil
可选链返回两个值:
- 如果目标有值,调用就会成功,返回该值
- 如果目标为nil,调用将返回nil
多次请求或调用可以被链接成一个链,如果任意一个节点为nil将导致整条链失效
可选链可替代强制解析
通过在属性、方法、或下标脚本的可选值后面放一个问号(?),即可定义一个可选链
Swift自动引用计数(ARC)
Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存
通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存
在有些时候我们还是需要在代码中实现内存管理
ARC功能:
- 当每次使用 init() 方法创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息
- 内存中会包含实例的类型信息,以及这个实例所有相关属性的值
- 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用
- 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用
- 实例赋值给属性、常量或变量,它们都会创建此实例的强引用,只要强引用还在,实例是不允许被销毁的
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) 开始初始化")
}
deinit {
print("\(name) 被析构")
}
}
// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// 创建Person类的新实例
reference1 = Person(name: "Runoob")
//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1
//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil
}
=>
Runoob 开始初始化
Runoob 被析构
类实例之间的循环强引用
可能会写出这样的代码,一个类永远不会有0个强引用。这种情况发生在两个类实例互相保持对方的强引用,并让对方不被销毁。这就是所谓的循环强引用。
解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题
- 弱引用
- 无主引用
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用
弱引用实例
class Module {
let name: String
init(name: String) {
self.name = name
}
var sub: SubModule?
deinit {
print("\(name) 主模块")
}
}
class SubModule {
let number: Int
init(number: Int) {
self.number = number
}
weak var topic: Module?
deinit {
print("子模块topic数为 \(number)")
}
}
var mod: Module?
var sub: SubModule?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mod = Module(name: "Swift")
sub = SubModule(number: 5)
mod?.sub = sub
sub?.topic = mod
mod = nil
sub = nil
}
无主引用实例
class Student {
let name: String
var mark: Marks?
init(name: String) {
self.name = name
}
deinit {
print("学生姓名: \(name)")
}
}
class Marks {
let score: Int
unowned let stu: Student
init(score: Int, stu: Student) {
self.score = score
self.stu = stu
}
deinit {
print("分数 \(score)")
}
}
var stu: Student?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
stu = Student(name: "Swift")
stu?.mark = Marks(score: 99, stu: stu!)
stu = nil
}
闭包引起的循环强引用
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。
这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。
这两种情况都导致了闭包 "捕获" self,从而产生了循环强引用
弱引用和无主引用
当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用
相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用
如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name)>"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name)被析构")
}
}
var para: HTMLElement? = HTMLElement(name: "p", text: "hello world")
print(para!.asHTML())
para = nil
=>
<p>hello world</p>
p被析构
Swift类型转换
Swift 语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例
Swift 中类型转换使用 is 和 as 操作符实现,is 用于检测值的类型,as 用于转换类型
类型转换也可以用来检查一个类是否实现了某个协议
class Car {
var name: String
init(name: String) {
self.name = name
}
}
class BMW: Car {
let type: String
init(name: String, type: String) {
self.type = type
super.init(name: name)
}
}
class Auto: Car {
let wheels: Int
init(name: String, wheels: Int) {
self.wheels = wheels
super.init(name: name)
}
}
let list = [
BMW(name: "宝马", type: "X3"),
Auto(name: "大众", wheels: 4),
BMW(name: "宝马", type: "X1"),
BMW(name: "宝马", type: "X5"),
Auto(name: "大众", wheels: 5),
]
var bmwCount = 0
var autoCount = 0
for item in list {
if item is BMW {
bmwCount += 1
} else if item is Auto {
autoCount += 1
}
}
print("宝马: \(bmwCount), 大众: \(autoCount)")
=>
宝马: 3, 大众: 2
向下转型
向下转型,用类型转换操作符(as? 或 as!)
当你不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil。
只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
for item in list {
if let show = item as? BMW {
print("BMW品牌: \(show.name)")
} else if let show = item as? Auto {
print("Auto品牌: \(show.name)")
}
}
BMW品牌: 宝马
Auto品牌: 大众
BMW品牌: 宝马
BMW品牌: 宝马
Auto品牌: 大众
Any和AnyObject的类型转换
Swift为不确定类型提供了两种特殊类型别名
- AnyObject可以代表任何class类型的实例
- Any可以表示任何类型,包括方法类型(function types)
注意:
只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的
var exampleAny: [Any] = []
exampleAny.append(10)
exampleAny.append(0.88)
exampleAny.append("Hello Swift")
exampleAny.append(true)
exampleAny.append(BMW(name: "宝马", type: "X5"))
for item in exampleAny {
if let int = item as? Int {
print("整型数: \(int)")
} else if let double = item as? Double {
print("浮点型: \(double)")
} else if let string = item as? String {
print("字符串: \(string)")
} else if let bool = item as? Bool {
print("布尔值: \(bool)")
} else if let object = item as? BMW {
print("对象类型: \(object.name)")
} else {
print("其他类型")
}
}
==>
var exampleAny: [Any] = []
exampleAny.append(10)
exampleAny.append(0.88)
exampleAny.append("Hello Swift")
exampleAny.append(true)
exampleAny.append(BMW(name: "宝马", type: "X5"))
for item in exampleAny {
switch item {
case let someInt as Int:
print("整型: \(someInt)")
case let someDouble as Double:
print("浮点型: \(someDouble)")
case let someString as String:
print("字符串: \(someString)")
case let someBool as Bool:
print("布尔值: \(someBool)")
case let someObject as BMW:
print("对象: \(someObject)")
default:
print("其他未知")
}
}
在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型
Swift扩展
扩展就是向一个已有的类、结构体或枚举类型添加新功能
扩展可以对一个类型添加新的功能,但是不能重写已有的功能
Swift中的扩展可以:
- 添加计算型属性和计算型静态属性
- 定义实例方法和类型方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使一个已有类型符合某个协议
extension SomeType {
// 加到SomeType的新功能写到这里
}
一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议
extension SomeType: SomeProtocol, AnotherProtocol {
// 协议实现写到这里
}
计算型属性
扩展可以向已有类型添加计算型实例属性和计算型类型属性
extension Int {
var add: Int {
return self + 100
}
var minus: Int {
return self - 100
}
var multiply: Int {
return self * 3
}
var divide: Int {
return self / 3
}
}
let add = 1.add
let minus = 101.minus
let mul = 5.multiply
let div = 120.divide
print("\(add), \(minus), \(mul), \(div)")
=>
101, 1, 15, 40
构造器
扩展可以向已有类型添加新的构造器
可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项
扩展可以向类中添加新的便利构造器 init(),但是它们不能向类中添加新的指定构造器或析构函数 deinit()
方法
扩展可以向已有类型添加新的实例方法和类型方法
extension Int {
func repeatContent(summary: () -> ()) {
for _ in 0..<self {
summary()
}
}
}
3.repeatContent {
print("打印3")
}
2.repeatContent {
print("打印2")
}
=>
打印3
打印3
打印3
打印2
打印2
Swift协议
协议规定了用来实现某一特定功能所必需的方法和属性
任意能够满足协议要求的类型被称为遵循(conform)这个协议
类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能
protocol SomeProtocol {
// 协议内容
}
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
Swift访问控制
Swift 中,访问控制(Access Control)是一种用于限制代码模块对其他代码模块的访问权限的机制。通过访问控制,可以控制代码中各个部分的可见性和可访问性,以便于提高代码的安全性、可维护性和可复用性
Swift 提供了以下几种访问级别:
- open 最高访问级别,可以被定义模块外的代码访问和继承
- public 可以被定义模块外的代码访问,但不能被继承
- internal: 默认访问级别,可以被同一模块中的任何代码访问
- fileprivate: 只能在定义的文件内部访问
- private: 只能在定义的作用域内部访问
访问控制规则:
- 一个实体不能被具有更低访问级别的实体定义
- 函数的访问级别不能高于其参数类型和返回类型的访问级别
- 类的访问级别不能高于其父类的访问级别
- 类型的访问级别会影响其成员的访问级别