内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

Swift 封装篇

2018-05-15 16:34 出处:清屏网 人气: 评论(0

简评:在日益壮大的项目工程中,保持代码的封装性是一个很大的挑战。随着新功能的添加,对象通常会承担新的职责,需要也其他的对象一起工作,有时新的改动可能会违背最初的设计,避免抽象泄露(leaking abstractions)不是一件容易的事情。

好的 API 规范可以帮助我们封装代码避免与其他类型共享不必要的实现细节。下面几种方法是实现代码封装的常见方法。

隐藏实现细节

隐藏实现细节可以帮助我们减少不必要的错误,例如:我们构建了一个 ProfileViewController 类用于显示当前登录用户的配置文件。它具有一个标题视图(headerView),并且我们在 viewDidLoad 中设置 delegate,代码如下所示:

class ProfileViewController: UIViewController, ProfileHeaderViewDelegate {
    lazy var headerView = ProfileHeaderView()

    override func viewDidLoad() {
        super.viewDidLoad()
        headerView.delegate = self
        view.addSubview(headerView)
    }
}

这样子咋一看没有问题。但是假如你要将这个类给同事用,同事需要实现这样一种功能:可让用户通过应用内购买来解锁高级模式,这种模式下 headerView 需要变得更加酷炫。由于 headerView 没有添加 private/fileprivate 修饰,所以这个 headerView 相当于暴露给了你的同事,他很可能用如下代码实现这个 vip 功能:

func userDidUnlockPremiumSubscription() {
    profileViewController.headerView = PremiumHeaderView()
}

当执行上面函数的时候原来的 headerView 已经丢失,并且新的 headerView 也没有设置 delegate,这样可能应用会卡在 ProfileViewController 这个界面,最后测试会直接找到你的头上。

所以直接在接口上现在这种类情况,可以避免不必要的麻烦,这就是封装的意义。

我们将上面代码改动一下:

class ProfileViewController: UIViewController, ProfileHeaderViewDelegate {
    private lazy var headerView = ProfileHeaderView()
}

这个时候你的同事找上门了,说内购成功后需要显示 vip 的专属界面。ok 我们只需要扩展原来的功能暴露一个 enterMode 方法即可:

extension ProfileViewController {
    enum Mode {
        case standard
        case premium
    }

    func enterMode(_ mode: Mode) {
        switch mode {
        case .standard:
            headerView.applyStandardAppearance()
        case .premium:
            headerView.applyPremiumAppearance()  
        }
    }
}

这你的同事只需要在内购成功回调中调用如下代码即可:

func userDidUnlockPremiumSubscription() {
    profileViewController.enterMode(.premium)
}

协议和私有类型

封装代码的另一个后方法是将协议和私有实现向结合。举个例子:

加入我们正在构建一个需要在各种 ViewController 中加载大量图像的 APP。为了解决这个问题,我们创建一个协议,该协议定义了 ImageLoader 我们 ViewController 可以访问的 API:

protocol ImageLoader {
    typealias Handler = (Result<UIImage>) -> Void

    func loadImage(from url: URL, then handler: Handler)
}

由于我们有很多的 ViewController 需要加载图像,因此希望每个 ViewController 都是用自己的 ImageLoader,当 ViewController 销毁的时候,需要取消未完成的请求。

为了很好的处理这个问题,我们使用工程模式构建一个 ImageLoaderFatory ,这样可以方便的为每个 ViewController 构建 ImageLoader。ImageLoaderFatory 如下所示:

class ImageLoaderFactory {
    private let session: URLSession

    init(session: URLSession = .shared) {
        self.session = session
    }

    func makeImageLoader() -> ImageLoader {
        return SessionImageLoader(session: session)
    }
}

我们注意到这里并没有 makeImageLoader 方法并没有直接返回图片加载器的具体类型,而是指明返回对象遵循 ImageLoader 这个协议。这样我们就将 ImageLoader 具体实现隐藏起来了。

private extension ImageLoaderFactory {
    class SessionImageLoader: ImageLoader {
        let session: URLSession
        private var ongoingRequests = Set<Request>()

        init(session: URLSession) {
            self.session = session
        }

        deinit {
            cancelAllRequests()
        }

        func loadImage(from url: URL,
                       then handler: (Result<UIImage>) -> Void) {
            let request = Request(url: url, handler: handler)
            perform(request)
        }
    }
}

这样做即给我们提供了很大的灵活性,同时也保持了API 非常简明。

第三方依赖

上面的方法对封装第三方库也是非常的有用。

就像我们使用协议来隐藏具体的实现(SessionImageLoader)一样,我们同样可以使用协议来隐藏第三方库,比如现在我们使用 AmazingImages 这个库,我们可以用如下代码进行封装:

import AmazingImages

class ImageLoaderFactory {
    func makeImageLoader() -> ImageLoader {
        return AmazingImageLoader()
    }
}

这样我们就只有一个文件使用到了 Amazing 这个库,后续我们需要替换其他第三方库也非常方便。

结论

项目越是复杂,应尽可能地封装代码---即使需要添加新功能,引入新的依赖关系或需求变更。这样才能保持良好的体系结构。

分享给小伙伴们:
本文标签: Swift

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号