别人一看到我的 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. openwrt拦截snmp报文

    SNMP使用的协议为UDP,默认端口为161和162. 使用iptables 命令如下: iptables -A INPUT -p udp -m udp --dport 161:162 -j DROP ...

  2. setsockopt、getsockopt详细介绍(转)

    本文只讲解socket编程中的 setsockopt.getsockopt两个函数.关于其它函数的介绍可以参考下面链接:http://blog.sina.com.cn/s/blog_a459dcf50 ...

  3. top.location != self.location

    top.location != self.location 就是说当前窗体的url和父窗体的 url是不是相同 这个是为了防止别的网站嵌入你的网站的内容(比如用iframe嵌入的你的网站的页面)

  4. 计算机安装了IE8一半退出重启时,桌面只显示背景

    记得我在一家公司实习网管的时候,我遇到过一个这样的情况:那时候公司就我一个网管(原来的那个老员工走了才临时要了我),公司有台台式,上面装了公司的ERP还有一系列的软件.因为那个ERP限制了机器,用另外 ...

  5. [译] Block 小测验

    本文来源于 ParseBlog 的其中一篇博文 <Objective-C Blocks Quiz> 如果您觉得我的博客对您有帮助,请通过关注我的新浪微博  MicroCai 支持我,谢谢! ...

  6. python中的矩阵运算

    摘自:http://m.blog.csdn.net/blog/taxueguilai1992/46581861 python的numpy库提供矩阵运算的功能,因此我们在需要矩阵运算的时候,需要导入nu ...

  7. Java--重载与重写

    父类(Parent): public class Parent { public String name = "parent 父类属性"; public void say(){ S ...

  8. C# 调用C++ DLL 的类型转换

    //C#调用C++的DLL搜集整理的所有数据类型转换方式,可能会有重复或者多种方案,自己多测试 //c++:HANDLE(void *) ---- c#:System.IntPtr //c++:Byt ...

  9. 利用xcopy命令实现本地文件复制到远程服务器的方法

    net use \\192.168.1.198\ipc$ Zqf198703 /user:royalpeak xcopy g:\backup\*.* \\192.168.1.198\数据备份 /D / ...

  10. NDK常见问题

    1. 忽略编译警告为错误 APP_CPPFLAGS += -Wno-error=format-security 2. android studio 手动编译 __android_log_print 错 ...