别人一看到我的 Swift 代码,立刻就会问我为什么如此频繁的使用 extension。这是前几天在我写的另一篇文章中收到的评论:

我大量使用 extension 的主要目的是为了提高代码可读性。以下是我喜欢使用 extension 的场景,尽管 extension 并非是为这些场景设计的。

私有的辅助函数

在 Objective-C 中,我们有 .h 文件和 .m 文件。同时管理这两个文件(以及在工程中有双倍的文件)是一件很麻烦的事情,好在我们只要快速浏览 .h 文件就可以知道这个类对外暴露的 API,而内部的信息则被保存在 .m 文件中。在 Swift 中,我们只有一个文件。

为了一眼就看出一个 Swift 类的公开方法(可以被外部访问的方法),我把内部实现都写在一个私有的 extension 中,比如这样:

// 这样可以一眼看出来,这个结构体中,那些部分可以被外部调用
struct TodoItemViewModel {
let item: TodoItem
let indexPath: NSIndexPath var delegate: ImageWithTextCellDelegate {
return TodoItemDelegate(item: item)
} var attributedText: NSAttributedString {
// the itemContent logic is in the private extension
// keeping this code clean and easy to glance at
return itemContent
}
} // 把所有内部逻辑和外部访问的 API 区隔开来
// MARK: 私有的属性和方法
private extension TodoItemViewModel { static var spaceBetweenInlineImages: NSAttributedString {
return NSAttributedString(string: " ")
} var itemContent: NSAttributedString {
let text = NSMutableAttributedString(string: item.content, attributes: [NSFontAttributeName : SmoresFont.regularFontOfSize(17.0)]) if let dueDate = item.dueDate {
appendDueDate(dueDate, toText: text)
} for assignee in item.assignees {
appendAvatar(ofUser: assignee, toText: text)
} return text
} func appendDueDate(dueDate: NSDate, toText text: NSMutableAttributedString) { if let calendarView = CalendarIconView.viewFromNib() {
calendarView.configure(withDate: dueDate) if let calendarImage = UIImage.imageFromView(calendarView) {
appendImage(calendarImage, toText: text)
}
}
} func appendAvatar(ofUser user: User, toText text: NSMutableAttributedString) {
if let avatarImage = user.avatar {
appendImage(avatarImage, toText: text)
} else {
appendDefaultAvatar(ofUser: user, toText: text)
downloadAvatarImage(forResource: user.avatarResource)
}
} func downloadAvatarImage(forResource resource: Resource?) {
if let resource = resource {
KingfisherManager.sharedManager.retrieveImageWithResource(resource,
optionsInfo: nil,
progressBlock: nil)
{ image, error, cacheType, imageURL in
if let _ = image {
dispatch_async(dispatch_get_main_queue()) {
NSNotificationCenter.defaultCenter().postNotificationName(TodoItemViewModel.viewModelViewUpdatedNotification, object: self.indexPath)
}
}
}
}
} func appendDefaultAvatar(ofUser user: User, toText text: NSMutableAttributedString) {
if let defaultAvatar = user.defaultAvatar {
appendImage(defaultAvatar, toText: text)
}
} func appendImage(image: UIImage, toText text: NSMutableAttributedString) {
text.appendAttributedString(TodoItemViewModel.spaceBetweenInlineImages)
let attachment = NSTextAttachment()
attachment.image = image
let yOffsetForImage = -7.0 as CGFloat
attachment.bounds = CGRectMake(0.0, yOffsetForImage, image.size.width, image.size.height)
let imageString = NSAttributedString(attachment: attachment)
text.appendAttributedString(imageString)
}
}

注意,在上面这个例子中,属性字符串的计算逻辑非常复杂。如果把它写在结构体的主体部分中,我就无法一眼看出这个结构体中哪个部分是重要的(也就是 Objective-C 中写在 .h 文件中的代码)。在这个例子中,使用 extension 使我的代码结构变得更加清晰整洁。

这样一个很长的 extension 也为日后重构代码打下了良好的基础。我们有可能把这段逻辑抽取到一个单独的结构体中,尤其是当这个属性字符串可能在别的地方被用到时。但在编程时把这段代码放在私有的 extension 里面是一个良好的开始。

分组

我最初开始使用 extension 的真正原因是在 Swift 刚诞生时,无法使用 pragma 标记(译注:Objective-C 中的 #pragma mark)。是的,这就是我在 Swift 刚诞生时想做的第一件事。我使用 pragma 来分割 Objective-C 代码,所以当我开始写 Swift 代码时,我需要它。

所以我在 WWDC Swift 实验室时询问苹果工程师如何在 Swift 中使用 pragma 标记。和我交流的那位工程师建议我使用 extension 来替代 pragma 标记。于是我就开始这么做了,并且立刻爱上了使用 extension。

尽管 pragma 标记(Swift 中的 //MARK)很好用,但我们很容易忘记给一段新的代码加上 MARK 标记,尤其是你处在一个具有不同代码风格的小组中时。这往往会导致若干个无关函数被放在了同一个组中,或者某个函数处于错误的位置。所以如果有一组函数应该写在一起,我倾向于把他们放到一个 extension 中。

一般我会用一个 extension 存放 ViewController 或者 AppDelegate 中所有初始化 UI 的函数,比如:

private extension AppDelegate {

    func configureAppStyling() {
styleNavigationBar()
styleBarButtons()
} func styleNavigationBar() {
UINavigationBar.appearance().barTintColor = ColorPalette.ThemeColor
UINavigationBar.appearance().tintColor = ColorPalette.TintColor UINavigationBar.appearance().titleTextAttributes = [
NSFontAttributeName : SmoresFont.boldFontOfSize(19.0),
NSForegroundColorAttributeName : UIColor.blackColor()
]
} func styleBarButtons() {
let barButtonTextAttributes = [
NSFontAttributeName : SmoresFont.regularFontOfSize(17.0),
NSForegroundColorAttributeName : ColorPalette.TintColor
]
UIBarButtonItem.appearance().setTitleTextAttributes(barButtonTextAttributes, forState: .Normal)
}
}

或者把所有和通知相关的逻辑放到一起:

extension TodoListViewController {

    // 初始化时候调用
func addNotificationObservers() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("onViewModelUpdate:"), name: TodoItemViewModel.viewModelViewUpdatedNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("onTodoItemUpdate:"), name: TodoItemDelegate.todoItemUpdatedNotification, object: nil)
} func onViewModelUpdate(notification: NSNotification) {
if let indexPath = notification.object as? NSIndexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
} func onTodoItemUpdate(notification: NSNotification) {
if let itemObject = notification.object as? ValueWrapper<TodoItem> {
let updatedItem = itemObject.value
let updatedTodoList = dataSource.listFromUpdatedItem(updatedItem)
dataSource = TodoListDataSource(todoList: updatedTodoList)
}
}
}

遵守协议

这是一种特殊的分组,我会把所有用来实现某个协议的方法放到一个 extension 中。在 Objective-C 中,我习惯使用 pragma 标记。不过我喜欢 extension 更加彻底的分割和更好的可读性:

struct TodoItemViewModel {
static let viewModelViewUpdatedNotification = "viewModelViewUpdatedNotification" let item: TodoItem
let indexPath: NSIndexPath var delegate: ImageWithTextCellDelegate {
return TodoItemDelegate(item: item)
} var attributedText: NSAttributedString {
return itemContent
}
} // 遵循 ImageWithTextCellDataSource 协议实现
extension TodoItemViewModel: ImageWithTextCellDataSource { var imageName: String {
return item.completed ? "checkboxChecked" : "checkbox"
} var attributedText: NSAttributedString {
return itemContent
}
}

这种方法同样非常适用于分割 UITableViewDataSource 和 UITableViewDelegate 的代码:

// MARK: 表格视图数据源
extension TodoListViewController: UITableViewDataSource { func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return dataSource.sections.count
} func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.numberOfItemsInSection(section)
} func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(String.fromClass(ImageWithTextTableViewCell), forIndexPath: indexPath) as! ImageWithTextTableViewCell
let viewModel = dataSource.viewModelForCell(atIndexPath: indexPath)
cell.configure(withDataSource: viewModel, delegate: viewModel.delegate)
return cell
}
} // MARK: 表格视图代理
extension TodoListViewController: UITableViewDelegate { // MARK: 响应列选择
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier(todoItemSegueIdentifier, sender: self)
} // MARK: 头部视图填充
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if dataSource.sections[section] == TodoListDataSource.Section.DoneItems {
let view = UIView()
view.backgroundColor = ColorPalette.SectionSeparatorColor
return view
}
return nil
} func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if dataSource.sections[section] == TodoListDataSource.Section.DoneItems {
return 1.0
} return 0.0
} // MARK: 删除操作处理
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
} func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? { let deleteAction = UITableViewRowAction(style: .Destructive, title: "Delete") { [weak self] action , indexPath in
if let updatedTodoList = self?.dataSource.listFromDeletedIndexPath(indexPath) {
self?.dataSource = TodoListDataSource(todoList: updatedTodoList)
}
} return [deleteAction]
}
}

模型(Model)

这是一种我在使用 Objective-C 操作 Core Data 时就喜欢采用的方法。由于模型发生变化时,Xcode 会生成相应的模型,所以函数和其他的东西都是写在 extension 或者 category 里面的。

在 Swift 中,我尽可能多的尝试使用结构体,但我依然喜欢使用 extension 将 Model 的属性和基于属性的计算分割开来。这使 Model 的代码更容易阅读:

struct User {
let id: Int
let name: String
let avatarResource: Resource?
} extension User { var avatar: UIImage? {
if let resource = avatarResource {
if let avatarImage = ImageCache.defaultCache.retrieveImageInDiskCacheForKey(resource.cacheKey) {
let imageSize = CGSize(width: 27, height: 27)
let resizedImage = Toucan(image: avatarImage).resize(imageSize, fitMode: Toucan.Resize.FitMode.Scale).image
return Toucan.Mask.maskImageWithEllipse(resizedImage)
}
}
return nil
} var defaultAvatar: UIImage? {
if let defaultImageView = DefaultImageView.viewFromNib() {
defaultImageView.configure(withLetters: initials)
if let defaultImage = UIImage.imageFromView(defaultImageView) {
return Toucan.Mask.maskImageWithEllipse(defaultImage)
}
} return nil
} var initials: String {
var initials = "" let nameComponents = name.componentsSeparatedByCharactersInSet(.whitespaceAndNewlineCharacterSet()) // 得到第一个单词的第一个字母
if let firstName = nameComponents.first, let firstCharacter = firstName.characters.first {
initials.append(firstCharacter)
} // 得到最后一个单词的第一个字母
if nameComponents.count > 1 {
if let lastName = nameComponents.last, let firstCharacter = lastName.characters.first {
initials.append(firstCharacter)
}
} return initials
}
}

长话短说(TL;DR)

尽管这些用法可能不那么“传统”,但 Swift 中 extension 的简单使用,可以让代码质量更高,更具可读性。

转载自:http://swift.gg/2016/05/16/using-swift-extensions/

参考链接:https://www.natashatherobot.com/using-swift-extensions/

Swift 使用Extension 场景 浅析的更多相关文章

  1. Swift protocol extension method is called instead of method implemented in subclass

    Swift protocol extension method is called instead of method implemented in subclass protocol MyProto ...

  2. 【iOS】Swift扩展extension和协议protocol

    加上几个关节前Playground摘要码进入github在,凝视写了非常多,主要是为了方便自己的未来可以Fanfankan. Swift语法的主要部分几乎相同的. 当然也有通用的.运算符重载.ARC. ...

  3. swift class extension 与继承

    1.扩展中无法继承重写已有函数,不能添加函数. Extensions can add new functionality to a type, but they cannot override exi ...

  4. Swift -> RunTime(动态性) 问题 浅析

    Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...

  5. Swift & OC 混编 浅析

    转载自:http://www.infoq.com/cn/articles/wangyi-cartoon-swift-mixed-practice?utm_campaign=rightbar_v2&am ...

  6. Swift 响应式编程 浅析

    这里我讲一下响应式编程(Reactive Programming)是如何将异步编程推到一个全新高度的. 异步编程真的很难 大多数有关响应式编程的演讲和文章都是在展示Reactive框架如何好如何惊人, ...

  7. Swift 与 JSON 数据 浅析

    转载自:http://www.cnblogs.com/theswiftworld/p/4660177.html 我们大家平时在开发 App 的时候,相信接触最多的就是 JSON 数据了.只要你的 Ap ...

  8. MySQL出现Waiting for table metadata lock的场景浅析

    MySQL版本为5.6.12. 在进行alter table操作时,有时会出现Waiting for table metadata lock的等待场景.而且,一旦alter table TableA的 ...

  9. alter table锁表,MySQL出现Waiting for table metadata lock的场景浅析及解决方案

    在修改/增加表字段的时候,发现很慢, show processlist; 时, Waiting for table metadata lock 能一直锁很久. 官网的一段话,可以理解下 http:// ...

随机推荐

  1. LeetCode OJ 40. Combination Sum II

    Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in ...

  2. 创建 .gitignore 文件过滤规

    文件 .gitignore 的格式规范如下: 所有空行或者以注释符号 # 开头的行都会被 Git 忽略. 可以使用标准的 glob 模式匹配. 匹配模式最后跟反斜杠(/)说明要忽略的是目录. 要忽略指 ...

  3. GDI+ 图片转存

    摘自MSDN,其中 GetEncoderClsid 第一个参数可为 L"image/bmp" L"image/jpeg" L"image/gif&qu ...

  4. 显示进度条tqdm

    http://www.open-open.com/lib/view/open1451794925808.html

  5. JS中offsetTop、clientTop、scrollTop、offsetTop各属性介绍(转载)

    这里是JavaScript中制作滚动代码的常用属性 页可见区域宽: document.body.clientWidth;网页可见区域高: document.body.clientHeight;网页可见 ...

  6. js iframe跨域访问

    1.什么是跨域? 2.前台解决跨域几种方法 2.1 动态创建script 2.2 使用document.domain 2.3使用HTML5新属性postMessage 2.4 利用iframe和loc ...

  7. 1.Perl 多线程:Threads

    详情可查看: perldoc threads 调用线程的方法: $thr = threads->create(FUNCTION, ARGS) #This will create a new th ...

  8. 使用HttpClient工具类测试Http接口

    一.httpClient模拟客户端 import java.util.ArrayList;import java.util.Iterator;import java.util.List;import ...

  9. MapReduce,DataJoin,链接多数据源

    主要介绍用DataJoin类来链接多数据源,先看一下例子,假设二个数据源customs和orders customer ID       Name      PhomeNumber 1         ...

  10. unity Mesh(网格)的使用

    创建两个三角形合成的矩形网格: GameObject obj= new GameObject(); MeshRenderer meshRenderer=obj.AddComponent<Mesh ...