Swift编程

Swift语言基础.jpg

简介

  • Swift 语言由苹果公司在2014年推出,用来撰写 macOS 和 iOS 应用程序

Swift 语言学习路线及重难点

  • 常量与变量
  • 数据类型
  • 运算符
  • 元组
  • 逻辑分支
  • 循环
  • 字符串
  • 数组
  • 字典
  • 可选型 (重点)
  • 类型转换(转化符号isas)
  • 函数
  • 闭包
  • 枚举
  • 结构体
  • 属性与方法
  • 构造与析构函数
  • 协议 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中闭包在官方系统库中的应用函数

  1. sort —— 排序
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 这种默认升序
array.sorted()
// 如果需要降序
array.sort { (str1, str2) -> Bool in 
  return str1 > str2
}
  1. forEach —— 遍历
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 遍历
array.forEach { (str) in 
  print(str)
}
  1. filter —— 筛选
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 筛选
let a = array.filter { (str) -> Bool in 
  str.starts(with: "A")
}
  1. map —— 变换
var array: [String] = ["Animal", "Baby", "Apple", "Google", "Aunt"]
// 闭包返回一个变换后的元素,接着组成一个新数组
let a = array.map { (str) -> String in 
  "Hello " + str
}
  1. reduce —— 合归
var sum:[Int] = [11, 22, 33, 44]
var total = sum.reduce(0) { (result, num) -> Int in 
  return result + num
}
  1. allSatisfy —— 条件符合
// 判断数组的所有元素是否全部大于85
let scores = [86, 88, 95, 92]
// 检查序列中的所有元素是否满足条件,返回Bool
let passed = scores.allSatisfy { $0 > 85 }
  1. compactMap —— 转换
let arr: Array = [1, 2, 34, 5, 6, 7, 8, 12, 45, 6. 9]
// 返回操作的新数组(并不是筛选),数组,字典都可以使用
let compact = arr.compactMap({ $0%2 == 0})
  1. mapValues —— 转换value
let dic = ["first":1, "second":2, "three":3, "four":4]
// 字典中的函数,对字典的value执行操作,返回改变value后新的字典
let mapValues = dic.mapValues({ $0 + 2 })
  1. 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

  1. #selector 中调用的方法需要在方法前声明@objc
  2. 协议的方法可选时,协议和可选方法前要用@objc声明
  3. 用weak修饰的协议时 ,协议前面要用@objc声明
  4. 类上加@objcMembers,则其及子类、扩展里的属性和方法都会隐式的加上@objc,如果部分不想加,可以用@nonobjc修饰
  5. 扩展前加上@objc,那么里面的方法都会隐式加上@objc

where关键字

where关键字的含义和数据库中差不多,用于条件筛选,在Swift中哪些地方用到,如下总结

  1. Switch case 分支
  2. for 循环
  3. protocol 协议
  4. Generic 泛型
  5. 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协议,可以通过编码和解码实现互转

访问权限

  • openpublic:允许被定义模块中任意源文件访问,也可以被另一模块源文件通过导入该定义模块来访问
  • internal:(默认)允许被定义模块中的任意源文件访问,但不能被该模块之外的任何源文件访问
  • fileprivate:使用限制于当前定义的原文件中
  • private:使用限制于封闭声明中,比fileprivate更严格

注意

  • 访问权限可以修饰 类、方法、属性等
  • 在Swift4中,private的属性作用域扩大到extension中,也就是说在extension中访问属性可以是fileprivate或private修饰的

学习参考

  • 持续关注Swift之后发布的新版本,了解新特性,关注SwiftUI等

学习网址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容