简介
- Swift 语言由苹果公司在2014年推出,用来撰写 macOS 和 iOS 应用程序
Swift 语言学习路线及重难点
- 常量与变量
- 数据类型
- 运算符
- 元组
- 逻辑分支
- 循环
- 字符串
- 数组
- 字典
- 可选型 (重点)
- 类型转换(转化符号is和as)
- 函数
- 闭包
- 枚举
- 结构体
- 类
- 属性与方法
- 构造与析构函数
- 协议 protocol
- 扩展 extension
- 泛型
- 异常 和 Result
- 元类型、.self 与 Self
- @objc关键字
- where关键字
- Key Path
- Codable协议
- 访问权限
- 混合开发
- 命名空间
- 学习参考
常量与变量
什么是常量和变量
- 常量:使用
let
定义,定义后不可修改 - 变量:使用
var
定义,定义后可以修改
常量和变量的使用注意
- 在开发中,建议先定义常量,如需修改再修改为变量(更加安全)
- 声明为常量不可修改的意思是 指针不可以再指向其他对象,但是可以通过指针拿到对象,修改其中属性
数据类型
- Swift中的数据类型有:整型/浮点型/Bool型/元组/枚举/结构体/对象类型等
类型推导
- Swift是强类型语言,是一种总是强制类型定义的语言,要求所有变量都必须先定义后使用
- 注意:
- 定义一个标识符时有直接进行赋值,标识符后面的类型可以省略
- Swift有类型推导,会自动根据后面的赋值来决定前面的标识符的数据类型
运算符
常见的运算符
- +、-、*、/、%
- =、+=、-=、*=、/=、%=
- >、>=、<、<=、==、!=
- 区间运算符
- 半开半闭区间:0..<10 表示0~9
- 闭区间:0...10 表示0~10
- &&、||、!
元组
- 元组:一种数据结构,可以用于定义一组数据,组成元祖的数据可以称为“元素”
// 元组的常见写法
var one = ("李四", 30, 1.75)
var two = (name:"李四", age:30, height:1.75)
let (errorCode, errorInfo) = (404, "Not Found")
逻辑分支
// if 的使用
if a > 9 {
print(a)
}
// guard 的使用
guard 条件表达式 else {
// guard是Swift2.0新增的语法,跳转语句一般是return、break、continue、throw
}
语句组
switch 分支
- 一个case判断,可以判断多个值,以 , 隔开
- 如需case穿透,使用关键字
fallthrough
- 支持区间判断和多种数据类型、浮点型、字符串类型等
循环
for 循环
// for in 循环
for i in 0..<10 {
print(i)
}
// 特殊写法 如不需要用下标i
for _ in 0...10 {
print("hello swift")
}
while 和 repeat while 循环
var a = 0
while a < 10 {
a = a + 1
}
var b = 0
repeat {
b = b + 1
} while b < 20
字符串
- String是一个结构体,NSString是OC对象,String性能更高
- String支持直接遍历
字符串常用操作
// 1、拼接 使用 + 或 append
let str = "abc" + "def"
// 2、遍历
for (index, value) in str.enumerated() {
print("\(index) --- \(value)")
}
// 3、大写或小写
str.lowercased().uppercased()
// 4、含有字符串
str.contains("cd")
// 5、分割
let str1 = "aa&$$bb$$cc$$dd"
let desc = str1.components(separatedBy: "$$")
// 6、替换
let desc1 = str1.replacingOccurrences(of:"$$", with:"**")
// 7、子串
str.prefix(5) // 截取前5个字符
str.suffix(5) // 截取后5个字符
str.index(str.startIndex, offsetBy: -2)
let sub1 = str[0..<5] // 从位置0开始到5结束获取字符串
数组
- 数组是一堆有序的由相同类型元素构成的集合
- 数组中的元素是有序的,可重复出现
- Swift中用Array表示数组,是一个结构体,可以放普通类型
// 定义
var array = ["zhangsan", "lisi", "wangwu"]
// 基本操作
array.count // 获取长度
array.isEmpty // 判空
array.append("l") // 添加数据
array.insert("wo", at:0) // 插入元素
array.dropFirst() // 删除元素
array[0] = "fangqi" // 修改元素
array.reverse() // 倒序
// 遍历
for (index, name) in array.enumerated() {
print(index)
print(name)
}
字典
// Swift中任意类型用Any表示,如下定义字典
var dict: [String:Any] = ["name":"张三", "age":18]
// 基本操作
dict.count // 获取长度
dict.isEmpty // 判空
dict["height"] = 1.82 // 添加数据
dict.removeValue(forKey: "height") // 删除字段
dict["name"] = "lisi" // 修改字典 或使用 dict.updateValue("lisi", forKey:"name")
// 遍历
for (key, value) in dict {
print("\(key) --- \(value)")
}
可选型 (重点)
- 可选类型(Optional)的取值为:有值 | nil
// 定义可选类型
let name: String? = nil
// 取出可选类型的值 ! 强制解包(显示解包)
print(name!) // 如果可选类型为nil,会报错
// 可选绑定(隐式解包)
if let str = name {
print(str) // 此时输出就是str的值,而不是Optional
}
// 或使用guard取出可选类型的值
guard let str = name else {
return
}
print(str)
类型转换
类型转化符号 is 和 as
// 定义数组
let array: [Any] = [12, "zhangsan"]
// 取出数组中最后一个元素
let objcLast = array.last!
// is 判断元素是否是一个Int类型
if objcLast is Int {
print("是Int类型")
}
// as? 将Any转成可选类型,通过判断可选类型是否有值,来决定是否转化成功了
let name = objcLast as? String
print(name) // 结果:Optional("zhangsan")
// as! 将Any转成具体的类型,如果不是该类型,那么程序会崩溃
let name2 = objcLast as! String
print(name2) // 结果:zhangsan
Any、AnyObject
- Any是一个空协议集合的别名,它表示没有实现任何协议,因此它可以是任何类型,包括类实例与结构体实例。可以表示任何类型,包括函数类型
- AnyObject是一个成员为空的协议,任何对象都实现了这个协议。可以表示任何类类型的实例
函数
func 函数名(参数列表) -> 返回值类型 {
return 返回值
}
函数的使用注意
- 函数参数没有用var和let修饰,但它是常量,不能在函数内修改
- 每个函数的形式参数都包含形式参数标签和形式参数名两部分
- 某些情况,如果没传入具体的参数,可以使用默认参数
- 可变参数,可接受不确定数量的参数,必须有相同的类型
- 默认函数参数是值传递,如想改变外面变量,使用
inout
关键字 - 函数的嵌套,不推荐该写法
函数类型
- 函数是 引用类型
- 每个函数都有属于自己的类型,由函数的 参数类型 和 返回类型 组成
- 有了函数类型,就可以把函数类型像Int、Double、Array来用,比如函数类型 (Int, Int) -> Int
闭包
// 闭包表达式
{ (parameters) -> (return type) in
statements
}
- 闭包表达式由一对 {} 开始与结束
- 由 in 关键字将闭包分割成两部分:参数与返回值、闭包体
- 闭包形参不能提供默认值
闭包参数名称缩写
let array = getList(score: [65,75,85,95], op: { (num: Int) -> Bool in return num>80 })
// 简写一:省略 -> 与返回值类型
let array1 = getList(score: [65,75,85,95], op: { (num: Int) in return num>80 })
// 简写二:省略参数类型和括号
let array2 = getList(score: [65,75,85,95], op: { num in return num>80 })
// 简写三:省略 return 关键字
let array3 = getList(score: [65,75,85,95], op: { num in num>80 })
// 简写四:参数名称缩写,省略参数声明和 in,改为$0
let array4 = getList(score: [65,75,85,95], op: { $0>80 })
捕获
- 闭包可以在上下文环境中捕获常量、变量、并在自己的作用域内使用
- Swift最简单的闭包形式是嵌套函数,可以捕获其外部函数所有的参数以及定义的常量和变量
尾随闭包
- 是一个写在函数括号之后的闭包表达式,函数支持将其做为最后一个参数调用
func doSomething(info: String, clousre: (String) -> Void) {
clousre(info)
}
// 使用尾随闭包进行函数调用
doSomething(info: "World") { s in
print(s)
}
逃逸闭包
- 闭包作为一个参数传递给一个函数
- 传入函数的闭包如果在函数执行结束后才会调用,那就叫做逃逸闭包
- 声明闭包作为形参的函数时,可以在形参的类型之前写上
@escaping
来明确闭包是允许逃逸的 - 逃逸闭包会在函数结束后才执行
自动闭包
- 一种自动创建的闭包,用于包装函数参数的表达式
- 不接受任何参数,被调用时会返回被包装在其中的表达式的值
- 在形参的类型之前加上
@autoclosure
关键字标识是一个自动闭包
Swift中闭包在官方系统库中的应用函数
- sort —— 排序
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 这种默认升序
array.sorted()
// 如果需要降序
array.sort { (str1, str2) -> Bool in
return str1 > str2
}
- forEach —— 遍历
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 遍历
array.forEach { (str) in
print(str)
}
- filter —— 筛选
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 筛选
let a = array.filter { (str) -> Bool in
str.starts(with: "A")
}
- map —— 变换
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 闭包返回一个变换后的元素,接着组成一个新数组
let a = array.map { (str) -> String in
"Hello " + str
}
- reduce —— 合归
var sum:[Int] = [11, 22, 33, 44]
var total = sum.reduce(0) { (result, num) -> Int in
return result + num
}
- allSatisfy —— 条件符合
// 判断数组的所有元素是否全部大于85
let scores = [86, 88, 95, 92]
// 检查序列中的所有元素是否满足条件,返回Bool
let passed = scores.allSatisfy { $0 > 85 }
- compactMap —— 转换
let arr: Array = [1, 2, 34, 5, 6, 7, 8, 12, 45, 6. 9]
// 返回操作的新数组(并不是筛选),数组,字典都可以使用
let compact = arr.compactMap({ $0%2 == 0})
- mapValues —— 转换value
let dic = ["first":1, "second":2, "three":3, "four":4]
// 字典中的函数,对字典的value执行操作,返回改变value后新的字典
let mapValues = dic.mapValues({ $0 + 2 })
- compactMapValues —— 上面两个的合并
let dic = ["first":1, "second":2, "three":3, "four":4, "five":"abc"]
// 将上述两个方法的功能合并在一起,返回一个对value操作后的新字典,并且自动过滤不符合条件的键值对
let newDic = dic.compactMapValues({Int($0)})
first(where:) —— 筛选第一个符合条件的
last(where:) —— 筛选最后一个符合条件
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
let elementF = array.first(where: { $0.hasPrefix("A") })
let elementL = array.last(where: { $0.hasPrefix("A") })
removeAll(where:) —— 删除
// 高效根据条件删除,比filter内存效率高,指定不想要的东西
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
array.removeAll(where: { $0.hasPrefix("A") })
枚举
Swift中的枚举是一等类型,它可以像类和结构体一样增加 属性和方法
枚举定义
enum Sex {
case male
case female
}
枚举赋值
- 枚举类型赋值可以是字符串/字符/整型/浮点型
- 如果有给枚举类型赋值,则必须在枚举类型后面明确具体类型
枚举类型推断
- 如果枚举类型确定了,在访问值的时候可以用 .值 来访问
枚举原始值
- 原始值区分大小写
- 通过
rawValue
可以获取原始值 - 通过
rawValue
返回的枚举是一个可选型,因为原始值对应的枚举不一定存在 - 如果指定第一个元素的原始值后,后面元素原始值默认+1,枚举一定是Int类型
枚举遍历
enum Method: CaseIterable {
case Add, Sub, Mul, Div
}
for method in Method.allCases {
print(method)
}
枚举的可选参数
// Swift5之后,可变参数的枚举定义时,...改成了数组
enum ABC {
// case abc(argv: Int...)
case abc(argv: [Int])
}
func getABC() -> ABC {
return .abc(argv: [0, 1, 2, 3])
}
结构体
- 结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合
- 结构体是值类型(包括枚举)它在代码传递中总是会被拷贝
- 结构体既可以定义属性又可以定义方法
- 常用的结构体比如:CGRect、CGSize、CGPoint
字符串,数组和字典的赋值与拷贝行为
- Swift 中的String,Array 和 Dictionary 类型是作为结构体来实现的,这意味着在它们被赋值到一个新的常量或变量,或它们本身被传递到一个方法中,其实是传递了拷贝。这里与OC中有明显区别
类
- Swift虽然推荐面向协议编程,但其也是一门面向对象开发的语言
- 特征运算符
- 相同于(===)
- 不同于(!==)
- 继承
- 重写
override
- 防止被重写
final
- 重写
类与结构体对比
- 相同点
- 定义属性
- 定义方法
- 定义构造函数(init函数)
- 可以被扩展
- 遵循协议
- 类有而结构体没有的额外功能
- 继承
- 类型转换(子类 as 父类)
- 析构函数
- 引用计数
属性与方法
类的属性介绍有多种
- 存储属性:存储实例的常量和变量
- 计算属性:依赖于存储属性,通过计算得出来,它提供getter和setter间接访问和设置值
- 类属性:与整个类自身相关的属性用static来修饰
- 懒加载属性:用lazy修饰,必须进行初始化
总结
- 存储属性,最先被初始化
- 构造方法,仅次与存储属性调用,可以在这里对存储属性进行赋值
- 懒加载属性、类属性、全局属性都是在第一次使用的时候初始化一次,以后调用都不再初始化
- 当懒加载属性是基于一个存储属性计算的时候,切勿使用懒加载属性,采用计算属性
监听属性的改变
- Swift中可以通过属性观察者来监听和响应属性值的变化
- 定义观察者
- willSet:在属性值被存储之前设置。此时新属性值作为一个常量参数被传入。该参数名默认为newValue,可以自定义
- didSet:在新属性值被存储后立即调用。与willSet相同,此时传入的是属性的旧值,默认参数名为oldValue,可以自定义
- willSet与didSet只有在属性改变时才会调用,在初始化时不会去调用这些监听的方法
值类型在实例方法中修改属性和调用方法
- 值类型默认情况下,不能在实例方法中修改属性
- 不能用self调用其他的函数
- 可以在函数前方一个
mutating
关键字来实现
类方法
- 在函数前使用
static
关键字(能在类、结构体中使用) - 在函数前使用
class
关键字(只能在类中使用)
class 和 static 总结
构造与析构函数
默认构造函数
- 默认构造函数就像一个没有形参的实例方法,使用
init
关键字来写
自定义构造函数
- 希望在创建一个对象时手动给属性赋值(属性的值是在外面传进去的)
- 自定义构造函数和默认构造函数可以同时存在
Swift为类 类型定义了两种构造函数以确保所有存储属性接收一个初始值,指定构造函数和便捷构造函数
- 指定构造函数是类的主要构造函数,指定构造函数可以初始化所有类引用的属性,并且调用合适的父类构造函数来继续这个初始化过程给父类链
- 便捷构造函数是次要的,可以在相同的类里定义一个便捷构造函数来调用一个指定构造函数给指定构造函数设置默认形式参数
// 类的指定构造函数
init(parameters) {
statements
}
// 便捷构造函数需用 convenience 修饰符放到 init 关键字前
convenience init(parameters) {
statements
self.init(parameters)
}
- 指定构造函数必须总是向上委托
- 便捷构造函数必须总是横向委托
析构函数
- 当引用计数为0时,系统自动调用析构函数
deinit
(不可手动调用)
协议 protocol
协议可被类、结构体、或枚举类型采纳以提供所需功能的具体实现即遵循协议
扩展 extension
- 为现有的类、结构体、枚举类型、协议添加新功能。扩展和Objective-C中的分类类似
- Swift中使用
extension
关键字实现扩展
面向协议编程
针对某个需要实现的功能,可以使用协议定义出接口,然后利用协议扩展提供默认的实现,需要这个功能,只需要声明遵守这个协议即可,遵守某个协议的对象调用协议声明的方法时,如果遵守者本身没有提供实现,协议扩展提供的默认实现会被调用
泛型
类型约束 和 关联类型
- 关联类型通过
associatedtype
关键字指定
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
}
protocol SomeProtocol {
associatedtype Element: Equatable
func method1(element: Element)
}
异常
// 1、定义异常
enum FileReadError: Error {
case FileIsNull
case FileNotFound
}
// 2、让方法抛出异常
func readFileContent(filePath: String) throws -> String {
if filePath == "" {
throw FileReadError.FileIsNull
}
if filePath != "/User/Desktop/123.plist" {
throw FileReadError.FileNotFound
}
return "123"
}
// 处理异常
do {
let result = try readFileContent(filePath: "abc")
} catch {
print(error) // 有一个隐藏参数 error
}
// defer关键字
Result
- 在Swift5中,新增了一个枚举类型Result,使我们能够更简单、更清晰处理复杂代码中的错误
// 使用Result处理异常如下
func readFileContent(filePath: String) -> Result<String, FileReadError> {
if filePath == "" {
return .failure(.FileIsNull)
}
if filePath != "/User/Desktop/123.plist" {
return .failure(.FileNotFound)
}
return .success("123")
}
// 调用
let result = readFileContent(filePath: "")
switch result {
case .failure(let error)
print(error)
case .success(let content)
print(content)
}
元类型、.self 与 Self
- 获取对象类型:
type(of: )
语法 - 元类型:可以理解为类型的类型,可以通过
类型.Type
定义,可以修饰变量或常量,如何得到这种类型?需要通过类型.self
- Self大写在定义协议的时候用的频率很高,用于协议中限制相关的类型
@objc关键字
出于安全的考虑,需将暴露给Objective-C使用的如类、属性和方法的声明前面加上@objc
- #selector 中调用的方法需要在方法前声明
@objc
- 协议的方法可选时,协议和可选方法前要用
@objc
声明 - 用weak修饰的协议时 ,协议前面要用
@objc
声明 - 类上加
@objcMembers
,则其及子类、扩展里的属性和方法都会隐式的加上@objc
,如果部分不想加,可以用@nonobjc
修饰 - 扩展前加上
@objc
,那么里面的方法都会隐式加上@objc
where关键字
where关键字的含义和数据库中差不多,用于条件筛选,在Swift中哪些地方用到,如下总结
- Switch case 分支
- for 循环
- protocol 协议
- Generic 泛型
- do catch 异常处理
Key Path
- 类似OC中的KVC
- 用于间接获取/设置值
- 类必须继承自NSObject,否则不能用
- 哪些属性可以通过KeyPath操作,就需要在前面加上@objc
// Swift 3 之前
stu.value(forKey: "name")
stu.setValue("lisi", forKey: "name")
// Swift 3
stu.value(forKey: #keyPath(Student.sex))
stu.setValue("女", forKey: #keyPath(Student.sex))
// Swift 4
stu[keyPath: \Student.sex]
stu[keyPath: \Student.sex] = "女"
Codable协议
- JSON转Model,以前可以利用KVC、NSJSONSerialization实现
- Swift 4之后推荐使用Codable协议,可以通过编码和解码实现互转
访问权限
-
open
和public
:允许被定义模块中任意源文件访问,也可以被另一模块源文件通过导入该定义模块来访问 -
internal
:(默认)允许被定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问 -
fileprivate
:使用限制于当前定义的原文件中 -
private
:使用限制于封闭声明中,比fileprivate更严格
注意
- 访问权限可以修饰 类、方法、属性等
- 在Swift4中,private的属性作用域扩大到extension中,也就是说在extension中访问属性可以是fileprivate或private修饰的
学习参考
- 持续关注Swift之后发布的新版本,了解新特性,关注SwiftUI等
学习网址
网友评论