原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 1、map、flatMap、compactMap的区别
- 2、Async/await
- 3、CustomStringConvertible
- 4、property wrapper
- 5、截取字符串
1、map、flatMap、compactMap的区别
一维数组的使用
let numbers = [1, 2, 3, nil, 4, 5, 6, 7]
let maped = numbers.map { $0 }
let flatMapped = numbers.flatMap { $0 }
let compactMapped = numbers.compactMap { $0 }
print(maped)
print(flatMapped)
print(compactMapped)
-
map:不会对数据进行解包操作,不可以过滤
nil -
flatMap:将对数据进行解包操作,可以过滤
nil -
compactMap:将对数据进行解包操作,可以过滤
nil
[Optional(1), Optional(2), Optional(3), nil, Optional(4), Optional(5), Optional(6), Optional(7)]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7]
二维数组的使用
let numbers = [[1, 2, 3, nil], [4, 5, 6], [7]]
let maped = numbers.map { $0 }
let flatMapped = numbers.flatMap { $0 }
let compactMapped = numbers.compactMap { $0 }
print(maped)
print(flatMapped)
print(compactMapped)
将数组扁平化,实现数组降维,但不可以对子数组进行过滤nil操作
- map:不可以降维
- flatMap:可以降维
- compactMap:不可以降维
[[Optional(1), Optional(2), Optional(3), nil], [Optional(4), Optional(5), Optional(6)], [Optional(7)]]
[Optional(1), Optional(2), Optional(3), nil, Optional(4), Optional(5), Optional(6), Optional(7)]
[[Optional(1), Optional(2), Optional(3), nil], [Optional(4), Optional(5), Optional(6)],
2、Async/await
一言以蔽之, 以前需要用闭包回调来写的代码, 我们现在可以用async/await来写, 这让我们可以抛弃复杂的闭包嵌套代码, 极大的简化了代码, 提升可读性。举个🌰,我们先查询历史天气, 再计算出平均温度, 最后上传。
func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
DispatchQueue.global().async {
let results = (1...100_000).map { _ in Double.random(in: -10...30) }
completion(results)
}
}
func calculateAverageTemperature(for records: [Double], completion: @escaping (Double) -> Void) {
DispatchQueue.global().async {
let total = records.reduce(0, +)
let average = total / Double(records.count)
completion(average)
}
}
func upload(result: Double, completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
completion("OK")
}
}
调用的时候:
fetchWeatherHistory { records in
calculateAverageTemperature(for: records) { average in
upload(result: average) { response in
print("Server response: \(response)")
}
}
}
可以发现, 无论是写起来还是阅读都非常hard, 尤其对新手及其不友好。那么用了async/await之后呢?
func fetchWeatherHistory() async -> [Double] {
(1...100_000).map { _ in Double.random(in: -10...30) }
}
func calculateAverageTemperature(for records: [Double]) async -> Double {
let total = records.reduce(0, +)
let average = total / Double(records.count)
return average
}
func upload(result: Double) async -> String {
"OK"
}
可以看到, 非常清晰, 可读性非常高。
async/await 也支持 try/catch
enum UserError: Error {
case invalidCount, dataTooLong
}
func save(users: [String]) async throws -> String {
let savedUsers = users.joined(separator: ",")
if savedUsers.count > 32 {
throw UserError.dataTooLong
} else {
// Actual saving code would go here
return "Saved \(savedUsers)!"
}
}
func fetchUsers(count: Int) async throws -> [String] {
if count > 3 {
// Don't attempt to fetch too many users
throw UserError.invalidCount
}
// Complex networking code here; we'll just send back up to `count` users
return Array(["Antoni", "Karamo", "Tan"].prefix(count))
}
使用:
func updateUsers() async {
do {
let users = try await fetchUsers(count: 3)
let result = try await save(users: users)
print(result)
} catch {
print("Oops!")
}
}
只读属性里也可以用
enum FileError: Error {
case missing, unreadable
}
struct BundleFile {
let filename: String
var contents: String {
get async throws {
guard let url = Bundle.main.url(forResource: filename, withExtension: nil) else {
throw FileError.missing
}
do {
return try String(contentsOf: url)
} catch {
throw FileError.unreadable
}
}
}
}
func printHighScores() async throws {
let file = BundleFile(filename: "highscores")
try await print(file.contents)
}
async let的使用
struct UserData {
let username: String
let friends: [String]
let highScores: [Int]
}
func getUser() async -> String {
"Taylor Swift"
}
func getHighScores() async -> [Int] {
[42, 23, 16, 15, 8, 4]
}
func getFriends() async -> [String] {
["Eric", "Maeve", "Otis"]
}
如果想通过这三个属性构造一个User对象,async let将是最简单的方法 -- 每个方法都是异步的, 等待这三个方法全部执行完, 才会去构建新对象。
func printUserDetails() async {
async let username = getUser()
async let scores = getHighScores()
async let friends = getFriends()
let user = await UserData(name: username, friends: friends, highScores: scores)
print("Hello, my name is \(user.name), and I have \(user.friends.count) friends!")
}
async let必须在声明为async的context中, 如果缺少async标记, 那么async let将不会等待结果产生, 直到离开当前这个作用域。
在那些会抛出异常的方法中, 我们也不必使用try加async let, 因为一旦发生错误, 会直接转到你await的那个结果处, 所以我们不用写try await someFunction(), 直接async let xx = someFunction()就完事! 这个例子中我们本来要写try await fibonacc(of:), 但是留到了最后写。
enum NumberError: Error {
case outOfRange
}
func fibonacci(of number: Int) async throws -> Int {
if number < 0 || number > 22 {
throw NumberError.outOfRange
}
if number < 2 { return number }
async let first = fibonacci(of: number - 2)
async let second = fibonacci(of: number - 1)
return try await first + second
}
优雅的处理外部传入的闭包
比如在外部定义了这样一个方法,我们不方便直接改造成async await:
func fetchLatestNews(completion: @escaping ([String]) -> Void) {
DispatchQueue.main.async {
completion(["Swift 5.5 release", "Apple acquires Apollo"])
}
}
我们可以用一个新的fetchLatestNews()方法将这个闭包包起来:
func fetchLatestNews() async -> [String] {
await withCheckedContinuation { continuation in
fetchLatestNews { items in
continuation.resume(returning: items)
}
}
}
我们可以这样调用了:
func printNews() async {
let items = await fetchLatestNews()
for item in items {
print(item)
}
}
3、@MainActor
使用@MainActor可以替代DispatchQueue.main
class OldDataController {
func save() -> Bool {
guard Thread.isMainThread else {
return false
}
print("Saving data…")
return true
}
}
class NewDataController {
@MainActor func save() {
print("Saving data…")
}
}
4、if 作为后缀表达式
Text("Welcome")
#if os(iOS)
.font(.largeTitle)
#else
.font(.headline)
#endif
还可以嵌套:
#if os(iOS)
.font(.largeTitle)
#if DEBUG
.foregroundColor(.red)
#endif
#else
.font(.headline)
#endif
使用可以很广泛:
let result = [1, 2, 3]
#if os(iOS)
.count
#else
.reduce(0, +)
#endif
print(result)
3、CustomStringConvertible
CustomStringConvertible 和CustomDebugStringConvertible这两个协议类似于Objective-C中的重写description方法,继承协议实现description和debugDescription属性,即可打印出想要的数据内容。
在通知类型中我们添加了此协议用来展示不同通知类型的描述文本:
enum NoticeType: Int, CustomStringConvertible {
/// 未知
case unknown = 0
/// 收到的喜欢
case receivedLike
/// 评论
case comment
/// 通知
case notification
/// 通知类型描述
var description: String {
switch self {
case .receivedLike:
return "收到的喜欢"
case .comment:
return "评论"
case .notification:
return "通知"
default:
return ""
}
}
}
在枚举类型中也为其添加上了请求方法:
/// 请求路径
func api() -> String {
switch self {
case .receivedLike, .notification:// 喜欢和通知
return "v1.1/messagecenter.api"
case .comment:// 评论
return "v1.1/usertimeline.api"
default:
return ""
}
}
可以通过以下方式来进行使用:
self.type.api()
4、property wrapper
什么是property wrapper?
property wrapper可以理解为给属性property加了一个封装层,用来定义属性在存取时的存储或计算方式,可以将不同的property的set和get方法中的公共逻辑抽取出来。
我们先看一个常见的使用UserDefaults存取数据的例子:
extension UserDefaults {
public enum Keys {
static let hasSeenAppIntroduction = "hasSeenAppIntroduction"
}
/// Indicates whether or not the user has seen the onboarding.
var hasSeenAppIntroduction: Bool {
set {
set(newValue, forKey: Keys.hasSeenAppIntroduction)
}
get {
return bool(forKey: Keys.hasSeenAppIntroduction)
}
}
}
可以这样使用该property:
UserDefaults.standard.hasSeenAppIntroduction = true
print(UserDefaults.standard.hasSeenAppIntroduction) // Prints: true
但假如有多个property呢?
extension UserDefaults {
public enum Keys {
static let hasSeenAppIntroduction = "hasSeenAppIntroduction"
static let hasSeenGiftTip = "hasSeenGiftTip"
static let hasSeenTagTip = "hasSeenTagTip"
}
/// Indicates whether or not the user has seen the onboarding.
var hasSeenAppIntroduction: Bool {
set {
set(newValue, forKey: Keys.hasSeenAppIntroduction)
}
get {
return bool(forKey: Keys.hasSeenAppIntroduction)
}
}
/// Indicates whether or not the user has seen the gift tip.
var hasSeenGiftTip: Bool {
set {
set(newValue, forKey: Keys.hasSeenGiftTip)
}
get {
return bool(forKey: Keys.hasSeenGiftTip)
}
}
/// Indicates whether or not the user has seen the tag tip.
var hasSeenTagTip: Bool {
set {
set(newValue, forKey: Keys.hasSeenTagTip)
}
get {
return bool(forKey: Keys.hasSeenTagTip)
}
}
}
敏锐的同学应该可以发现问题了:随着属性的增加,文件越来越大,并且都是重复的代码。property wrapper的诞生解决了这个问题。
如果使用property wrapper会变成什么样?
extension UserDefaults {
@UserDefault(key: "hasSeenAppIntroduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
@UserDefault(key: "hasSeenGiftTip", defaultValue: false)
static var hasSeenGiftTip: Bool
@UserDefault(key: "hasSeenTagTip", defaultValue: false)
static var hasSeenTagTip: Bool
}
@propertyWrapper
struct UserDefault<Value> {
let key: String
let defaultValue: Value
var container: UserDefaults = .standard
var wrappedValue: Value {
get {
return container.object(forKey: key) as? Value ?? defaultValue
}
set {
container.set(newValue, forKey: key)
}
}
}
使用该property的方式变为:
UserDefaults.hasSeenAppIntroduction = false
print(UserDefaults.hasSeenAppIntroduction) // Prints: false
如何创建property wrapper
还是上面的例子:
@propertyWrapper
struct UserDefault<Value> {
let key: String
let defaultValue: Value
var container: UserDefaults = .standard
var wrappedValue: Value {
get {
return container.object(forKey: key) as? Value ?? defaultValue
}
set {
container.set(newValue, forKey: key)
}
}
}
要创建一个property wrapper,我们可以创建一个struct(也可以是enum或class),内部必须包含一个名为wrappedValue的属性。使用@propertyWrapper来修饰这个struct,就可以用该struct作为关键字来修饰property,比如:
@UserDefault(key: "hasSeenAppIntroduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
该property的存取逻辑是用wrappedValue的set和get方法来实现的。并且可以使用范型的能力,来存取不同数据类型的property,比如:
@UserDefault(key: "username", defaultValue: "xiejiapei")
static var username: String
但是如果我们想创建一个optional的属性该怎么办?我们可以利用自定义的AnyOptional协议:
/// Allows to match for optionals with generics that are defined as non-optional.
public protocol AnyOptional {
/// Returns `true` if `nil`, otherwise `false`.
var isNil: Bool { get }
}
extension Optional: AnyOptional {
public var isNil: Bool { self == nil }
}
为了遵循这个协议,我们需要扩展一下property wrapper,增加不必指定默认值的初始化方法,并且将默认值设置为nil。
extension UserDefault where Value: ExpressibleByNilLiteral {
/// Creates a new User Defaults property wrapper for the given key.
/// - Parameters:
/// - key: The key to use with the user defaults store.
init(key: String, _ container: UserDefaults = .standard) {
self.init(key: key, defaultValue: nil, container: container)
}
}
接下来需要改造一下property wrapper,让其拥有从UserDefaults中移除对象的能力:
@propertyWrapper
struct UserDefault<Value> {
let key: String
let defaultValue: Value
var container: UserDefaults = .standard
var wrappedValue: Value {
get {
return container.object(forKey: key) as? Value ?? defaultValue
}
set {
// Check whether we're dealing with an optional and remove the object if the new value is nil.
if let optional = newValue as? AnyOptional, optional.isNil {
container.removeObject(forKey: key)
} else {
container.set(newValue, forKey: key)
}
}
}
}
这样,我们就可以定义optional的property了:
extension UserDefaults {
@UserDefault(key: "year_of_birth")
static var yearOfBirth: Int?
}
UserDefaults.yearOfBirth = 1990
print(UserDefaults.yearOfBirth) // Prints: 1990
UserDefaults.yearOfBirth = nil
print(UserDefaults.yearOfBirth) // Prints: nil
projectedValue的使用
除了wrappedValue,property wrapper还可以定义projectedValue来扩展功能。举个例子:
@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber) // Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber) // Prints "true"
如果属性someNumber设置了超出最大值(12)的数字,SmallNumber内部就会对数字进行调整,此时projectedValue的作用就是用来记录是否发生了调整。可以使用$属性名来访问projectedValue的值。
我们还可以使用projectedValue来访问property wrapper中的私有变量。举个例子:
@propertyWrapper
struct UserDefault<Value> {
let key: String
let defaultValue: Value
var container: UserDefaults = .standard
var wrappedValue: Value {
get {
return container.object(forKey: key) as? Value ?? defaultValue
}
set {
container.set(newValue, forKey: key)
}
}
var projectedValue: UserDefault<Value> {
self
}
}
projectedValue返回的是struct本身,因此可以通过这种方法来访问其内部的私有变量。
extension UserDefaults {
@UserDefault(key: "hasSeenAppIntroduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool
static func printKey() {
print($hasSeenAppIntroduction.key) // "hasSeenAppIntroduction"
}
}
5、截取字符串
将String转成NSString
转换乘NSString后就可以使用了substring to:/from:/with:(range)而且返回值为String类型。
let movie = "当幸福来敲门"
let subString = (movie as NSString).substring(with: NSMakeRange(2, 3))
print("截取后的字符串:\(subString)")
使用新的字符串处理方法:
let movie = "当幸福来敲门"
let subString = movie[1..<4]
print("截取后的字符串:\(subString)")
传入一个Range区间进行截取,又不行?这是由于截取字符串所用的index range不能像OC那样,所以要还要一步处理才行。
let movie = "当幸福来敲门"
let indexStart = movie.startIndex
let indexTwo = movie.index(indexStart, offsetBy: 1)
let indexFive = movie.index(indexStart, offsetBy: 3)
let subString = movie[indexTwo..<indexFive]
print("截取后的字符串:\(subString)")
相信很多人觉得麻烦,还不如转成NSString简单,一句话就能搞定,所以现在还有一种方法就是通过两个Swift方法来处理,可以直接使用Int值来当参数,不需要专门去生成Index、Range。
let movie = "当幸福来敲门"
let subString = movie.dropFirst(1).prefix(2)
print("截取后的字符串:\(subString)")
dropFirst()用来丢弃前面的指定个数的字符来截取字符串,丢弃后prefix()剪切头部制定个数的字符串来获取subString。同样也有dropLast和suffix方法,具体使用类似。由于drop方法得到的字符串是substring类型,所以需要转化一下,使用String(movie.dropFirst(1))转化为String即可。











网友评论