Yoapp 小记 - 简单方便的扩展们

SppedUp

我们团队每隔两周会有一次 SppedUp ,两天时间开发一款全新的应用。从前天凌晨到昨天深夜整整48小时,第一次 SpeedUp 理论上应该已经结束,但是事实上并没有想象中那么理想,一直拖到今天还在继续,而且还存在诸如『登录时键盘遮挡输入框』这种低级错误。今天花了一天时间完善了细节,并且整理一下代码,整理一些 Swift 中的代码优化细节。

UIStoryboardSegue

作为一名纯粹的 IB 党,少不了和 UIStoryboardSegue 打交道。主要的使用场景是以下两个方法:

  • performSegueWithIdentifier(_:sender:)
  • prepareForSegue(_:sender:)

一开始我们是这么写的:

performSegueWithIdentifier("show_detail", sender: nil)

后来发觉这种 hard code 并不科学,于是用 struct 封装了一下定义成常量,这样合理多了:

struct SegueIdentifier {
static let showDetail = "show_detail"
}
performSegueWithIdentifier(SegueIdentifier.showDetail, sender: dataSource[indexPath.row])

再后来感觉 prepareForSegue 里不能 switch 不开心啊!于是改成用 enum 封装:

enum SegueIdentifier: String {
case Detail = "show_detail"
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard
let identifier = segue.identifier,
let segueIdentifier = SegueIdentifier(rawValue: identifier) else {
fatalError("INVALID SEGUE IDENTIFIER \(segue.identifier)")
}

switch segueIdentifier {
case .Detail:
// DO SOMETHING
}
}

但是在调用 performSegue 的时候还是有点难受,不能直接传 enum 进去,必须要 rawValue 才行:

performSegueWithIdentifier(SegueIdentifier.Detail.rawValue, sender: nil)

后来昨天看 Swift in Practice 的时候发现,其实可以利用 protocol extension 把通用的部分封装起来:

protocol SegueHandlerType {
typealias SegueIdentifier: RawRepresentable
}

extension SegueHandlerType where Self: UIViewController, SegueIdentifier.RawValue == String {
func performSegueWithIdentifier(identifier: SegueIdentifier, sender: AnyObject?) {
performSegueWithIdentifier(identifier.rawValue, sender: sender)
}

func segueIdentifierForSegue(segue: UIStoryboardSegue) -> SegueIdentifier {
guard
let identifier = segue.identifier,
let segueIdentifier = SegueIdentifier(rawValue: identifier) else {
fatalError("INVALID SEGUE IDENTIFIER \(segue.identifier)")
}
return segueIdentifier
}
}

这样只要 UIViewController 实现 SegueHandlerType 协议便可获得封装好的方法,比如更好用的 performSegueWithIdentifier

performSegueWithIdentifier(.LoginViewController, sender: nil)

而且 prepareForSegue 可以直接通过 switch segueIdentifierForSegue(segue) 对不同 segue 做处理:

extension RankingViewController: SegueHandlerType {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segueIdentifierForSegue(segue) {
case .Detail:
guard
let controller = segue.destinationViewController as? DetailViewController,
let cell = sender as? RankingCell,
let app = cell.appDetail else {
return
}
controller.appDetail = app
}
}
}

dequeueReusableCellWithIdentifier

在写 UITableView 的时候总会有一段很难记但是又不得不记的代码:

let cell = tableView.dequeueReusableCellWithIdentifier("XxxxxCell") as! XxxxxCell

注意这里又出现了 "XxxxxCell" 这样的 hard code ,我们可以用 Class 的名称作为它的 identifier ,这样只要能获取到 className 就行了。在 Swift 里可以这样获取 className

extension NSObject {
static var className: String {
get {
return self.description().componentsSeparatedByString(".").last!
}
}
}

于是乎我们的代码变成了这样:

let cell = tableView.dequeueReusableCellWithIdentifier(XxxxxCell.className) as! XxxxxCell

接下来我们可以给 UITableView 添加个扩展,封装一下上面的代码:

extension UITableView {
func dequeueCell<T: UITableViewCell>(cell: T.Type) -> T {
return dequeueReusableCellWithIdentifier(T.className) as! T
}
}

这样就可以这样生成 cell 了:

let cell = tableView.dequeueCell(RankingCell)

AVObject

由于在使用 LeanCloud 做后台服务,所以不可避免要和 AVObject 打交道,AVObject 基本和 NSDictionary 接口一致,所以做了如下封装:

import Foundation
import AVOSCloud

extension AVObject {
func stringForKey(key: String) -> String? {
return self.objectForKey(key) as? String
}
func doubleForKey(key: String) -> Double? {
let o = self.objectForKey(key)
if let r = o as? Double {
return r
} else if let r = o as? String {
return Double(r)
}
return nil
}
...
}

setToRouded

将图片设为圆角是一个比较常见的操作,可以通过 extension 简单封装一下:

extension UIView {
func setToRounded(radius: CGFloat? = nil) {
let r = radius ?? min(self.frame.size.width, self.frame.size.height) / 2
self.layer.cornerRadius = r
self.clipsToBounds = true
}
}

如果指定半径就设置圆角半径,如果没有值则取高和宽的最小值,实现『半圆』效果。

QUESTION

这里有个问题没太理解。请看下面两段代码:

import UIKit

extension UITableView {
static func dequeueCell1<T: UITableViewCell>(name: String, cell: T.Type) {
print(cell)
}

static func dequeueCell2<T: UITableViewCell>(cell: T.Type) {
print(cell)
}
}

唯一的区别就是参数数目不同,但是调用的时候:

UITableView.dequeueCell1("", cell: UITableViewCell.self)
UITableView.dequeueCell2(UITableViewCell)

两个参数的需要 .self 而另一个则不需要。=。=没太搞懂这是哪出,还望赐教!


参考文献: