【iOS】Swift内置基础库

旨在快速打通iOS开发流程

By yesmore on 2022-11-28
阅读时间 10 分钟
文章共 2.2k
阅读量

本章内容概览:Swift内置基础库(时间、格式化、度量值、Data、文件、Scanner、AttributeString、随机、UserDefaults)

上一节:【iOS】04_Swift基础语法(操作符)

下一节:【iOS】06_系统及设备

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

1.时间

1.1 Date 的基本用法

1
let now = Date()

Date 转 时间戳:

1
2
3
4
let interval = now.timeIntervalSince1970 // 时间戳
let df = DateFormatter()
df.dateFormat = "yyyy 年 MM 月 dd 日 HH:mm:ss"
print("时间戳:\(Int(interval))") // 时间戳:1642399901

格式化的时间:

1
print("格式化的时间:" + df.string(from: now)) // 格式化的时间:2022 年 01 月 17 日 14:11:41

short 样式时间:

1
2
df.dateStyle = .short
print("short 样式时间:" + df.string(from: now)) // short 样式时间:2022/1/17

full 样式时间:

1
2
3
df.locale = Locale(identifier: "zh_Hans_CN")
df.dateStyle = .full
print("full 样式时间:" + df.string(from: now)) // full 样式时间:2022年1月17日 星期一

时间戳转 Date:

1
2
let date = Date(timeIntervalSince1970: interval)
print(date) // 2022-01-17 06:11:41 +0000

1.2 复杂的时间操作

比如说 GitHub 接口使用的是 ISO 标准,RSS 输出的是 RSS 标准字符串,不同标准对应不同时区的时间计算处理,可以使用开源库 SwiftDate 来完成。示例代码如下:

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

// 使用 SwiftDate 库
let cn = Region(zone: Zones.asiaShanghai, locale: Locales.chineseChina)
SwiftDate.defaultRegion = cn
print("2008-02-14 23:12:14".toDate()?.year ?? "") // 2008

let d1 = "2022-01-17T23:20:35".toISODate(region: cn)
guard let d1 = d1 else {
return
}
print(d1.minute) // 20
let d2 = d1 + 1.minutes
print(d2.minute)

// 两个 DateInRegion 相差时间 interval
let i1 = DateInRegion(Date(), region: cn) - d1
let s1 = i1.toString {
$0.maximumUnitCount = 4
$0.allowedUnits = [.day, .hour, .minute]
$0.collapsesLargestUnit = true
$0.unitsStyle = .abbreviated
$0.locale = Locales.chineseChina
}
print(s1) // 9小时45分钟

2.格式化 - 描述

使用标准库的格式来描述不同场景的情况可以不用去考虑由于不同地区的区别,这些在标准库里就可以自动完成了。

2.1 描述两个时间之间相差多长时间

计算两个时间之间相差多少时间,支持多种语言字符串:

1
2
3
4
5
6
7
let d1 = Date().timeIntervalSince1970 - 60 * 60 * 24
let f1 = RelativeDateTimeFormatter()
f1.dateTimeStyle = .named
f1.formattingContext = .beginningOfSentence
f1.locale = Locale(identifier: "zh_Hans_CN")
let str = f1.localizedString(for: Date(timeIntervalSince1970: d1), relativeTo: Date())
print(str) // 昨天

简写:

1
2
3
let str2 = Date.now.addingTimeInterval(-(60 * 60 * 24))
.formatted(.relative(presentation: .named))
print(str2) // yesterday

描述多个事物

1
2
3
// 描述多个事物
let s1 = ListFormatter.localizedString(byJoining: ["冬天","春天","夏天","秋天"])
print(s1)

描述名字

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
// 名字
let f2 = PersonNameComponentsFormatter()
var nc1 = PersonNameComponents()
nc1.familyName = "宋"
nc1.givenName = "潇"
nc1.nickname = "宋宋"
print(f2.string(from: nc1)) // 宋潇
f2.style = .short
print(f2.string(from: nc1)) // 宋宋
f2.style = .abbreviated
print(f2.string(from: nc1)) // 宋

var nc2 = PersonNameComponents()
nc2.familyName = "Song"
nc2.givenName = "Xiao"
nc2.nickname = "SongSong"
f2.style = .default
print(f2.string(from: nc2)) // Xiao Song
f2.style = .short
print(f2.string(from: nc2)) // SongSong
f2.style = .abbreviated
print(f2.string(from: nc2)) // XS

// 取出名
let componets = f2.personNameComponents(from: "宋潇")
print(componets?.givenName ?? "") // 潇

描述数字

1
2
3
4
5
6
7
8
9
10
11
// 数字
let f3 = NumberFormatter()
f3.locale = Locale(identifier: "zh_Hans_CN")
f3.numberStyle = .currency
print(f3.string(from: 123456) ?? "") // ¥123,456.00
f3.numberStyle = .percent
print(f3.string(from: 123456) ?? "") // 12,345,600%

let n1 = 1.23456
let n1Str = n1.formatted(.number.precision(.fractionLength(3)).rounded())
print(n1Str) // 1.235

描述地址

1
2
3
4
5
6
7
8
9
10
11
12
13
// 地址
import Contacts

let f4 = CNPostalAddressFormatter()
let address = CNMutablePostalAddress()
address.street = "海淀区王庄路XX号院X号楼X门XXX"
address.postalCode = "100083"
address.city = "北京"
address.country = "中国"
print(f4.string(from: address))
/// 海淀区王庄路XX号院X号楼X门XXX
/// 北京 100083
/// 中国

3.度量值

标准库里的物理量,在这个文档里有详细列出,包括角度、平方米等。

参考:https://developer.apple.com/documentation/foundation/nsdimension

1
2
3
4
5
6
7
let m1 = Measurement(value: 1, unit: UnitLength.kilometers)
let m2 = m1.converted(to: .meters) // 千米转米
print(m2) // 1000.0 m
// 度量值转为本地化的值
let mf = MeasurementFormatter()
mf.locale = Locale(identifier: "zh_Hans_CN")
print(mf.string(from: m1)) // 1公里

一些物理公式供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
面积 = 长度 × 长度
体积 = 长度 × 长度 × 长度 = 面积 × 长度

速度=长度/时间
加速度=速度/时间

力 = 质量 × 加速度
扭矩 = 力 × 长度
压力 = 力 / 面积

密度=质量 / 体积
能量 = 功率 × 时间
电阻 = 电压 / 电流

3.Data

数据压缩和解压

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 对数据的压缩
let d1 = "看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?看看能够压缩多少?".data(using: .utf8)! as NSData
print("ori \(d1.count) bytes")
do {
/// 压缩算法
/// * lz4
/// * lzma
/// * zlib
/// * lzfse
let compressed = try d1.compressed(using: .zlib)
print("comp \(compressed.count) bytes")

// 对数据解压
let decomressed = try compressed.decompressed(using: .zlib)
let deStr = String(data: decomressed as Data, encoding: .utf8)
print(deStr ?? "")
} catch {}
/// ori 297 bytes
/// comp 37 bytes

4.文件

4.1 文件的一些基本操作

1
2
let path1 = "/Users/mingdai/Downloads/1.html"
let path2 = "/Users/mingdai/Documents/GitHub/"
1
2
3
4
5
6
7
8
9
10
11
let u1 = URL(string: path1)
do {
// 写入
let url1 = try FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: u1, create: true) // 保证原子性安全保存
print(url1)

// 读取
let s1 = try String(contentsOfFile: path1, encoding: .utf8)
print(s1)

} catch {}
1
2
3
4
5
6
7
8
9
10
11
12
// 检查路径是否可用
let u2 = URL(fileURLWithPath:path2)
do {
let values = try u2.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
if let capacity = values.volumeAvailableCapacityForImportantUsage {
print("可用: \(capacity)")
} else {
print("不可用")
}
} catch {
print("错误: \(error.localizedDescription)")
}

4.2 遍历多级目录结构中的文件

怎么遍历多级目录结构中的文件呢?看下面的代码的实现:

1
2
3
4
5
6
7
8
9
10
// 遍历路径下所有目录
let u3 = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
let fm = FileManager.default
fm.enumerator(atPath: u3.path)?.forEach({ path in
guard let path = path as? String else {
return
}
let url = URL(fileURLWithPath: path, relativeTo: u3)
print(url.lastPathComponent)
})

4.3 创建文件夹和文件

可以使用 FileWrapper 来创建文件夹和文件。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// FileWrapper 的使用
// 创建文件
let f1 = FileWrapper(regularFileWithContents: Data("# 第 n 个文件\n ## 标题".utf8))
f1.fileAttributes[FileAttributeKey.creationDate.rawValue] = Date()
f1.fileAttributes[FileAttributeKey.modificationDate.rawValue] = Date()
// 创建文件夹
let folder1 = FileWrapper(directoryWithFileWrappers: [
"file1.md": f1
])
folder1.fileAttributes[FileAttributeKey.creationDate.rawValue] = Date()
folder1.fileAttributes[FileAttributeKey.modificationDate.rawValue] = Date()

do {
try folder1.write(
to: URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("NewFolder"),
options: .atomic,
originalContentsURL: nil
)
} catch {}
print(FileManager.default.currentDirectoryPath)

上面代码写起来比较繁琐,对 FileWrapper 更好的封装可以参考这篇文章《 A Type-Safe FileWrapper | Heberti Almeida 》。

文件读写处理完整能力可以参看这个库 GitHub - JohnSundell/Files: A nicer way to handle files & folders in Swift

本地或者网络上,比如网盘和FTP的文件发生变化时,怎样知道能够观察到呢?

通过 HTTPHeader 里的 If-Modified-Since、Last-Modified、If-None-Match 和 Etag 等字段来判断文件的变化,本地则是使用 DispatchSource.makeFileSystemObjectSource 来进行的文件变化监听。可以参考 KZFileWatchers 库的做法。

5.Scanner

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
let s1 = """
one1,
two2,
three3.
"""
let sn1 = Scanner(string: s1)
while !sn1.isAtEnd {
if let r1 = sn1.scanUpToCharacters(from: .newlines) {
print(r1 as String)
}
}
/// one1,
/// two2,
/// three3.

// 找出数字
let sn2 = Scanner(string: s1)
sn2.charactersToBeSkipped = CharacterSet.decimalDigits.inverted // 不是数字的就跳过
var p: Int = 0
while !sn2.isAtEnd {
if sn2.scanInt(&p) {
print(p)
}
}
/// 1
/// 2
/// 3

上面的代码还不是那么 Swifty,可以通过用AnySequence和AnyIterator来包装下,将序列中的元素推迟到实际需要时再来处理,这样性能也会更好些。具体实现可以参看《 String parsing in Swift 》这篇文章。

6.AttributeString

效果如下:

img

代码如下:

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
48
49
50
51
52
53
54
55
56
57
var aStrs = [AttributedString]()
var aStr1 = AttributedString("""
标题
正文内容,具体查看链接。
这里摘出第一个重点,还要强调的内容。
""")
// 标题
let title = aStr1.range(of: "标题")
guard let title = title else {
return aStrs
}

var c1 = AttributeContainer() // 可复用容器
c1.inlinePresentationIntent = .stronglyEmphasized
c1.font = .largeTitle
aStr1[title].setAttributes(c1)

// 链接
let link = aStr1.range(of: "链接")
guard let link = link else {
return aStrs
}

var c2 = AttributeContainer() // 链接
c2.strokeColor = .blue
c2.link = URL(string: "https://ming1016.github.io/")
aStr1[link].setAttributes(c2.merging(c1)) // 合并 AttributeContainer

// Runs
let i1 = aStr1.range(of: "重点")
let i2 = aStr1.range(of: "强调")
guard let i1 = i1, let i2 = i2 else {
return aStrs
}

var c3 = AttributeContainer()
c3.foregroundColor = .yellow
c3.inlinePresentationIntent = .stronglyEmphasized
aStr1[i1].setAttributes(c3)
aStr1[i2].setAttributes(c3)

for r in aStr1.runs {
print("-------------")
print(r.attributes)
}

aStrs.append(aStr1)

// Markdown
do {
let aStr2 = try AttributedString(markdown: """
内容[链接](https://ming1016.github.io/)。需要**强调**的内容。
""")

aStrs.append(aStr2)

} catch {}

SwiftUI 的 Text 可以直接读取 AttributedString 来进行显示。

7.随机

用法:

1
2
3
4
5
let ri = Int.random(in: 0..<10)
print(ri) // 0到10随机数
let a = [0, 1, 2, 3, 4, 5]
print(a.randomElement() ?? 0) // 数组中随机取个数
print(a.shuffled()) // 随机打乱数组顺序

8.UserDefaults

使用方法如下:

1
2
3
4
5
6
7
enum UDKey {
static let k1 = "token"
}
let ud = UserDefaults.standard
ud.set("xxxxxx", forKey: UDKey.k1)
let tk = ud.string(forKey: UDKey.k1)
print(tk ?? "")

参考资料:


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