美文网首页程序员
Swift 奇巧淫技

Swift 奇巧淫技

作者: SSBun | 来源:发表于2020-11-12 15:27 被阅读0次

    转载至 我的博客

    Swift 中的正常操作!!!

    传入 KeyPath 作为函数

    Swift 5.2新特性,使用 map 等函数来进行数据转换时是更加简洁了,一个小小的改动。

    struct Movie {
        var name: String
        var isFavorite: Bool
        ...
    }
    
    let movies: [Movie] = loadMovies()
    
    // Equivalent to movies.map { $0.name }
    let movieNames = movies.map(\.name)
    
    // Equivalent to movies.filter { $0.isFavorite }
    let favoriteMovies = movies.filter(\.isFavorite)
    

    使用 keypath 来匹配 switch

    Swift 的 switch 已经如此强大了,配合 ~= 符号能让我们实现更强大的 switch

    func ~=<T>(lhs: KeyPath<T, Bool>, rhs: T) -> Bool {
        rhs[keyPath: lhs]
    }
    
    func handle(_ character: Character) {
        switch character {
        case "<":
            parseElement()
        case "#":
            parseHashtag()
        case \.isNumber:
            parseNumber()
        case \.isNewline:
            startNewLine()
        default:
            parseAnyCharacter()
        }
    }
    

    计算属性和有返回值方法中如果自有一个表达式可以省略 return

    好用

    extension MarkdownReader {
        var isAtStart: Bool { index == string.startIndex }
        var didReachEnd: Bool { index == string.endIndex }
        var currentCharacter: Character { string[index] }
        
        func encodeCurrentCharacter() -> String {
            currentCharacter.encoded()
        }
    }
    

    Swift5.1 中枚举的关联值也可以使用默认参数

    撒花

    // Associated enum value defaults are specified the same way as
    // default function arguments:
    enum Content {
        case text(String)
        case image(Image, description: String? = nil)
        case video(Video, autoplay: Bool = false)
    }
    
    // At the call site, any associated value that has a default
    // can be omitted, and the default will be used:
    let video = Content.video(Video(url: url))
    

    使用元组成组的捕获异常

    如果遇见调用多个会抛出一样的函数,可以使用元组将其括起来,那样的话你就只需写一个 try。

    // Here we have three highly related expressions that are
    // all throwing, requiring separate assignments and separate
    // 'try' keywords:
    let contentFolder = try Folder.current.subfolder(named: "content")
    let templatesFolder = try Folder.current.subfolder(named: "templates")
    let output = try Folder.current.createSubfolderIfNeeded(withName: "output")
    
    // By combining them all into a tuple, we only need one
    // 'try', and can easily group our data into a single,
    // lightweight container:
    let folders = try (
        content: Folder.current.subfolder(named: "content"),
        templates: Folder.current.subfolder(named: "templates"),
        output: Folder.current.createSubfolderIfNeeded(withName: "output")
    )
    
    // The call sites also become really nice and clean, with
    // increased "namespacing" for our local variables:
    readFiles(in: folders.content)
    loadTemplates(from: folders.templates)
    

    用函数来联合变量

    最后我们将生成一个无参闭包,针对一些闭包 API 可以直接传递,并且不需要在闭包当中捕获 self。

    func combine<A, B>(_ value: A, with closure: @escaping (A) -> B) -> () -> B {
        return { closure(value) }
    }
    
    // BEFORE:
    
    class ProductViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            buyButton.handler = { [weak self] in
                guard let self = self else {
                    return
                }
                
                self.productManager.startCheckout(for: self.product)
            }
        }
    }
    
    // AFTER:
    
    class ProductViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            buyButton.handler = combine(product, with: productManager.startCheckout)
        }
    }
    

    善用方法作依赖注入

    如果需要注入的依赖并不复杂,就不允许使用协议或是导入整个 Model 了。

    final class ArticleLoader {
        typealias Networking = (Endpoint) -> Future<Data>
        
        private let networking: Networking
        
        init(networking: @escaping Networking = URLSession.shared.load) {
            self.networking = networking
        }
        
        func loadLatest() -> Future<[Article]> {
            return networking(.latestArticles).decode()
        }
    }
    

    解包可选值失败时可以抛出一个异常

    不用修改方法本身,就能将其改造成一个可以抛出异常的 API

    extension Optional {
        func orThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
            switch self {
            case .some(let value):
                return value
            case .none:
                throw errorExpression()
            }
        }
    }
    
    let file = try loadFile(at: path).orThrow(MissingFileError())
    

    自定义 UIView 的 layer

    这样的话就可以自定义许多有趣的 View 了,不用添加多余的 layer,不用管理 layer 的布局,就像一个系统的 view 一样。

    final class GradientView: UIView {
        override class var layerClass: AnyClass { return CAGradientLayer.self }
    
        var colors: (start: UIColor, end: UIColor)? {
            didSet { updateLayer() }
        }
    
        private func updateLayer() {
            let layer = self.layer as! CAGradientLayer
            layer.colors = colors.map { [$0.start.cgColor, $0.end.cgColor] }
        }
    }
    

    如果枚举的关联类型遵守 Equatable 则枚举自动遵守 Equatable

    看起来很合理

    struct Article: Equatable {
        let title: String
        let text: String
    }
    
    struct User: Equatable {
        let name: String
        let age: Int
    }
    
    extension Navigator {
        enum Destination: Equatable {
            case profile(User)
            case article(Article)
        }
    }
    
    func testNavigatingToArticle() {
        let article = Article(title: "Title", text: "Text")
        controller.select(article)
        XCTAssertEqual(navigator.destinations, [.article(article)])
    }
    

    元组类型的解构

    在 Swift 中,元组类型在赋值时可以像其他语言那样使用解构

    class ImageTransformer {
        private var queue = [(image: UIImage, transform: Transform)]()
    
        private func processNext() {
            // When unwrapping an optional tuple, you can assign the members
            // directly to local variables.
            guard let (image, transform) = queue.first else {
                return
            }
    
            let context = Context()
            context.draw(image)
            context.apply(transform)
            ...
        }
    }
    

    嵌套泛型类型

    嵌套的泛型类型,能够继承上层类型的泛型定义,这样省去了重复定义相同类型泛型的麻烦。

    struct Task<Input, Output> {
        typealias Closure = (Input) throws -> Output
    
        let closure: Closure
    }
    
    extension Task {
        enum Result {
            case success(Output)
            case failure(Error)
        }
    }
    

    alias 也能用泛型

    看起来似乎有一些用处

    typealias Pair<T> = (T, T)
    
    extension Game {
        func calculateScore(for players: Pair<Player>) -> Int {
            ...
        }
    }
    

    为类型扩展静态工厂方法

    在构建 UI 时非常的有用,特别是对统一设计风格的通用 UI 来说,高度封装,使得对 UI 的修改变的简单,代码也更加的简洁,如果配合上点语法就更完美了。

    extension UILabel {
        static func makeForTitle() -> UILabel {
            let label = UILabel()
            label.font = .boldSystemFont(ofSize: 24)
            label.textColor = .darkGray
            label.adjustsFontSizeToFitWidth = true
            label.minimumScaleFactor = 0.75
            return label
        }
    
        static func makeForText() -> UILabel {
            let label = UILabel()
            label.font = .systemFont(ofSize: 16)
            label.textColor = .black
            label.numberOfLines = 0
            return label
        }
    }
    
    class ArticleViewController: UIViewController {
        lazy var titleLabel = UILabel.makeForTitle()
        lazy var textLabel = UILabel.makeForText()
    }
    

    将实例方法当做静态方法来调用

    同样是对函数式编程的实践

    // This produces a '() -> Void' closure which is a reference to the
    // given view's 'removeFromSuperview' method.
    let closure = UIView.removeFromSuperview(view)
    
    // We can now call it just like we would any other closure, and it
    // will run 'view.removeFromSuperview()'
    closure()
    
    // This is how running tests using the Swift Package Manager on Linux
    // works, you return your test functions as closures:
    extension UserManagerTests {
        static var allTests = [
            ("testLoggingIn", testLoggingIn),
            ("testLoggingOut", testLoggingOut),
            ("testUserPermissions", testUserPermissions)
        ]
    }
    

    在 for-loop 循环中使用 where

    如果你需要在循环中使用 if 来筛选数据,那不妨使用 where 来让代码的结构更加清晰

    func archiveMarkedPosts() {
        for post in posts where post.isMarked {
            archive(post)
        }
    }
    
    func healAllies() {
        for player in players where player.isAllied(to: currentPlayer) {
            player.heal()
        }
    }
    

    使用点语法来访问静态方法、静态变量和构造方法

    省略类型,直接使用点语法访问,用在 API 和默认值上时,让代码开起来无比的简洁

    public enum RepeatMode {
        case times(Int)
        case forever
    }
    
    public extension RepeatMode {
        static var never: RepeatMode {
            return .times(0)
        }
    
        static var once: RepeatMode {
            return .times(1)
        }
    }
    
    view.perform(animation, repeated: .once)
    
    // To make default parameters more compact, you can even use init with dot syntax
    
    class ImageLoader {
        init(cache: Cache = .init(), decoder: ImageDecoder = .init()) {
            ...
        }
    }
    

    在 enum 和 struct 的构造方法里面可以直接设置 self

    class 中我们只能设置 self 的属性,而在 enum 和 struct 中我们能设置 self 的值,这样的话我们就能很方便的为它们扩展各种便利构造方法了。

    extension Bool: AnswerConvertible {
        public init(input: String) throws {
            switch input.lowercased() {
            case "y", "yes", "👍":
                self = true
            default:
                self = false
            }
        }
    }
    

    ExpressibleBy... 系列函数的使用

    一定要确保使用时语义清楚,特别是针对自定义类型,否则一段时间后,你都未必知道它到底做了什么

    extension URL: ExpressibleByStringLiteral {
        // By using 'StaticString' we disable string interpolation, for safety
        public init(stringLiteral value: StaticString) {
            self = URL(string: "\(value)").require(hint: "Invalid URL string literal: \(value)")
        }
    }
    
    // We can now define URLs using static string literals 🎉
    let url: URL = "https://www.swiftbysundell.com"
    

    闭包类型用来做泛型约束

    闭包类型也是类型,作为 Swift 中的第一公民,当然也能用来做泛型约束啦

    extension Sequence where Element == () -> Void {
        func callAll() {
            forEach { $0() }
        }
    }
    
    extension Sequence where Element == () -> String {
        func joinedResults(separator: String) -> String {
            return map { $0() }.joined(separator: separator)
        }
    }
    
    callbacks.callAll()
    let names = nameProviders.joinedResults(separator: ", ")
    

    将一组方法合并起来

    在函数式编程当中,就是所谓的管道函数 |>

    internal func +<A, B, C>(lhs: @escaping (A) throws -> B,
                             rhs: @escaping (B) throws -> C) -> (A) throws -> C {
        return { try rhs(lhs($0)) }
    }
    
    public func run() throws {
        try (determineTarget + build + analyze + output)()
    }
    

    使用 mapflatMap 来优化可选链

    Swift 中的可选链可真是简洁代码杀手,我们总是会陷入 if let 和 gaurd let 的解包泥潭中去,不过在单一参数的情况下,我们可以利用 mapflatMap来简化我们的可选链代码

    // BEFORE
    
    guard let string = argument(at: 1), let url = URL(string: string) else {
        return
    }
    
    handle(url)
    
    // AFTER
    
    argument(at: 1).flatMap(URL.init).map(handle)
    

    使用可变参数

    如果你的 API 需要传入一组手动创建的数据,使用可变参数要比使用数组看起来更合理。

    public extension UIView {
        func addSubviews(_ views: UIView...) {
            views.forEach(self.addSubview(_:))
        }
    }
    

    有关联值的 enum 类型,在不存入关联值的时候是一个闭包

    看来枚举是函数式编程和柯里化的忠实粉丝啊!

    enum UnboxPath {
        case key(String)
        case keyPath(String)
    }
    
    struct UserSchema {
        static let name = key("name")
        static let age = key("age")
        static let posts = key("posts")
        
        private static let key = UnboxPath.key
    }
    
    print(type(of: UnboxPath.key)) // (String) -> UnboxPath
    

    初始化方法可被用来设置参数和闭包的默认值

    如果用的到的话,代码看起来会更简洁明了

    class Logger {
        private let storage: LogStorage
        private let dateProvider: () -> Date
        
        init(storage: LogStorage = .init(), dateProvider: @escaping () -> Date = Date.init) {
            self.storage = storage
            self.dateProvider = dateProvider
        }
        
        func log(event: Event) {
            storage.store(event: event, date: dateProvider())
        }
    }
    

    利用 #function 将属性名指定为 UserDefault 的 key

    灵魂操作!!!

    extension UserDefaults {
        var onboardingCompleted: Bool {
            get { return bool(forKey: #function) }
            set { set(newValue, forKey: #function) }
        }
    }
    

    类型名称与系统框架内类型名称重复

    首先尽量不用使用与系统内部框架重名的标识符,当然如果你非要用,可以使用 Swift. 来为系统类型指定命名空间

    extension Command {
        enum Error: Swift.Error {
            case missing
            case invalid(String)
        }
    }
    

    善用 typealias 来减少类型名的长度

    首先,就像在 OC 中一样用 typealias 来声明长长的闭包类型。其次嵌套类型的名称往往很长,如果需要用到了一个其他类型嵌套下的子类型时,不妨在当前作用域给它起一个短别名。

    public class PathFinder<Object: PathFinderObject> {
        public typealias Map = Object.Map
        public typealias Node = Map.Node
        public typealias Path = PathFinderPath<Object>
        
        public static func possiblePaths(
            for object: Object,
            at rootNode: Node,
            on map: Map
        ) -> Path.Sequence {
            return .init(object: object, rootNode: rootNode, map: map)
        }
    }
    

    自动闭包

    如果你的方法中有一个参数需要耗时计算才能得到,而这个参数可能不会被使用或是不会被立即使用,这个时候我们应该使用 @autoclosure将其转换成一个闭包。这样即保证了调用形式和原来一样,也能够保证仅在需要的时候才去运算获取相应的值。

    // Swift 中的 || 就是这样实现的,如果左侧为 ture 就不用再费力执行右侧的结果了。
    public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool {
        if lhs {
            return true
        } else {
            return try rhs()
        }
    }
    

    使用嵌套类型实现命名空间

    很常用,例如一个特殊 API 返回的数据类型不通用,但是又想使用 Model 的便利性,此时创建一个全局的 Model 就太过浪费了,在当前类型的作用域下创建一个 Model 是一个不错的方法。当然也包括了 UI 模块,一个 UI 模块内部的更小模块,无须占用全局的类型名称,可以创建在当前模块的作用域下。

    public struct Map {
        public struct Model {
            public let size: Size
            public let theme: Theme
            public var terrain: [Position : Terrain.Model]
            public var units: [Position : Unit.Model]
            public var buildings: [Position : Building.Model]
        }
        
        public enum Direction {
            case up
            case right
            case down
            case left
        }
        
        public struct Position {
            public var x: Int
            public var y: Int
        }
        
        public enum Size: String {
            case small = "S"
            case medium = "M"
            case large = "L"
            case extraLarge = "XL"
        }
    }
    

    相关文章

      网友评论

        本文标题:Swift 奇巧淫技

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