使用 JavaScript 来写 Masonry 代码

Masonry

上篇文章里面我们遗留了一个问题,就是实现的 Promise 不支持链式调用,要解决这个问题有一点点麻烦。

为了更好的理解这个问题,我们在这一篇里面先聊点别的。

Masonry 是一个 iOS 平台上知名的布局 Framework,更确切地说,他是 Auto Layout 的一个 DSL,能够让你写 Auto Layout 的时候更爽,毕竟 Auto Layout 的 API 可以说是又臭又长,而写 VFL 更是不可能的,这辈子都不可能的...

Masonry 最大的特点是什么?是链式调用,他的 Swift 版本 SnapKit 可能会更优雅,但实际上在 Objective-C 上面他也长得比原生的 Auto Layout 好了不知道多少倍。

一个典型的 Masonry 代码长这个样子:

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
  make.top.equalTo(superview.mas_top).with.offset(padding.top); // with is an optional semantic filler
  make.left.equalTo(superview.mas_left).with.offset(padding.left);
  make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
  make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

他可以被简化成:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
  make.edges.equalTo(superview).with.insets(padding);
}];

这两个是官方的示例,这里面有意思的地方在于你可以不断地用 . 符号调用下去,能够把一些代码写的很短。当然,关于 iOS 上链式调用的实现,以及 Masonry 的实现,可以在网上找到很多资源,这里并不是要炒这个冷饭,而是希望能够借助这个例子搞清楚一个事情。

我们知道,链式调用的核心有二:

  • 方法返回一个 Block
  • Block 的返回值是一个对象,通常意义上可以是 self

返回的 Block 相当于是一个函数,函数执行后如果返回了 self 的话,相当于是对 self 进行一些改动后重新获得了 self 的引用,于是链式调用得以进行去下。

有了这个预备知识我们就能来搞清楚如何在 Native 环境实现一个 JavaScriptCore 环境可以使用的链式调用了。

JSBox 的 Layout 语法

JSBox 是可以写 UI 的,你可以写出这样的代码来构建界面:

$ui.render({
  views: [
    {
      type: "button",
      props: {
        title: "Tap Me"
      },
      layout: (make, view) => {
        make.center.equalTo(view.super)
        make.width.equalTo(64)
      },
      events: {
        tapped: () => $ui.toast("Tapped")
      }
    }
  ]
})

这是一个最简单的样例用来构建一个按钮,代码中的 layout 函数就是 JSBox 里面用于布局的语法,他和 Masonry 是完全一模一样的语法。

Auto Layout 是 JSBox 里面推荐的布局方式,当然你也可以使用 Frame Layout。至于为什么选用了 Auto Layout 而不是类似 Flex 的其他方案,大概是因为 JSBox 还是以“导出” iOS 原生特性为主的一个平台。

这里要说的是,这个语法如何实现的问题。

JSExport

在上一篇文章里面我们已经讲过 JSExport 这个东西,简单说他是 Native 对象和 JavaScript 对象的一个桥梁,通过他可以把 Native 对象里面的方法导出到 JavaScript 环境,其实想通了这一点,以及我们上面提到的问题,要实现这个样一个 Layout 的语法极为简单。

我们只需要关注两个部分,MASConstraintMakerMASConstraint

MASConstraintMaker 其实没什么可说的,你只需要建一个 JSExport 然后把 MASConstraintMaker 里面所有的成员都给填进去,然后使用 class_addProtocol 完成导出即可:

```objc
@protocol MASConstraintMakerExport

@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
...

@end

top Created with Sketch.