别人一看到我的 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 136 137 260 SingleNumber I II III

    Leetccode 136 SingleNumber I Given an array of integers, every element appears twice except for one. ...

  2. digitalocean优惠码30美元1G内存VPS免费使用两个月

    著名的云主机服务商CloudWays送福利啦!CloudWays是一家云主机网站服务商,与Digitalocean和亚马逊AWS开展合作,新用户注册验证手机即可赠送价值30美元的免费VPS主机. 申请 ...

  3. C -小晴天老师系列——竖式乘法

    C - 小晴天老师系列——竖式乘法 Time Limit: 4000/2000MS (Java/Others)    Memory Limit: 128000/64000KB (Java/Others ...

  4. MATLAB将变量存储到EXCEL

    代码如下: d = {'Time','Temperature'; 12,98; 13,99; 14,97}; xlswrite('testdata2.xls', d, 1, 'E1') 运行如下:

  5. visible绑定(The "visible" binding)

    对visible进行绑定可以控制元素的显示和隐藏. 示例: <div data-bind="visible: shouldShowMessage"> You will ...

  6. Understanding Neural Networks Through Deep Visualization

    当数据一层一层通过更多的卷积层时,你可以得到的特征图像代表的特征就会更加的复杂. 在网络的最后,你也许可以得到一个抽象的物体.如果你想通过可视化方法在卷积神经网络中看到更多的信息.这里有一个工具方便你 ...

  7. grub引导centos

    下面来主要讲一下在grub下来引导centos: 其步骤如下; a   进入grub的命令模式. b  先熟悉一下grub  的一些命令 grub>help c  熟悉一下cat命令 d  ro ...

  8. jquery删除未来项 jquery on

    $(document).on('click', '.delbtn', function() { if (confirm("确定要删除吗?")) { var adminid=$(th ...

  9. C# 鼠标事件弹框

    if (e.Button == MouseButtons.Right) { if (gridView1.GetFocusedRowCellValue("color").ToStri ...

  10. 用shell获得hadoop中mapreduce任务运行结果的状态

    在近期的工作中,我需要用脚本来运行mapreduce,并且要判断运行的结果,根据结果来做下一步的动作. 开始我想到shell中获得上一条命令运行结果的方法,即判断"$?"的值 if ...