写更优雅的 Swift 框架 - 续

前几天写了一篇 API 从 rx_taprx.tap 的讨论 写更优雅的 Swift 框架 — rx_tap -> rx.tap

写完该文章的第二天上午就顺手把代码都改成了这样的形式。

当然我只是改了很多 UI 配置相关的代码。先来论述一下我改进的步骤,要补充的一点是 Enjoy 这边基本都是代码布局,换句话说视图的样式和布局都需要用代码来写。

# 使用 lazy

我个人是喜欢用 lazy 的方法,所以基本上各种属性都是 lazy 。所以最开始代码大概写成这个样子。

lazy var avatarImageView: UIImageView = {
	  let imageView = UIImageView()
	  imageView.image = UIImage(named: "DianQK")
	  imageView.contentMode = .ScaleAspectFit
	  return imageView
}()
...
view.addSubview(avatarImageView)
avatarImageView.snp_makeConstraints { (make) in
	  make.centerX.equalTo(self.view)
	  make.centerY.equalTo(self.view)
}

就目前而言,有几点麻烦。

  • 自动补全不完整,特别是在写 lazy 时,忘记先加 () 的场景,即使是加了,也没什么卵用。
  • 要写类型,指明具体类型。
  • 每个 imageView 都要写 let imageView = UIImageView() return imageView。
  • 总是需要记得先添加视图 addSubview

当然,我们也可以先全部 add 。比如,view.addSubviews(avatarImageView, detailButton)

终于有一天我忍不了,我再也不想写重复的 let imageView = UIImageView() return imageView 。寻求新的写法。

# Then

Then (opens new window) 是一个很有意思的 Swift 框架,直接看 README 就可以知道为什么 Then 很有意思。

let titleLabel = UILabel().then {
	  $0.textColor = .blackColor()
	  $0.textAlignment = .Center
}

于是我们可以将之前的代码进行一点的改进。

private lazy var avatarImageView = UIImageView()
	  .then { (imageView) in
	      imageView.image = UIImage(named: "DianQK")
	      imageView.contentMode = .ScaleAspectFit
}

一定程度上可以不声明类型,以及自动补全相对完善了。

我将在下一小节解释为什么只是一定程度上可以不声明类型。

此时的代码还有几点比较麻烦,代码复用问题,每次都需要写闭包。

我试着改了一下,大概可以写成下面的样子。

lazy var avatarImageView = UIImageView()
	  .then(Config.scaleAspectFit)
	  .then(Config.image(UIImage(named: "DianQK")))

这样写就很有意思了,不需要每次都去写闭包了。同时如果这个 config 是个常用的 config ,那就可以很好的提高代码复用率。比如有一个 Config.h1 ,设置为 h1 字体。

当然这样的代码我个人感觉,这很优雅了。直到 rx.tap 的出现。

这种命名空间形式的代码很有意思,这表明 taprx 的一些属性/方法。那么上面对于 UI 配置是否也能写成类似的样子?可以。

# Namespace

根据之前的 rx.tap 实现方式,我写了个 Config

lazy var avatarImageView = UIImageView()
	  .config
	  .fill
	  .image(UIImage(named: "DianQK"))
	  .view

虽然行数稍微多了一些,但是这段代码相比 then 更有意思些。then 只是表达了然后,然后,然后。

而这里的代码含义为,创建 UIImageView ,进入 Config 阶段,设置为 fill 样式,设置图片为 DianQK ,进入/回到 View 阶段。

在合理的作用域调用合理的方法/属性

为此我们还可以写更多类似的代码, Layout

avatarImageView
	  .layout
	  .adhereTo(self.view)
	  .make { (make) in
	      make.centerX.equalTo(self.view)
	      make.centerY.equalTo(self.view)
	  }

当然,为了更好的约束代码(先添加视图,后设置布局),我移除了 Layoutmake 的方法。

比较疯狂的写法可以这样。

let avatarImageView = UIImageView()
	  .config
	  .fill
	  .image(UIImage(named: "DianQK"))
	  .layout
	  .adhereTo(view)
	  .make { (make) in
	      make.centerX.equalTo(self.view)
	      make.centerY.equalTo(self.view)
	  }
	  .view

具体实现见 Demo (opens new window)

这样写稍微错在一点小问题,不是很容易理解代码逻辑,除非很清楚 configlayout 表示进入了一个新的层级配置。我整理一下代码如下。

let avatarImageView = UIImageView()
	  .config
	      .fill
	      .image(UIImage(named: "DianQK"))
	  .layout
	      .adhereTo(view)
	      .make { (make) in
	          make.centerX.equalTo(self.view)
	          make.centerY.equalTo(self.view)
	      }
	  .view

这样可能看起来好一些<s>然而 Xcode 并不知道我想写成这样</s> 。具体怎么整理这个问题,看个人喜好了,也可以 .Config .Layout ,当然如果是 .CONFIG.LAYOUT 就算了。

比如项目中完整的代码。

lazy var kTableView = UITableView(frame: CGRect.zero, style: .Grouped)
	  .config
	  .register([PayProductInfoTableViewCell.self, PayPriceInfoTableViewCell.self, PayAddressTableViewCell.self])
	  .centerSeparatorInset
	  .custom { tableView in
	      // ...
	  }
	  .view

当然,如果你好奇 register([PayProductInfoTableViewCell.self, PayPriceInfoTableViewCell.self, PayAddressTableViewCell.self]) 这段代码,我想留给你来思考会比较有意思。

# Confused

前面提到一定程度上可以不声明类型,那么什么情况可以?什么情况不可以?用一句话总结,使用 self 时,需要声明类型

不预期的 self

写完这段代码,我的脸和这个 self 是一个颜色。这居然是一个 NSObject -> () -> NamespaceViewController这里就不讨论为什么是这个类型了。

解决方案也很简单,指明类型即可。

指明类型

# 彩蛋

当我提出上面这种写法时,我收到了梁杰的一条回复。

哈哈哈哈哈 哈哈

可以,这很前端。

$(document).ready(function() {
	$('html').find('label').html('测试你大爷').next().find('img')
	.attr('src', 'http://static.zybuluo.com/numbbbbb/letp7i9nq115kbfiswhhfplg/2.png')
	.width('100px').height('100px')
	.next().css({color: 'red'}).html('这里是正文,点我试试看').on('click', function(){alert('别摸我!')})
	.closest('div').css({border: '1px solid red'})
});

代码可以见https://jsfiddle.net/Lsg7brb9/ (opens new window)

当然,稍微有些不同的是,本文的代码有加入 config layout表明层级代码进行约束。

需要注意的是这样的写法虽然爽,但不要到处都用。毕竟这样的代码写多了容易让人把所有的代码都连起来(这是可以的),这时候 100 多个 . 连起来怕也是一种灾难。

此外,Enjoy 欢迎喜欢 Swift 的同学加入 iOS 组,我们在招人哦,如果你对尝试各种因缺斯坦的代码有兴趣,我很欢迎。差点忘了,请发简历到 songxutao@ricebook.com 。