19になりました。
こんにちは。
例年のごとく今年もやってきた誕生日ですが、今年は気がつくと謎のアドベントカレンダーにjoinしてました。
誕生日アドベントカレンダー、次回は家庭の医学くんです。(ノンアポ)
ということで当日からしばらく経ってしまいましたが、欲しいものリストからいただいたものリストを公開していこうと思います。
そういえば実は昨日19歳になりました。
— kateinoigakukun (@kateinoigakukun) 2019年9月13日
抱負は限界突破です。頑張ります。https://t.co/JBiMkdBzER
リストには図々しくも、普通に読みたかった技術書たちをドサっと入れました。
@kishikawakatsumiさんから
- 作者: W. Richard Stevens,Stephen A. Rago,大木敦雄
- 出版社/メーカー: 翔泳社
- 発売日: 2014/04/22
- メディア: 大型本
- この商品を含むブログ (2件) を見る
どデカいダンボールで届いたので、むむ、これは本では無いな、とエスパーしたのですが、本という名の鈍器が入っていました。
Amazonの商品画像は正面の図しか教えてくれないので、まさかここまでとは思わず…
ドンッ
先人が築いてきた土台の厚みですね。
最近は興味の方向が段々と下に向いてきたので、楽しく読めそうです。
- 作者: 伊坂幸太郎
- 出版社/メーカー: 新潮社
- 発売日: 2003/11/28
- メディア: 文庫
- 購入: 21人 クリック: 154回
- この商品を含むブログ (832件) を見る
もう一つ、伊坂幸太郎の作品をいただきました。友人に勧められて以来この作家さんの作品をいくつか読んでいるのですが、珍しくハマった作家さんです。
ありがとうございます!
@bannzaiさんから
bannzaiさんのnは2つなんですね。
きつねさんでもわかるLLVM ~コンパイラを自作するためのガイドブック~
- 作者: 柏木餅子,風薬
- 出版社/メーカー: インプレス
- 発売日: 2013/06/21
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (11件) を見る
わいわいswifrcのイベントページに載っている、もはや検定教科書と言っても過言では無い本です。
じつはまだ読んだことなくて、雰囲気でLLVM触ってるのでありがたいです!
果たして僕はきつねさんになれるのでしょうか。
乞うご期待です。
@noppefoxwolfさんから
- 出版社/メーカー: 伊藤園
- 発売日: 2012/03/05
- メディア: 食品&飲料
- 購入: 11人 クリック: 29回
- この商品を含むブログ (9件) を見る
野菜ジュースをいただきました!
大学の食堂の混み具合が異常すぎて、入学後2週間で昼のメニューを野菜ジュースとラムネにシフトした僕にとっては非常にありがたいです。
メッセージに
異世界転生の仕方を教えてください
とあったのですが、たまたまそれを見た母親に怪訝な目をされました!今日も我が家は平和です!
9/17 追記
@kishikawakatsumiさんから(その2)
リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY)
- 作者: 坂井弘亮
- 出版社/メーカー: CQ出版
- 発売日: 2010/08/01
- メディア: 単行本
- 購入: 6人 クリック: 55回
- この商品を含むブログ (16件) を見る
なんと第2弾があったようで、リンカの本をいただきました。
今年の夏の前半でLinkers & Loadersを読んで「リンカ完全に理解した」状態になったのですが、最近また「何も分からん」のフェーズに入ったので最高のタイミングでした!
パラパラめくってみたところ、Linkers & Loadersとはまた毛色が違うようなので、まだまだ学べることがありそうです。ありがとうございます!!
@d_dateさんから
- 作者: 杉原厚吉
- 出版社/メーカー: 共立出版
- 発売日: 2001/12/01
- メディア: 単行本
- 購入: 3人 クリック: 15回
- この商品を含むブログ (3件) を見る
大学の図書館で読めそうじゃんこれ というものにしました
というメッセージと共に、まさに図書館にありそうな本をいただきました!
絶対に図書館にあると思いますが、手元にある安心感は何にも代え難いです!
実はアルゴリズムの類の本は全く読んだことが無く、必要に応じてwikipediaで理解していただけなので、脳の引き出しを拡張するために勉強したいと思います。
ありがとうございます!
@takasekさんから
- 作者: Sam Newman,佐藤直生,木下哲也
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/02/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
これからも無双してください
最近は暇さえあればマイクロサービスのアーキテクチャについて思いを馳せているので、これを読んで無双できるようなアーキテクチャへの道を見出したいと思います!
あと🐝が思ったよりリアルでした!!
ありがとうございます!
@giginetさんから
エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)
- 作者: エリック・エヴァンス,今関剛,和智右桂,牧野祐子
- 出版社/メーカー: 翔泳社
- 発売日: 2011/04/09
- メディア: 大型本
- 購入: 19人 クリック: 1,360回
- この商品を含むブログ (131件) を見る
完全にたかりの図ですが、本当にありがたいことにDDD本を頂きました。
修行の身なのでいつでもウェルカムですよ(非常に図々しい)
— kateinoigakukun (@kateinoigakukun) 2019年9月16日
正しくドメインを切り出せるようになるのが直近の目標なので、無限に吸収できることが詰まっている予感です。
これでOSSを全部いい感じにしてくれ〜
という雑で壮大なタスクがメッセージとして降ってきたので、全部いい感じにしたいとおもいます。
僕とgiginetさんがメインでメンテナをしてるIBLinterをよろしくお願いします GitHub - IBDecodable/IBLinter: A linter tool for Interface Builder
@crcrpar
- 出版社/メーカー: 森永製菓
- 発売日: 2018/03/20
- メディア: 食品&飲料
- この商品を含むブログ (2件) を見る
昼ラムネ買っておきました。
— M.K. (@crcrpar) 2019年9月16日
僕の頭がまともだったら明日届くはずですが何かの間違いで僕の家に届くかもしれない
僕の生活を支える技術の一つである「大粒ラムネ」をデプロイしていただきました! 野菜ジュースとラムネの最強タッグでしばらくお昼ご飯難民にならなくて済みそうです!
ありがとうございます〜!
多分全部読むのに1年くらいかかりそうですが、無限の伸び代を感じることができて非常にワクワクしてます。
やるぞやるぞ〜
みなさんありがとうございました!
この恩はいろんな形で還元していきたいと思います。
これからも仲良くしてください〜〜
Inside SwiftUI (About @State)
SwiftUI was announced at this WWDC and we've tried it for a few days. This WWDC was the moment everything changed for me.
But SwiftUI is still a private source software as well as UIKit and we need to develop applications while speculating the behavior of it.
There is some features which I felt is a mystery in SwiftUI. I investigated them and internal implementation of SwiftUI.
(This post is just a my prediction)
@State
@State var value: Int
You may write this many time while doing tutorials of SwiftUI. Do you understand how State
works?
This feature uses
Property Delegates introduced from Swift5.1 and it allows to delegate the implementation of getter and setter to another instance (In this case "another instance" means State
).
State
is a struct defined in SwiftUI using @propertyDelegate
. If State
is changed, View
will be re-rendered.
First, let's implement State
by myself!
@propertyDelegate struct State<Value> { var storage: Value var value: Value { get { storage } set { storage = newValue renderView() } } init(initialValue value: Value) { self.storage = value } func renderView() { // ??? } } struct ContentView: View { @State var text: String }
It's almost easy to implement but I couldn't figure out how re-render View
. SwiftUI seems not re-render the all Views but some particular views whose state is modified. So I need to link ContentView
and text: State<String>
to update the view.
Internal structure of State
Let's dump the state instance to investigate internal structure of State
by using dump
.
struct ContentView: View { var text = State<String>(initialValue: "Hello") init() { print("Init:") dump(text) } var body: some View { print("Body:") dump(text) return Text("Hello, world") } }
I dumped text
in init
and body
but their outputs are different.
Init: ▿ SwiftUI.State<Swift.String> - _value: "Hello" - _location: nil Body: ▿ SwiftUI.State<Swift.String> - _value: "Hello" ▿ _location: Optional(SwiftUI.StoredLocation<Swift.String>) ▿ some: SwiftUI.StoredLocation<Swift.String> #0 - super: SwiftUI.AnyLocation<Swift.String> - super: SwiftUI.AnyLocationBase ▿ viewGraph: Optional(SwiftUI.ViewGraph) ...
This result shows ViewGraph
appears after init
but before body
. It seems there is a way to inject ViewGraph
into State
outside of View.
Since ViewGraph
is an internal type of SwiftUI, I can only speculate it but it seems ViewGraph
manages tree structure of View. If this ViewGraph
can render View
selectively, it's easy to imitate the re-rendering system.
Then, let's fill the body of renderView
.
@propertyDelegate struct State<Value> { var storage: Value + var viewGraph: ViewGraph? var value: Value { get { storage } set { storage = newValue renderView() } } init(initialValue value: Value) { self.storage = value } + func setViewGraph(_ viewGraph: ViewGraph) { + self.viewGraph = viewGraph + } func renderView() { + viewGraph.render() } }
It became clear that how re-render view but it's still uncertain that how inject the ViewGraph
into ContentView
and make the relationship between State
and View
.
@State
filed belongs to View but there is no way to access text: State<String>
through View
protocol because the field name is not bound by the protocol. For example, it can be easy to inject if State
field is bound by the protocol as follows code.
But actually, fields can be named freely.
protocol ViewGraphInjectable { var state: State<Value> { get } } struct ContentView: View, ViewGraphInjectable { @State var state: String }
Reflection API
The only way to get fields without protocol I first thought is Mirror
. It's very simple that getting fields of View using Mirror
and if there is State
, call setViewGraph
.
But does SwiftUI really use Mirror
? If Mirror
is used, CustomReflectable.customMirror
should be called and print "Mirror is used!".
struct ContentView: View, CustomReflectable { @State var text: String var customMirror: Mirror { print("Mirror is used!") return Mirror(reflecting: self) } }
But customMirror
is not called. For checking, I tried to set breakpoint at swift_reflectionMirror_count
which is runtime function used by Mirror
but it also doesn't break.
This result indicates that SwiftUI get fields without using Mirror
.
Reflection without Mirror
There is still a way to get fields without using Mirror
. It's using metadata.
If you don't know metadata, you can learn it by my try! Swift presentation.
Metadata has Field Descriptor which contains accessors for fields of the type. It's possible to get fields by using it.
My various experiments result AttributeGraph.framework
uses metadata internally. AttributeGraph.framework
is a private framework that SwiftUI use internally for constructing ViewGraph
.
You can see it by the symbols of the framework.
$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph
There is AG::swift::metadata_visitor::visit_field
in the list of symbols. i didn't analysis the whole of assembly code but the name implies that AttributeGraph
use visitor pattern to parse metadata. If you set break point, it surely hit.
Why not use Mirror
So why is metadata used instead of Mirror
? I think it's for performance.
Mirror.children
is represented as [(label: String, value: Any)]
to hold any type of value. But Any
wraps the actual value and when you use it, Any
is unwrapped every time. SwiftUI uses View
many times and the overheads can be critical problem.
On the other hand, Using raw pointer through Field Descriptor
doesn't affect performance seriously.
Flow to update View
- Find
State
ofView
usingField Descriptor
- Inject
ViewGraph
intoState
- Render
View.body
State
is changedState
notify theViewGraph
to update view- Re-render
View.body
Only Apple knows the actual implementation. But it's certain that AttributeGraph.framework
has its own reflection system.
I said "I'm looking forward to your great libraries using metadata" in try! Swift but I had never thought Apple do it, I think Apple started to use metadata because ABI stability was built since Swift5. In fact, ABI stability brings us great benefits!
I'll write an article about DynamicViewProperty
by next week.
Inside SwiftUI @State編
WWDCでSwiftUIが発表されてから数日が経ちました。一気に世界が変わった気がしますね。 ただ、UIKitと同様にSwiftUIはオープンソースでは無いため、我々開発者は依然挙動をエスパーしながら開発する必要があります。
その中でも、SwiftUIのチュートリアルを試している中で不思議な仕組みがいくつかあったので、僕が調べたSwiftUIの内部構造について書き留めておきます。
(あくまで考察なので間違っていても悪しからず)
追記(2022-09-15)
未だにアクセスがあるので追記。
TokamakというSwiftWasmでSwiftUI互換なコードを書くためのUIフレームワークがあり、
ここで考察した内容とほぼ同じようなテクニックを使ってState
を実装しています。
具体的な実装が気になる方は読んでみてください。
目次
@State
編(今ココ)DynamicViewProperty
編(来週くらいには書きます)- 差分更新編(調査中)
@State
@State var value: Int
チュートリアルをこなした方であれば何度も書いたことでしょう。
Swift5.1からのProperty Delegatesを使った記法で、プロパティの値のgetterとsetterを別の型(ここではState
)に委譲できます。
State
というのはSwiftUIに定義された@propertyDelegate
なstructです。変更されるとStateが定義されているViewが再レンダリングされる、という振る舞いをします。
ではこの振る舞いからStateの実装を予想してみます。
@propertyDelegate struct State<Value> { var storage: Value var value: Value { get { storage } set { storage = newValue renderView() } } init(initialValue value: Value) { self.storage = value } func renderView() { // ??? } } struct ContentView: View { @State var text: String }
(本当はvalueのsetterがnonmutatingなのでbox化されてるはずですが説明のため省略してます。)
簡単に実装できましたがViewを再レンダリングする部分だけは想像できません。keyWindow
配下のViewを全て再レンダリングしているのか?、と一瞬思いましたが、Stateが変更されてもルートのViewから全て再レンダリングされる訳では無さそうです。
では、どうやってtext: State<String>
とContentView
を紐付けてContentView
だけ再レンダリングしているのでしょうか。
Stateの内部構造
とりあえずState
のpublicなフィールドにはそれっぽい物は無いので、内部フィールドをdump
で調べてみます。
struct ContentView: View { var text = State<String>(initialValue: "Hello") init() { print("Init:") dump(text) } var body: some View { print("Body:") dump(text) return Text("Hello, world") } }
(State
がdelegateValue
を使っており$text
がState
を返さないのでProperty Delegateを使わない記法にあえてしています。)
init
とbody
でそれぞれtext: State<String>
をdumpしましたが、実は違った結果が出力されます。
Init: ▿ SwiftUI.State<Swift.String> - _value: "Hello" - _location: nil Body: ▿ SwiftUI.State<Swift.String> - _value: "Hello" ▿ _location: Optional(SwiftUI.StoredLocation<Swift.String>) ▿ some: SwiftUI.StoredLocation<Swift.String> #0 - super: SwiftUI.AnyLocation<Swift.String> - super: SwiftUI.AnyLocationBase ▿ viewGraph: Optional(SwiftUI.ViewGraph) ...
ViewGraphという怪しいオブジェクトが見えるようになりました。initとbodyの間に何かしらの方法でStateにViewGraphが注入されています。
Internalな型なので名前から推測するしかありませんが、どうやらViewGraphはViewのツリー構造を管理するオブジェクトのようです。このViewGraphがContentViewのみを選択的にレンダリングできる、と仮定すると再レンダリングの仕組みは説明できそうです。
では、StateがViewGraphを持つことを考慮して最初のStateの実装の???
を埋めてみます。
@propertyDelegate struct State<Value> { var storage: Value + var viewGraph: ViewGraph? var value: Value { get { storage } set { storage = newValue renderView() } } init(initialValue value: Value) { self.storage = value } + func setViewGraph(_ viewGraph: ViewGraph) { + self.viewGraph = viewGraph + } func renderView() { + viewGraph.render() } }
なるほど、Viewを再レンダリングする方法は分かりました。しかし、未だどうやってContentView
とState
を紐付けてContentView
のViewGraph
を注入するのか、は謎のままです。
先ほどの実験からinit
とbody
の間に呼ばれることは分かっていますが、どこから呼ばれるかは不明です。
また、@State
フィールドはView
に対して生えていますが、protocolでフィールド名が縛られている訳では無いのでtext: State<String>
にアクセスする方法がありません。
例えば以下のようにprotocolで縛られていれば注入できると思いますが、実際フィールド名は自由に付けられるので他の方法で実現されているようです。
protocol ViewGraphInjectable { var state: State<Value> { get } } struct ContentView: View, ViewGraphInjectable { @State var state: String }
Reflection API
protocolを使わずに任意の構造体から動的に値を取り出す、となると真っ先に思いつくのがMirror
です。View
をMirror
にかけて、children
を取り出して、その中にState
型があればsetViewGraph
を呼ぶ、という比較的単純な方法です。
それでは本当にMirror
を使っているか実験してみましょう。CustomReflectable
が実装されている場合、Mirror
にかけるとcustomMirror
が呼ばれるはずなので、"Mirror is used!"
が出力されるはずです。
struct ContentView: View, CustomReflectable { @State var text: String var customMirror: Mirror { print("Mirror is used!") return Mirror(reflecting: self) } }
しかし、実際はcustomMirror
は呼ばれません。念のため、Mirror
が使っているランタイムAPI、swift_reflectionMirror_count
にブレークポイントを貼ってみましたがヒットしませんでした。
つまり、SwiftUIは内部でMirror
を使わずにView
のフィールドを取得している訳です。
Mirror
を使わないReflection
Mirror
を使わずともフィールドを取得する方法はまだあります。そうですメタデータを利用する方法です。メタデータについては僕のtry! Swiftの発表を見てもらえると雰囲気が掴めると思います。
メタデータにはField Descriptor
という、その型のフィールドのアクセサ群が格納されています。それを利用すればフィールドの一覧を取得できるのでMirror
の代わりに使っている可能性は十分にあります。
色々と試行錯誤した結果1、SwiftUIが内部でViewGraph
を組み立てるのに使っているAttributeGraph.framework
というプライベートフレームワークが内部でメタデータを使っている事が分かりました。
では早速AttributeGraph.framework
のシンボルをnm
コマンドで見てみましょう。
$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph
AG::swift::metadata_visitor::visit_field
というシンボルが見えると思います。アセンブリを全て読んだ訳ではありませんが、名前から察するにメタデータからフィールドをVisitorパターンで回していそうです。ブレークポイントを貼ってみると実際に動いている様子が確認できます。
なぜMirror
を使わないのか
ではなぜMirror
でもできることをメタデータで実装しているのでしょうか。完全に私の考察ですが、パフォーマンスが主な理由だと思っています。
Mirror
はchildren
を[(label: String, value: Any)]
で表現します。任意の型を表現するのにAny
を使っていますが、Any
は値をラップしているため、実際の値を使うには毎回取り出す必要があります。View
が多くなればなるほどこのオーバーヘッドは無視できなくなってくるため、フィールドをポインタで直接操作できるField Descriptor
を使っている、というのが予想です。
StateがViewに更新されるまで
ここまでの実験でState
が変更された際にどうやってView
が再レンダリングされるか予想できたのでまとめます。
View
に対してField Descriptor
でState
を探してViewGraph
を注入するView.body
を評価してレンダリングする。State
が変更されるState
がViewGraph
に変更を通知するView.body
が再評価されView
が更新される。
実際の実装はAppleのみぞ知るところですが、これ以上のアイディアは思いつきませんでした。ただ、AttributeGraph.framework
が独自にリフレクションの仕組みを使っているのは確実です。
try! Swiftの発表で「メタデータを使ったライブラリをみんな作ってみてね」と言いましたが、まさかAppleがABI安定化を期に率先して使ってくるとは思いませんでした。今年のWWDCは驚くことばかりですね。
次はDynamicViewPropertyについて書きます。
try! Swift 2019
毎年期末試験の時期と重なって参加を断念していたので初参加でした。
参加できるだけでも嬉しかったのですが、光栄なことにスピーカーとして登壇することもできました。
大きなカンファレンスでの登壇は昨年のiOSDCが最初で今回はそれ以来です。ただ、今回は規模がさらに大きいことや、英語での登壇だったこともあり余裕は全くなかったです。
どのくらい余裕がなかったかというと、2日目、登壇する直前の午前中のトークをほぼ覚えていないくらいです。絶対にキャッチアップするので許してください。
登壇
Swift Type Metadata (en) - kateinoigakukunのブログ
Swift Type Metadata (ja) - kateinoigakukunのブログ
Swiftのメタデータについて話しました。内容的には年末に書いたアドカレの発展版です。 Swift Type metadata - Qiita
わいわいswiftcで@omochimetaruが楽しそうに話していたのがきっかけで調べ始めたのですが、ここら辺の技術は掘れば掘るほど面白かったです。
ただ、発表は20分しか無い上に900人にウケる内容にする必要があるので、風呂敷を広げすぎないように構成するのが大変でした。シュッと飛ばした部分が結構あるので、今回の発表で興味を持った方はこの辺を見てもらえると色々と繋がってきて楽しいと思います。
- Swift Type metadata - Qiita
- IRGenから見たType Metadata - Speaker Deck
- Swiftのジェネリクスはどうやって動いているのかコンパイラのソースから探る - Speaker Deck
- ジェネリック型のメタタイプ - Speaker Deck
- SwiftランタイムライブラリとValue Witness Table - Speaker Deck
- コンパイラから紐解くSwift method dispatch - Speaker Deck
- Box式Type Erasureのmethod dispatch - Speaker Deck
後半で紹介した拙作のOSSはメルカリのインターンの中で作ったものです。基本的なアイディアはCodableのデコードの仕組みに乗っかって任意の構造体を組み立てる、という簡単なものです。
最初はenumをサポートしておらず、各自書いてもらう方式をとっていたのですが、今回のスピーカーで僕のメンターでもある@tarunonから、どうにかしてくれ〜と御達しがあったので、なんとか作りました。
初めてコンパイラの勉強が業務の役に立ちました。
英語で話したこともあってか、Ask the speakerの時間に何人か海外の方もいらしてくださり、今回は言及出来なかったstaticなメタプログラミングとの比較や具体的なユースケースの実装相談など、楽しく議論することができました。何より自分の話した内容が伝わっていることが実感できて嬉しかったです。
セッション
個人的に面白かったトークは 「protocol/extensionにジェネリクスを入れたい」です。型パラを静的に分解して詰め直す技がテクニカルでした。カンファレンス中にお話する機会が何度かあったのですが、Swiftに限らない言語の型システムの深い理解から来る興味深い話が聞けました。
ワークショップ
ワークショップはOpen Source Swiftに参加しました。以前自分が報告した SR-7467 に取り組みました。調査を始めた時はTypeCheckerのキャッシュ機構のバグだと思っていたのですが実際の問題は別のところにあり、相当時間を使ってしまいました。最終的にバグの原因は完全にわかったのですが、色々と考慮漏れがあり未だテストが通ってません。
今後
やりたいこと、勉強したいことは増えるばかりで時間がいくらあっても足りません。正しく取捨選択していくぞ
Swift Type Metadata (en)
try! Swift 2019
I'm kateinoigakukun, working at Mercari as an intern. Today, I'll talk about Metadata which is one of the most important things to understand how Swift works.
Swift is well-known for being a statically-typed language, but it actually has many dynamic things on runtime.
let typeName = String(describing: Int.self)
I'm sure all of you have looked at StackOverflow and written code like this to get a type name.
extension UITableView { func register<Cell>(nibWithCellClass: Cell.Type) where Cell: UITableViewCell { let typeName = String(describing: Cell.self) let nib = UINib(nibName: typeName, bundle: Bundle.main) register(nib, forCellReuseIdentifier: typeName) } } tableView.register(nibWithCellClass: TweetCell.self)
For example, when you call register method for UITableViewCell
, you use this to match the xib name and type name. It's useful extension. But have you ever thought about how this code works on runtime?
This is your first step towards thinking about memory representation in Swift. Let's dig into the world of metadata!
Agenda
- What is type metadata?
- Explore
String(describing: Int.self)
- How to use metadata in Swift
- Use cases in OSS
First, I'll explain "What is type metadata". Swift type metadata is not something we are familiar with. But we usually get the benefits of it, because Core features of Swift use it for dynamic behavior. I'll explain the example of the String
initializer and talk about how type metadata is used inside of Swift. Then I'll introduce how to use metadata in Swift and some examples of hacking Swift.
What is type metadata?
- Type information in Swift runtime
- Used in Swift internal dynamic behavior
- Metatype is pointer to metadata
let metatype: Int.Type = Int.self
Let's start. Type metadata is Swift's internal information about types like instance size, number of cases of enum, and so on. This information is stored statically in binary or generated dynamically in runtime. Metatype is represented as type name plus self
keyword and the actual value is a pointer to the metadata.
extension String { public init<Subject: CustomStringConvertible>(describing instance: Subject) { ... } public init<Subject>(describing instance: Subject) { ... } } let typeName = String(describing: Int.self) // "Int"
Int.self
is a metatype object and it is passed to the String
initializer. This initializer accepts any type of value, and returns the description
property if the type of the value conforms to CustomStringConvertible
and returns the type name if a metatype is passed in.
extension Int.Type: CustomStringConvertible { // 🚫 Cannot extend a metatype 'Int.Type' var description: String { return "Int" } }
It can not be true that metatype implements the description
property of CustomStringConvertible
because there is no way to extend metatype. And it seems impossible to implement with the pure Swift API because Swift doesn’t have a runtime API like Objective-C. So there should be some magic.
SwiftCore
- Swift standard library
- Fundamental types and interfaces
SwiftRuntime
- Swift runtime library
- Dynamic behavior
The initializer is implemented in SwiftCore
and the magic is implemented in SwiftRuntime
. SwiftCore
is Swift standard library written in Swift and contains fundamental data types like String
and Int
and protocols. SwiftRuntime
is the Swift runtime library written in C++ and contains runtime behavior. Many dynamic features like dynamic casting
and allocating instances
are implemented in this library.
stdlib/public/core/Mirror.swift
struct String { public init<Subject>(describing instance: Subject) { _print_unlocked(instance, &self) } }
Swift is open source, so we can see this on Github. In this initializer, the print_unlocked
which is internal function is called.
stdlib/public/core/Misc.swift
public func _typeName(_ type: Any.Type, qualified: Bool = true) -> String { let (stringPtr, count) = _getTypeName(type, qualified: qualified) return String._fromUTF8Repairing( UnsafeBufferPointer(start: stringPtr, count: count)).0 } @_silgen_name("swift_getTypeName") public func _getTypeName(_ type: Any.Type, qualified: Bool) -> (UnsafePointer<UInt8>, Int)
Digging into the call stack for the case when type of argument is metatype, the _typeName
function is called,
and calls another function _getTypeName
. Look at the _getTypeName
definition.
First, you can see that the function has @_silgen_name
attribute and doesn't have body code. What’s this? This @_silgen_name
attribute specifies the function name that the declaration will have at link time. In this use case, this definition is used to link to a function in SwiftRuntime
. I'll skip the details of the linked function, but it simply extracts the type name from the metadata.
Then, how is metadata represented in the memory world?
As you can see in the figure, metadata is broken down and represented as - Value witness table which is a group of functions for manipulating instance
- a kind value which represents the kind of type such as class, struct, protocol, etc,
- and NominalTypeDescriptor which records detailed information of the type.
- In the case of a class, VTable is also included,
- and in the case of a generic type, type parameters are embedded dynamically.
So, in the NominalTypeDescriptor there is the type name we are looking for.
We can get the type name from the nominal type descriptor by just advancing the metadata pointer. It seems not difficult to implement, so let's reproduce the String initializer in SwiftRuntime
.
(docs/ABI/TypeMetadata.rst)
struct StructMetadata { let kind: Int let typeDescriptor: UnsafePointer<StructTypeDescriptor> } struct StructTypeDescriptor { let flags: Int32 let parent: Int32 let name: RelativePointer<CChar> }
In the first step, reproduce the memory layout as a struct. Most information about the memory layout is documented but a part of it is already outdated, so we need to read the source code of swift compiler sometimes. To simplify this example, I will only implement for structs.
Then, we need to understand RelativePointer
to reproduce the memory layout.
(include/swift/Basic/RelativePointer.h)
RelativePointer
is not just a pointer. A basic absolute pointer has the address to the referent, but relative pointer has the offset from its own address to the referent's address. It works by just reading the offset and advancing from its own address. Using RelativePointer
instead of an absolute pointer reduces relocation.
func getTypeName<Subject>(of type: Subject.Type) -> String { let metadataPointer = unsafeBitCast( type, to: UnsafePointer<StructTypeMetadata>.self ) let namePointer: UnsafePointer<CChar> = metadataPointer.pointee .typeDescriptor.pointee .name.advancedPointer() return String(cString: namePointer) }
Then, we finished preparing, so let's extract the type name.
First, cast the metatype object into the pointer of metadata. But in Swift's type system, metatype doesn't have subtyping relation with metadata pointer, so use unsafeBitCast
to cast the metatype.
Access the name pointer through the type descriptor and advance the offset to be an absolute CChar
pointer.
Next convert this to a Swift String. Then, the implementation has been completed!
let typeName = getTypeName(of: Int.self) // "Int"
Execute this, You can get the type name. This is the first step of meta programming with metadata!
Use cases inside of Swift
Metadata is used for dynamic behavior in Swift. Many people use Swift without realizing this, but there are many use cases. Where? The most common use case is allocating an instance. And if you call a method through protocol
or class
, the method table stored in metadata is referenced to get the method reference.
In other cases, Mirror
API uses metadata to reflect properties.
In this way, metadata is very useful inside of Swift, but we can abuse it.
Method swizzling
Next, I'll talk about the Black magic that you used in Objective-C. The black magic is Method swizzling. If you understand the metadata, you can get great power.
Method swizzling
class Animal { func bar() { print("bar") } func foo() { print("foo") } } struct ClassMetadata { ... // VTable var barRef: FunctionRef var fooRef: FunctionRef }
First, reproduce the memory layout as getting the type name. Class methods are called via VTable that is a table of pointers to functions. So it should work if we replace the pointers.
let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.self ) let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 } let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 } bar.pointee = foo.pointee let animal = Animal() animal.bar() // foo
Get the metadata from metatype using unsafeBitCast
and get the both pointers of the functions to swizzle.
Then, it's easy to replace them.
This is very simple but this works well.
Like this, using metadata we can achieve what seems impossible.
Use cases
- Zewo/Reflection
- wickwirew/Runtime
- alibaba/HandyJSON
- kateinoigakukun/StubKit
Now I will introduce some use cases of metadata in OSS I found. The top two provide a Swifty interface to access metadata information. They are very useful when using metadta. The third one is a JSON serialization library which enables encoding and decoding JSON without mapping configuration.
alibaba/HandyJSON
struct Item: HandyJSON { var name: String = "" var price: Double? var description: String? } if let item = Item.deserialize(from: jsonString) { // ... }
This feature has been achieved by Codable
with compiler code generation since Swift4, but HandyJSON
was created before Codable
and uses metadata to make a relationship between value and property name without Objective-C API.
Use cases
- Zewo/Reflection
- wickwirew/Runtime
- alibaba/HandyJSON
- kateinoigakukun/StubKit
The last use case is my library StubKit
.
kateinoigakukun/StubKit
import StubKit struct User: Codable { let name: String let age: UInt } let user = try Stub.make(User.self) // User(name: "This is stub string", age: 12345)
This library enables instantiating stubs without any arguments and makes it easy to instantiate struct with many many fields. Most of this feature is implemented with Codable
but some features are implemented using type metadata.
kateinoigakukun/StubKit
Before I introduce the use case of metadata, I'll share with you how this stub function works. First, a basic struct forms a tree structure and you can traverse it using Decoder
protocol.
So if we prepare the stub of a leaf and inject it while traversing, we can instantiate any type of stub without arguments.
func leafStub<T>(of type: T.Type) -> T { guard let stubbable = type as? Stubbable else { return nil } return type.stub } extension Int: Stubbable { var stub: Int { return 12345 } } extension enum: Stubbable { // 🚫 Can't extend var stub: Self { return enumStub() } }
For example, String
, Int
and enum can be leaf types. It's easy to prepare a stub of basic data types, but enum can be defined by users so we need to prepare all stubs of custom enums manually. I know it's very hard so I implemented generating enum instances using metadata.
func enumStub<T>(of type: T.Type) -> T? { if isEnum(type: type) { let rawValue = 0 let rawPointer = withUnsafePointer(to: rawValue) { UnsafeRawPointer($0) } return rawPointer.assumingMemoryBound(to: T.self).pointee } return nil } func isEnum<T>(type: T.Type) -> Bool { let metadata = unsafeBitCast(type, to: UnsafePointer<EnumMetadata>.self).pointee return metadata.kind == 1 // kind value of enum is 1 }
We know that we can cast Int
value to enum because they have same memory layout.
However the method is only available for enum and it's necessary to check whether the type is enum. Metadata is effective here.
The head address of the metadata is which kind the type is class
, struct
or enum
. The kind value of enum is statically 1. So we can check if the type is enum by comparing this value.
Caution
- ABI stability
- Responsibility
Swift4.2 has no ABI stability so metadata layout will be broken in Swift5. But we have good news that ABI stability is accomplished in Swift5! So you can make a library easily. But if you want to support both Swift4 and 5, you will need a lot of effort to maintain it. We got great power but please remember that using metadata is not an official way of doing things in Swift. If you use the metadata and particularly if you rewrite it, you must have a deep understanding of metadata. For example, method swizzling may be reverted to the original implementation by optimization. It's necessary to dispatch method through method table to swizzle implementation, but dynamic dispatch can be optimized to static dispatch by "Devirtualize" optimization. You must handle cases like this to use this magic. "With great power comes great responsibility"
Summary
- Swift uses metadata for dynamic behavior
- We can use metadata in Swift
- Let's write meta programming libraries!
Let me wrap up the key points. - First, Swift uses metadata for dynamic method dispatch, reflection API and so on. - Second, we can use it in Swift by reproducing memory layout. It brings big benefits and it's just fun. So, I'm looking forward to your great libraries using metadata. That’s all, thank you all very much.
Swift Type Metadata (ja)
try! Swift 2019
kateinoigakukunです。メルカリでインターンをしています。
今日はSwiftを理解するために重要なメタデータについて話します。Swiftは静的型付けな言語として知られていますが、 実はランタイムにおいては動的な部分が多々あるのです。
let typeName = String(describing: Int.self)
皆さんもStackOverflowを見て、一度はこんなコードを書いて型名を取得したことがあるでしょう。
extension UITableView { func register<Cell>(nibWithCellClass: Cell.Type) where Cell: UITableViewCell { let typeName = String(describing: Cell.self) let nib = UINib(nibName: typeName, bundle: Bundle.main) register(nib, forCellReuseIdentifier: typeName) } } tableView.register(nibWithCellClass: TweetCell.self)
例えばUITableViewCell
をregisterするとき、Xibのファイル名をクラス名と合わせるために使っていると思います。よくあるextensionですね。一方で、この一行がランタイムでどのように動いているか考えたことはありますか?これを機にSwiftのランタイムのメモリ表現を考えてみませんか? メタデータの世界に飛び込んでみましょう!
Agenda
- What is type metadata?
- Explore
String(describing: Int.self)
- How to use metadata in Swift
- Use cases in OSS
まず、そもそもメタデータとはなにか、について話します。メタデータは我々がアプリを開発する上であまり馴染みが無いかもしれません。しかし、アプリを作っている時でさえ、Swiftのコア機能は動的な動作のために内部的にメタデータを使っており、実はいつも恩恵を受けているのです。前のスライドで出した例を紐解きながら、Swiftが内部でどのようにメタデータを使っているかを解説します。そして、Swiftでどのようにメタデータを使うか、それを使ったハックを紹介します。
What is type metadata?
- Type information in Swift runtime
- Used in Swift internal dynamic behavior
- Metatype is pointer to metadata
let metatype: Int.Type = Int.self
メタデータは型についてのSwiftの内部情報です。例えばインスタンスのサイズや、enumのcase数などが含まれています。これらの情報はバイナリ上に静的に格納されていたり、ランタイムで動的に生成される場合もあります。Swift上では型名にself
を付けて表現されるメタタイプがメタデータへのポインタとなっています。
extension String { public init<Subject: CustomStringConvertible>(describing instance: Subject) { ... } public init<Subject>(describing instance: Subject) { ... } } let typeName = String(describing: Int.self) // "Int"
前述したように、 Int.self
はメタタイプのオブジェクトであり、そのメタタイプはString
のイニシャライザに渡されます。 このイニシャライザは任意の型の値を受け付けます。渡されてきた値の型が CustomStringConvertible
に準拠している場合はdescription
プロパティをそのまま返しますが、メタタイプが渡された場合は型名を返す、という挙動になっています。
extension Int.Type: CustomStringConvertible { // 🚫 Cannot extend a metatype 'Int.Type' var description: String { return "Int" } }
Swiftではメタタイプは拡張できないので、CustomStringConvertible
のdescription
プロパティが実装されているはずがありません。そのため、純粋なSwiftのAPIで実装するのは不可能だと思われます。SwiftにはObjective-CのようなランタイムAPIが無いからです。つまり、どこかに魔法があるはずです。
SwiftCore
- Swift standard library
- Fundamental types and interfaces
SwiftRuntime
- Swift runtime library
- Dynamic behavior
このイニシャライザはSwiftCore
で実装されており、魔法はSwiftRuntime
に実装されています。SwiftCore
には、Swiftで使われるString
やInt
などの基本的なデータ型とSequence
やNumeric
などのプロトコルが実装されています。 また、SwiftRuntime
はSwiftのC++で書かれたランタイムライブラリでランタイムの振る舞いが実装されています。ダイナミックなキャストやインスタンスのアロケーションのような多くの動的な言語機能はこのライブラリに実装されています。
stdlib/public/core/Mirror.swift
struct String { public init<Subject>(describing instance: Subject) { _print_unlocked(instance, &self) } }
Swiftの実装はオープンソースになっているので、Githubからいつでも見ることが出来ます。このイニシャライザの内部ではprint_unlocked
関数が呼ばれます。
stdlib/public/core/Misc.swift
public func _typeName(_ type: Any.Type, qualified: Bool = true) -> String { let (stringPtr, count) = _getTypeName(type, qualified: qualified) return String._fromUTF8Repairing( UnsafeBufferPointer(start: stringPtr, count: count)).0 } @_silgen_name("swift_getTypeName") public func _getTypeName(_ type: Any.Type, qualified: Bool) -> (UnsafePointer<UInt8>, Int)
引数の型がメタ型である場合の呼び出しスタックを掘り下げていくと、typeName
関数がメタ型を引数として呼び出されています。そして、また別の関数_getTypeName
がさらに呼ばれます。
_getTypeName
の定義を見てみましょう。
まず、 @ _silgen_name
属性が追加されていること、関数の実装がないことに気がつくと思います。これは何でしょう?この@_silgen_name
アトリビュートは、リンク時に付けられる関数の名前を指定します。 今回の場合、このシグネチャーは SwiftRuntime
の関数にリンクするために使われます。リンクされた関数の実装については省略しますが、単純にメタデータに含まれている型名を取り出すだけです。
では、メタデータはメモリ上でどのように表現されるでしょうか?
図のようにメタデータは分割されて表現されます。
- インスタンスを操作するための関数の集合であるValueWitnessTable
- クラス、構造体、プロトコルなどの種類の種類を表すkind value
- 型の詳細な情報を保持するNominalTypeDescriptor
。
- クラスの場合はVTableもメタデータに含まれています、
- さらに、genericな型の場合、型パラメータも動的に埋め込まれます。
NominalTypeDescriptorには、我々が今求めている型の名前が入っています。
つまり、メタデータのアドレスを進めるだけで、NominalTypeDescriptor
から型の名前を取得できるということです。 実装するのは難しく無さそうです。Swiftのコードでこの SwiftRuntime
のStringのイニシャライザの実装を再現してみましょう!
(docs/ABI/TypeMetadata.rst)
struct StructMetadata { let kind: Int let typeDescriptor: UnsafePointer<StructTypeDescriptor> } struct StructTypeDescriptor { let flags: Int32 let parent: Int32 let name: RelativePointer<CChar> }
まず最初にランタイムのメモリレイアウトをSwiftのstructとして再現します。メモリレイアウトに関する情報はほとんどドキュメントに書かれていますが、一部は古くなっているため、時々Swiftコンパイラーのソースコードを直接読まなくてはいけません。簡単のため、今回はstructについてのみ実装します。
ここでRelativePointer
というポインタの型が登場します。
(include/swift/Basic/RelativePointer.h)
RelativePointer
はただのポインターではありません。通常のポインターが参照先へのアドレスを持っているのに対し、RelativePointer
は自身のアドレスから参照先のアドレスへのオフセットを保持しています。参照するときはオフセット分アドレスを進めるだけです。absolute addressの代わりにrelative addressを使うことで無駄なアドレスの再配置を減らせます。
func getTypeName<Subject>(of type: Subject.Type) -> String { let metadataPointer = unsafeBitCast( type, to: UnsafePointer<StructTypeMetadata>.self ) let namePointer: UnsafePointer<CChar> = metadataPointer.pointee .typeDescriptor.pointee .name.advancedPointer() return String(cString: namePointer) }
下準備が出来たので実際に型名を取り出してみましょう。まず、引数から渡ってきたメタタイプをメタデータのポインタにキャストします。ここではSwift上のサブタイピング関係が無いためunsafeBitCast
を使います。取り出したメタデータからtype descriptorを経由してnameのrelative pointerにアクセスし、relative pointerからabsolute pointerに変換するとCChar
のポインターになります。これをSwiftのString型に変換してreturnします。
これで実装は完了しました。
let typeName = getTypeName(of: Int.self) // "Int"
これを動かすと、無事に型名を取得できます。メタプログラミングの第一歩目ですね!
Use cases inside of Swift
メタデータはSwiftの動的な振る舞いのためによく使われます。 皆さんはメタデータを使っていることを意識せずにSwiftを書いていますが、Swiftには多くのユースケースがあります。 それはどこでしょう? 最も一般的なユースケースは、インスタンスをアロケートすることです。さらに、protocol
やclass
を介してメソッドを呼び出すと、メタデータに格納されている関数テーブルが参照され、呼び出したいメソッドの参照が取得されます。
他の例としては、プロパティのリフレクションのためにメタデータを使っているMirror
APIが挙げれられます。
このように便利に使われているメタデータですが、人間はずる賢いので他の使い道を思いついてしまいます。
Method swizzling
ここからは皆さんがObjective-Cのころ使っていたあの黒魔術をSwiftで再現してみましょう。そうです、Method swizzlingです。メタデータを理解することで、大いなる力を手に入れることができるのです。
class Animal { func bar() { print("bar") } func foo() { print("foo") } } struct ClassMetadata { ... // VTable var barRef: FunctionRef var fooRef: FunctionRef }
型名を取得したときと同じ要領でメタデータのメモリレイアウトをstructで再現します。ClassのメソッドはVTableという関数ポインタのテーブルを通して呼び出されるため、そのポインタを書き換えれば実現できそうです。
let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.self ) let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 } let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 } bar.pointee = foo.pointee let animal = Animal() animal.bar() // foo
メタデータのポインタをメタタイプから取り出して、そこからswizzleする関数のポインタをmutableなポインタとして取り出します。すると関数ポインタの値を入れ替えるのは簡単です。このコードは非常にシンプルですがきちんと動きます。 このように、メタデータの情報によって我々は不可能なように見えるものでも実現できるのです。
Use cases
- Zewo/Reflection
- wickwirew/Runtime
- alibaba/HandyJSON
- kateinoigakukun/StubKit
私が見つけたメタデータを使っているOSSを紹介します。上の2つはメタデータの情報にアクセスするためのSwiftyなインターフェースを提供してくれるライブラリです。3つ目は、マッピングのための明示的な設定なしでJSONのエンコードとデコードができるJSONライブラリです。
alibaba/HandyJSON
struct Item: HandyJSON { var name: String = "" var price: Double? var description: String? } if let item = Item.deserialize(from: jsonString) { // ... }
この機能はSwift4.0以降ではCodable
でコンパイラがコード生成を使って達成できますが、HandyJSON
は Codable
が登場する前から動いており、Objective-CのAPIなしで動的にプロパティ名とプロパティの値の紐づけのためにメタデータを使っています。
Use cases
- Zewo/Reflection
- wickwirew/Runtime
- alibaba/HandyJSON
- kateinoigakukun/StubKit
最後のユースケースは私のライブラリStubKit
です。
kateinoigakukun/StubKit
import StubKit struct User: Codable { let name: String let age: UInt } let user = try Stub.make(User.self) // User(name: "This is stub string", age: 12345)
このライブラリを使うと、たくさんのフィールドを持つstructでも引数なしで簡単にスタブをインスタンス化できます。この機能の大部分は Codable
で実装されていますが、いくつかの機能はmetadataを使って実装されています。
メタデータを使った事例を紹介する前にこのスタブをインスタンスする機能がどのように動いているのかを解説します。まず、基本的に我々の使っている構造体は木構造で成り立っており、Decoder
プロトコルを使ってトラバースすることができます。
そのため、この木構造中の葉の部分のスタブを準備してトラバースしながらスタブを注入すれば、引数なしで任意のスタブをインスタンス化できます。
func leafStub<T>(of type: T.Type) -> T { guard let stubbable = type as? Stubbable else { return nil } return type.stub } extension Int: Stubbable { var stub: Int { return 12345 } } extension enum: Stubbable { // 🚫 Can't extend var stub: Self { return enumStub() } }
例えば、 String
、Int
、URL
およびenumは葉の型になる可能性があります。 基本データの型のスタブを準備するのは簡単ですが、enumはユーザーが定義できるため、自前で定義した全てのenumのスタブを手動で準備する必要があります。これはできれば避けたいですね。そのため、メタデータを使ってenumインスタンスを動的に生成してみました。
func enumStub<T>(of type: T.Type) -> T? { if isEnum(type: type) { let rawValue = 0 let rawPointer = withUnsafePointer(to: rawValue) { UnsafeRawPointer($0) } return rawPointer.assumingMemoryBound(to: T.self).pointee } return nil } func isEnum<T>(type: T.Type) -> Bool { let metadata = unsafeBitCast(type, to: UnsafePointer<EnumMetadata>.self).pointee return metadata.kind == 1 // kind value of enum is 1 }
メモリレイアウトからEnumのスタブはInt
型の値をキャストすれば手に入れられることが分かっています。しかし、その方法が使えるのはenumだけなので、スタブする型がenumの型であるか、ということをチェックする必要があります。そこでメタデータの出番です。メタデータの先頭アドレスにはその型がclass
なのか、struct
なのか、enum
なのか、といったkindの値が入っています。enumのkind値は1で固定なのでこの値を比較すれば型がenumかどうかを調べることができます。
Caution
- ABI stability
- Responsibility
Swift4.2ではABIが安定していないため、Swift5ではメタデータのレイアウトが崩れます。しかし、喜ばしいことにSwift5でABIの安定性が達成されました。今後は気軽にメタデータを使ったライブラリを作ることができます。ただし、Swift 4と5の両方をサポートしたい場合は維持が大変でしょう。 私たちは大いなる力を手に入れましたが、メタデータを使うことはSwiftのオフィシャルな方法ではないことを覚えておいてください。メタデータを使う時、特に書き換える場合は、メタデータについて正しい知識を持っておく必要があります。
Caution
- ABI stability
- Responsibility
例えば、前に紹介したMethod swizzlingはSwiftの型による最適化によって元の実装に戻ってしまうことがあります。実装を入れ替えるにはテーブルを参照してメソッドを実行する必要がありますが、Devirtualizeという最適化によって動的呼び出しが静的呼び出しになってしまうのです。 魔法を使うにはこのようなケースを正しくハンドルしなければなりません。 「大いなる力には大いなる責任が伴う」のです。
Summary
- Swift uses metadata for dynamic behavior
- We can use metadata in Swift
- Let's write meta programming libraries!
要点をまとめます。 - まず、Swiftは動的メソッドディスパッチやリフレクションのAPIなどにメタデータを内部的に使用しています。 - また、メモリレイアウトを再現することでSwiftでメタデータを使うことができます。とても役に立つだけでなく、単純に楽しいです。 皆さんのメタデータを使った素晴らしいライブラリを待ち望んでいます。 以上で終わります。ありがとうございました。
18になった
18歳になりました。1年間の振り返りと気持ちを書いておきます。
17歳
やったこととしてはこんな感じ。
自分としては結構充実していたと思ってます。が、行き当たりばったりにやっていたので計画を立てていたらもう少し色々出来たかもなーという思いもあります。
バイト
スタートトゥデイテクノロジーズでのアルバイトはめちゃめちゃ楽しかったです。隣でコンパイラの穴を見つけてボコボコに壊してる人がいたり、AndroidエンジニアとRxの話ができる環境は楽しすぎました。あと、Type Orientedな設計のお陰でパチっと機能追加できた、みたいなことがたまにあって体験が良かったです。
元々複数人での開発を経験してみたいというモチベーションで始めたので、これは実績解除できました。
勉強会
主にわいわいswittcに参加してました。5月の#2くらいまでは興味はあるが話を聞いても「ふむふむ分からん」状態だったのでフワフワ聞いてました。7月に入ってからコードを読み進めながら勉強会のアーカイブ動画を見るというスタイルで勉強してみました。自分の勉強した範囲と発表のテーマの知識を補完し合いながら読んでいくのは効率が良かったのでオススメです。
カンファレンス登壇
これ。 iOSDCで登壇したよ - kateinoigakukunのブログ
登壇をきっかけに僕が一方的に認知していた人から認知してもらえたり、「あ、あの人か!」と言ってもらえることが増えました。あとフォロワーもチョット増えました。
卒論
iOSDCで話した内容で書いてます。
卒論教員「君の卒論は読むのに勇気が必要なので後回しにしました」
— kateinoigaku (@kateinoigakukun) 2018年9月12日
OSS開発
主にInterface Builder用のLinterのIBLinterを作ってました。 GitHub - IBDecodable/IBLinter: A linter tool for Interface Builder
ポコポコPRやissueが立ったりして嬉しいなーという感じです。オーガニゼーション作ろうぜ!って言ってくれる人とかオーガニゼーションのアイコンを作ってくれる人が出てきて、インターネットって凄い!
18歳
重いので反転。
今月の終わりからバイト先がメルカリになります。今最高に楽しいらしいので楽しみです。 やっていくぞ💪