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一个,就好像多了一个影分身,而这个影分身去遵守代理、遵守数据源,遵守协议等,去实现不同的功能。那么每个影分身会有着不同技能,而其本体则可以任意调用这些技能。