【iOS】Swift自带协议、Codable、网络状态检查、动画、安全、工程、工具

旨在快速打通iOS开发流程

By yesmore on 2022-11-29
阅读时间 6 分钟
文章共 1.2k
阅读量

本章内容概览:Swift自带协议、Codable、网络状态检查、动画、安全、工程、工具

上一节:【iOS】07_Swift自带属性包装

下一节:【iOS】09_Swift编程规范、资料推荐

同系列文章请查看:快乐码元 - iOS篇

1.自带协议 - Hashable

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
struct H: Hashable {
var p1: String
var p2: Int

// 提供随机 seed
func hash(into hasher: inout Hasher) {
hasher.combine(p1)
}
}

let h1 = H(p1: "one", p2: 1)
let h2 = H(p1: "two", p2: 2)

var hs1 = Hasher()
hs1.combine(h1)
hs1.combine(h2)
print(h1.hashValue) // 7417088153212460033 随机值
print(h2.hashValue) // -6972912482785541972 随机值
print(hs1.finalize()) // 7955861102637572758 随机值
print(h1.hashValue) // 7417088153212460033 和前面 h1 一样

let h3 = H(p1: "one", p2: 1)
print(h3.hashValue) // 7417088153212460033 和前面 h1 一样
var hs2 = Hasher()
hs2.combine(h3)
hs2.combine(h2)
print(hs2.finalize()) // 7955861102637572758 和前面 hs1 一样

应用生命周期内,调用 combine() 添加相同属性哈希值相同,由于 Hasher 每次都会使用随机的 seed,因此不同应用生命周期,也就是下次启动的哈希值,就会和上次的哈希值不同。

2.Codable

JSON 没有 id 字段

如果SwiftUI要求数据Model都是遵循Identifiable协议的,而有的json没有id这个字段,可以使用扩展struct的方式解决:

1
2
3
4
5
6
7
8
9
10
struct CommitModel: Decodable, Hashable {
var sha: String
var author: AuthorModel
var commit: CommitModel
}
extension CommitModel: Identifiable {
var id: String {
return sha
}
}

3.网络状态检查

通过 Network 库的 NWPathMonitor 来检查

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
import Combine
import Network

// 网络状态检查 network state check
final class Nsck: ObservableObject {
static let shared = Nsck()
private(set) lazy var pb = mkpb()
@Published private(set) var pt: NWPath

private let monitor: NWPathMonitor
private lazy var sj = CurrentValueSubject<NWPath, Never>(monitor.currentPath)
private var sb: AnyCancellable?

init() {
monitor = NWPathMonitor()
pt = monitor.currentPath
monitor.pathUpdateHandler = { [weak self] path in
self?.pt = path
self?.sj.send(path)
}
monitor.start(queue: DispatchQueue.global())
}

deinit {
monitor.cancel()
sj.send(completion: .finished)
}

private func mkpb() -> AnyPublisher<NWPath, Never> {
return sj.eraseToAnyPublisher()
}
}

使用方法:

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
var sb = Set<AnyCancellable>()
var alertMsg = ""

Nsck.shared.pb
.sink { _ in
//
} receiveValue: { path in
alertMsg = path.debugDescription
switch path.status {
case .satisfied:
alertMsg = ""
case .unsatisfied:
alertMsg = "😱"
case .requiresConnection:
alertMsg = "🥱"
@unknown default:
alertMsg = "🤔"
}
if path.status == .unsatisfied {
switch path.unsatisfiedReason {
case .notAvailable:
alertMsg += "网络不可用"
case .cellularDenied:
alertMsg += "蜂窝网不可用"
case .wifiDenied:
alertMsg += "Wifi不可用"
case .localNetworkDenied:
alertMsg += "网线不可用"
@unknown default:
alertMsg += "网络不可用"
}
}
}
.store(in: &sb)

4.动画

4.1 布局动画

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
import SwiftUI

struct AnimateLayout: View {
@State var changeLayout: Bool = true
@Namespace var namespace

var body: some View {
VStack(spacing: 30) {
if changeLayout {
HStack { items }
} else {
VStack { items }
}
Button("切换布局") {
withAnimation { changeLayout.toggle() }
}
}
.padding()
}

@ViewBuilder var items: some View {
Text("one")
.matchedGeometryEffect(id: "one", in: namespace)
Text("Two")
.matchedGeometryEffect(id: "Two", in: namespace)
Text("Three")
.matchedGeometryEffect(id: "Three", in: namespace)
}
}

5.安全

5.1 Keychain

使用方法:

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
38
39
40
41
42
43
44
45
46
47
let d1 = Data("keyChain github token".utf8)
let service = "access-token"
let account = "github"
let q1 = [
kSecValueData: d1,
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account
] as CFDictionary

// 添加一个 keychain
let status = SecItemAdd(q1, nil)

// 如果已经添加过会抛出 -25299 错误代码,需要调用 SecItemUpdate 来进行更新
if status == errSecDuplicateItem {
let q2 = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account
] as CFDictionary
let q3 = [
kSecValueData: d1
] as CFDictionary
SecItemUpdate(q2, q3)
}

// 读取
let q4 = [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
kSecReturnData: true
] as CFDictionary

var re: AnyObject?
SecItemCopyMatching(q4, &re)
guard let reData = re as? Data else { return }
print(String(decoding: reData, as: UTF8.self)) // keyChain github token

// 删除
let q5 = [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
] as CFDictionary

SecItemDelete(q5)

6.工程

6.1 程序入口点

Swift 允许全局编写 Swift 代码,实际上 clang 会自动将代码包进一个模拟 C 的函数中。Swift 也能够指定入口点,比如 @UIApplicationMain 或 @NSApplicationMain,UIKit 启动后生命周期管理是 AppDelegate 和 SceneDelegate,《 Understanding the iOS 13 Scene Delegate 》这篇有详细介绍。

@UIApplicationMain 和 @NSApplicationMain 会自动生成入口点。这些入口点都是平台相关的,Swift 发展来看是多平台的,这样在 Swift 5.3 时引入了 @main,可以方便的指定入口点。代码如下:

1
2
3
4
5
6
@main // 要定义个静态的 main 函数
struct M {
static func main() {
print("let's begin")
}
}

ArgumentParser 库,Swift 官方开源的一个开发命令行工具的库,也支持 @main。使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import ArgumentParser

@main
struct C: ParsableCommand {
@Argument(help: "Start")
var phrase: String

func run() throws {
for _ in 1...5 {
print(phrase)
}
}
}

7.工具

7.1 Swift-DocC

现在支持 Swift、OC 和 C,文档标记一样。.doccarchive 包含可部署的网站内容,兼容大多数托管服务,比如 Github pages。部署到在线服务上可参考 Generating Documentation for Hosting OnlinePublishing to GitHub Pages 文档。

和 SPM 集成参看 SwiftDocCPlugin

session 有 What’s new in Swift -DocCImprove the discoverability of your Swift-DocC content

SE-0356 Swift Snippets 代码片段用于示例文档的提案。

参考资料:


Tips: Please indicate the source and original author when reprinting or quoting this article.