iOS
メタプログラミング
構文解析
Swift
コードジェネレーター

「コードを書く量が多すぎる!1ヶ月に240時間も働いてしまった!半分にしたい!」
というわけでメタプロ入門しました。

利用するライブラリの候補としては、SourceKittenSourcery を検討しましたが、SourceKitten は構文解析用のライブラリで自作クラスをいっぱい作らなければいけなくて大変なので、メタプロ用には Sourcery の方を使いました。

しかし、APIが割と分かりにくいので、利用例を随時追加していきます。

Sourcery
https://github.com/krzysztofzablocki/Sourcery

Sourceryで使うメタプロ言語(stencil)
https://github.com/stencilproject/Stencil

http://stencil.fuller.li/en/latest/

API使用例

入力ファイルの型を全て取得する

{{ types.all }}

for文でクラス取得

{% class type in types.classes %}

for文でプロトコル取得

{% protocol type in protocols %}

変数をfor文で取得する

{%% for variable in type.variables %}

type の部分には、取得したタイプを入れる。

変数名の一文字目を大文字にして、関数生成に使う

{% for variable in type.variables %}
func setup{{ variable.name|upperFirstLetter }}() {
    // do something
}
{% endfor %}

(生成例)

func setupMyView() {
  // do something
}

変数のジェネリクスの中身を取得して、別の変数の生成に使う

元が Binder<String> みたいな場合も、中身の型だけ取れる。

{% for variable in type.variables %}
var {{ variable.name }}: Observable<{{ variable.typeName.generic.typeParameters.first.typeName }}> { 
        return Observable.just("hoge")
    }

(生成例)

var formTitle: Observable<String> {
    return Observable.just("hoge")
}

テストコード生成

internalのメソッドを拾ってきて、 testHoge~() というテスト関数のガワを生成してくれるのでちょっと楽ができます。

{% for class in types.classes %}

//
//  {{ class.name }}Tests.swift
//  TestAppTests
//  created by code-generator.

import XCTest
@testable import TestApp

class {{ class.name }}Tests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }

    {% for method in class.methods %}
    {% if method.accessLevel == "internal" %}
    func test{{ method.name|upperFirstLetter }}() {

    }
    {% endif %}
    {% endfor %}

    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }

}

{% endfor %}