美文网首页
Swift:零散语法(下)

Swift:零散语法(下)

作者: 时光啊混蛋_97boy | 来源:发表于2022-04-23 12:18 被阅读0次

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的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必须在声明为asynccontext中, 如果缺少async标记, 那么async let将不会等待结果产生, 直到离开当前这个作用域。

在那些会抛出异常的方法中, 我们也不必使用tryasync 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

CustomStringConvertibleCustomDebugStringConvertible这两个协议类似于Objective-C中的重写description方法,继承协议实现descriptiondebugDescription属性,即可打印出想要的数据内容。

在通知类型中我们添加了此协议用来展示不同通知类型的描述文本:

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加了一个封装层,用来定义属性在存取时的存储或计算方式,可以将不同的propertysetget方法中的公共逻辑抽取出来。

我们先看一个常见的使用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(也可以是enumclass),内部必须包含一个名为wrappedValue的属性。使用@propertyWrapper来修饰这个struct,就可以用该struct作为关键字来修饰property,比如:

@UserDefault(key: "hasSeenAppIntroduction", defaultValue: false)
static var hasSeenAppIntroduction: Bool

property的存取逻辑是用wrappedValuesetget方法来实现的。并且可以使用范型的能力,来存取不同数据类型的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)
            }
        }
    }
}

这样,我们就可以定义optionalproperty了:

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的使用

除了wrappedValueproperty 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值来当参数,不需要专门去生成IndexRange

let movie = "当幸福来敲门"
let subString = movie.dropFirst(1).prefix(2)
print("截取后的字符串:\(subString)")

dropFirst()用来丢弃前面的指定个数的字符来截取字符串,丢弃后prefix()剪切头部制定个数的字符串来获取subString。同样也有dropLastsuffix方法,具体使用类似。由于drop方法得到的字符串是substring类型,所以需要转化一下,使用String(movie.dropFirst(1))转化为String即可。

相关文章

  • Swift:零散语法(下)

    原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...

  • Swift高级

    接下来我再看一下,Swift高级。语法,可能会被认为是简单的、零散的,但是我们学习的重点是,将这些简单的、零散的语...

  • Swift 基本语法04-"switch"和

    Swift 基本语法01-Swift简介Swift 基本语法02-"let"和"var"Swift 基本语法03-...

  • Swift 基本语法06-数组和字典

    Swift 基本语法01-Swift简介Swift 基本语法02-"let"和"var"Swift 基本语法03-...

  • Swift 基本语法03-"if let"和

    Swift 基本语法01-Swift简介Swift 基本语法02-"let"和"var"Swift 基本语法04-...

  • Swift 基本语法05-"String"

    Swift 基本语法01-Swift简介Swift 基本语法02-"let"和"var"Swift 基本语法03-...

  • Swift基本语法之函数

    Swift基本语法之初体验-常量变量-数据类型 Swift基本语法之逻辑分支 Swift基本语法之循环 Swift...

  • Swift基本语法之数组和字典

    Swift基本语法之初体验-常量变量-数据类型 Swift基本语法之逻辑分支 Swift基本语法之循环 Swift...

  • Swift基本语法之元组和可选类型

    Swift基本语法之初体验-常量变量-数据类型 Swift基本语法之逻辑分支 Swift基本语法之循环 Swift...

  • Swift基本语法之闭包

    Swift基本语法之初体验-常量变量-数据类型 Swift基本语法之逻辑分支 Swift基本语法之循环 Swift...

网友评论

      本文标题:Swift:零散语法(下)

      本文链接:https://www.haomeiwen.com/subject/kazxertx.html