swift小技巧之Extension的使用

遵守代理或者数据源的时候使用Extension

这是一个很常见的编码需求.控制器中创建了一个tableView,设置tableView的数据源与代理给控制器。

  • 常规做法

    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
        override func viewDidLoad() {
            super.viewDidLoad()
            let tableView = UITableView()
            tableView.dataSource = self
            tableView.delegate = self
            view.addSubview(tableView)
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 5
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            return UITableViewCell()
        }
    }

    或者这样:

    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            let tableView = UITableView()
            tableView.dataSource = self
            tableView.delegate = self
            view.addSubview(tableView)
        }
    }
    
    extension ViewController: UITableViewDataSource, UITableViewDelegate {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 5
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            return UITableViewCell()
        }
    }
  • 建议做法

    更建议这样写,将数据源和代理分别使用一个Extension去进行遵守,这样虽然代码行多了点,但是对于分隔业务与功能是非常清晰的。
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            let tableView = UITableView()
            tableView.dataSource = self
            tableView.delegate = self
            view.addSubview(tableView)
        }
    }
    
    extension ViewController: UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 5
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            return UITableViewCell()
        }
    }
    
    extension ViewController: UITableViewDelegate {
        
    }
  • 总结

    Extension可以用于遵守代理与数据源使用,同时建议每遵守一个协议就另起一个分类,因为Extension的用途之一就是用来分隔不同的业务。

    加上再使用//MARK: - 极度舒适。

不同的业务使用Extension

其实上面的遵守代理和数据源使用Extension时我就是用了这条原则。比如我在一个控制器中,有很多不同的业务逻辑,比如需要进行网络请求,比如有多个按钮的点击事件。

那么你可以这么使用Extension,使得代码在可读性和可维护性上所有提高。

  • 建议用法
    // MARK: - 网络请求处理模块
    extension ViewController {
    
    }
    // MARK: - 按钮点击事件的处理模块
    extension ViewController {
    
    }
    .
    .
    .

你可以继续按照这个思路去添加Extension去分离不同业务层。

私有API与对外API使用Extension

同事刚从OC转Swift的时候,经常和我抱怨,OC有.h和.m文件,使用类的时候,点击.h文件就可以很清晰的看到该类的那些API我是可以使用的,而Swift中只有一个.swift文件,不能一眼看出那些是私有API那些是对外API。需要看private 和 fileprivate关键字才行。

建议通过Extension进行私有API与对外API的分离。当然这个和不同的业务使用Extension进行分离有点相违背,不过团队合作的时候可以约定一个规则,便于进行风格管理。

  • 建议用法

    // MARK: - 私有API
    private extension ViewController {
        
    }
    
    fileprivate extension ViewController {
        
    }
    
    // MARK: - 对外API
    extension ViewController {
    
    }

    或者在声明类class的大括号中全部写对外API函数,在Extension中写私有API函数。

    // MARK: - 对外API
    class ViewController: UIViewController {
    
    }
    
    // MARK: - 私有API
    extension ViewController {
    
    }

Protocol与Extension配合使用,变相实现多继承

需要注意的是这里的Protocol并不是上文提到的代理或者数据源,你可以理解为更加接近于‘继承’的样子。

  • 变相实现多继承

    public protocol URLConvertible {
        func asURL() throws -> URL
    }
    
    extension String: URLConvertible {
        public func asURL() throws -> URL {
            guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) }
            return url
        }
    }
    
    extension URL: URLConvertible {
        public func asURL() throws -> URL { return self }
    }
    
    extension URLComponents: URLConvertible {
        public func asURL() throws -> URL {
            guard let url = url else { throw AFError.invalidURL(url: self) }
            return url
        }
    }

    这里你可以理解为定义了URLConvertible协议,使得String、URL、URLComponents三个不同的类型,最终都被碾平为URLConvertible的类型了,这样对于使用者入参的时候,有了更多的选择。于是我们可以看到在Alamofire中的请求Api中是这样写的:

    @discardableResult
    public func request(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil)
    -> DataRequest
    {
        return SessionManager.default.request(
            url,
            method: method,
            parameters: parameters,
            encoding: encoding,
            headers: headers
        )
    }

    我对于url的入参具体是String、URL还是其他类型并不关心,只要遵守URLConvertible协议即可,你甚至可以自己通过遵守协议URLConvertible来碾平自定义的类。

使用Extension的注意事项

  • class的Extension中不能写一级构造函数与析构函数,这两个函数必须在声明的class中进行使用。struct的Extension中可以写构造函数。

  • class的Extension中可以写便利构造函数(二级构造函数)。

  • Extension中不能定义属性,如果非要定义,请使用Runtime的那一套原则。

  • Extension中可以定义只读计算属性。

Extension到底啥个用法?

做一个比较生动的例子:

就像火影忍者的影分身之术,每Extension一个,就好像多了一个影分身,而这个影分身去遵守代理、遵守数据源,遵守协议等,去实现不同的功能。那么每个影分身会有着不同技能,而其本体则可以任意调用这些技能。

posted on 2022-02-21 16:33  梁飞宇  阅读(575)  评论(0)    收藏  举报