(discord/swift/2018/03/13/1) Codableのtypealiasにstructを使ったtips
概要
- discord ios dev
- #swift
- 2018/03/13
- Codableのtypealiasにstructを使ったtips
log
omochimetaru
- 2018/03/13
ときどき話題に出てくるCodableとJSONの話で、JSON表現用の型とモデルの型をわけて・・・ってパターン、こうやると少し楽になると気づいた
t.ae
- 2018/03/13
assoctypeにtypealiasじゃなくてstruct定義でもいいんですね。
言われてみりゃそりゃそうだって感じですけど使ったことなかった。
tarunon
- 2018/03/13
いや、これは暗黙的に推論されてると思う
t.ae
- 2018/03/13
メソッドのほうからかな?
tarunon
- 2018/03/13
そう
t.ae
- 2018/03/13
試そうとしたんですが最近playgroundsが開かない
protocol Init { init() } protocol Proto { associatedtype JSON: Init } extension Proto { func hoge() -> JSON { return JSON() } } struct Hoge: Proto { struct JSON { } }
こう書いたらtypealias書けって言われますね
tarunon
- 2018/03/13
してないかな
t.ae
- 2018/03/13
: Init
つけたら怒られないですね
tarunon
- 2018/03/13
お、そこはそうだ
t.ae
- 2018/03/13
protocol Init { init() } protocol Proto { associatedtype JSON: Init } extension Proto { func hoge() -> JSON { return JSON() } } struct Hoge: Proto { struct JSON: Init, CustomStringConvertible { var description: String = "Hoge.JSON" } } print(Hoge().hoge()) // Hoge.JSON
こうかな
hoge()が挿入されて推論されるのかJSONが先にあるのか分からない
tarunon
- 2018/03/13
protocol Foo { associatedtype Bar } struct Piyo: Foo { struct Bar { } }
ありゃ、これ通るから
動きますね、すごい。昔はこれができなかったような気がします
omochimetaru
- 2018/03/13
ん?
t.ae
- 2018/03/13
それだけだとassoctypeのBarとPiyo.Barが同一視されてるか微妙だなぁと思ったんですが
omochimetaru
- 2018/03/13
え、結局、名前でマッチしてた?
tarunon
- 2018/03/13
そうっぽい
tarunon
- 2018/03/13
@t.ae 同一視出来ていないとコンパイルが通らないのでこれはOKなきがします
t.ae
- 2018/03/13
あーない場合はtypealias無いって言われますねそういえば
そんな基本的なことを忘れて長いコードを書いてしまった
omochimetaru
- 2018/03/13
本題どっか行った
t.ae
- 2018/03/13
encodeToInnerJSONの部分隠蔽できないんですかね
omochimetaru
- 2018/03/13
隠蔽とは?
t.ae
- 2018/03/13
UserCredentialをそのままJSONEncoderとかにわたせるように
omochimetaru
- 2018/03/13
なってるよ
Codableだから
t.ae
- 2018/03/13
あれ?
ああ一番上から来てるのか
omochimetaru
- 2018/03/13
そうか、エンコード・デコードのサンプルも書かないとだめだった。
apple/swift - stdlib/public/SDK/ObjectiveC コードリーディング(全体)
概要
swift/stdlib/public/SDK/ObjectiveC at master · apple/swift · GitHub
ObjectiveC.swift
ObjCBool
Objective-C BOOL型、64bitのiOSではC / C++ bool
のtypedefで、他ではsigned char
としている
Clang ImporterはObjCBool型としてインポートしている
定義
@_fixed_layout public struct ObjCBool : ExpressibleByBooleanLiteral
ObjCBool型はBoolのリテラルとして振る舞う為にExpressibleByBooleanLiteral
プロトコルに準拠している
他の準拠プロトコルとしてはCustomReflectable
、CustomStringConvertible
があるが今回は省略する
Selector
定義
@_fixed_layout public struct Selector : ExpressibleByStringLiteral
Selector型は文字列リテラルとして振る舞う為に、ExpressibleByBooleanLiteral
プロトコルに準拠している
上述同様に他の準拠プロトコルとしてはCustomReflectable
、CustomStringConvertible
などがあるが今回は省略する
NSZone
@_fixed_layout public struct NSZone { var pointer: OpaquePointer } // Note: NSZone becomes Zone in Swift 3. typealias Zone = NSZone
コメント通りNSZoneはSwift3からZoneとしてaliasが貼られている
OpaquePointer
をプロパティとして持っているので、実装を隠す意図でこの定義をしてると想定できる
autoreleasepool
public func autoreleasepool<Result>( invoking body: () throws -> Result ) rethrows -> Result { let pool = _swift_objc_autoreleasePoolPush() defer { _swift_objc_autoreleasePoolPop(pool) } return try body() }
Objective-C時代の@autoreleasepool
の機能を代替してくれる関数
RCStateTransition.cpp
からisAutoreleasePoolCall
にて呼び出されている
_swift_objc_autoreleasePoolPush
と_swift_objc_autoreleasePoolPop
は_swift_
のprefixが動的に付与されている模様
参考
Swift3.0時代のautoreleasepoolは素晴らしい | Tomorrow Never Comes.
NSObject
等価性比較 & hash対応
// NSObject implements Equatable's == as -[NSObject isEqual:] // NSObject implements Hashable's hashValue() as -[NSObject hash] // FIXME: what about NSObjectProtocol? extension NSObject : Equatable, Hashable { public static func == (lhs: NSObject, rhs: NSObject) -> Bool { return lhs.isEqual(rhs) } /// The hash value. /// /// **Axiom:** `x == y` implies `x.hashValue == y.hashValue` /// /// - Note: the hash value is not guaranteed to be stable across /// different invocations of the same program. Do not persist the /// hash value across program runs. @objc open var hashValue: Int { return hash } }
まずNSObject系統のクラス群はポインタの等価性チェックを行う際に- (BOOL)isEqual:(NSObject *)
を使う為、それをSwiftの世界で等価性を比較をするのにEquatable
プロトコルに乗っけることで実現している
同じように- (int)hash
はNSObject系統でのHashableプロトコルで返すhashValueと意味合いが同じため、Hashable
プロトコルとして返すように繋げている
CVarArg
extension NSObject : CVarArg { /// Transform `self` into a series of machine words that can be /// appropriately interpreted by C varargs public var _cVarArgEncoding: [Int] { _autorelease(self) return _encodeBitsAsWords(self) } }
(discord/swift/2018/03/13/0) inout atributeの挙動について
概要
- discord ios dev
- #swift
- 2018/03/13
- inout atributeの挙動について
log
koher
- 2018/03/13
最近、 inout 的に @escaping
なクロージャ式の中からアクセスできないようにするけど、特に out
したいわけじゃないというのを表せるものがあればいいんじゃないかという気がしてるですがどうでしょう
↓の asyncAfter
のクロージャ式から p
にアクセスするのを防ぎたい。
import Foundation func foo() { let a = [2, 3, 5] a.withUnsafeBufferPointer { p in DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(3)) { print(p[0]) } } } foo() Thread.sleep(forTimeInterval: 5)
0
omochimetaru
- 2018/03/13
エスケープさせたくないってことですか?
koher
- 2018/03/13
↑だと a が解放された後にそのバッファのポインタ p
にアクセスしちゃってるよね?
omochimetaru
- 2018/03/13
そうですね、これはだめ
koher
- 2018/03/13
↓だと inout だからコンパイルエラーになる。
import Foundation func foo() { var a = [2, 3, 5] a.withUnsafeMutableBufferPointer { p in DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(3)) { print(p[0]) } } } foo() Thread.sleep(forTimeInterval: 5)
'escaping closures can only capture inout parameters explicitly by value'
closure_implicit_capture_without_noescape <- (DiagnosticSema.def)
omochimetaru
- 2018/03/13
あ〜、なるほど、inout的にってそういう意味か。
koher
- 2018/03/13
withUnsafeBufferPointer
はクロージャに渡される引数がそのクロージャ式のスコープの中でのみ有効なのに、せっかく @escaping
を区別してるのにコンパイラがそれを強制する術がない。
omochimetaru
- 2018/03/13
え、もう一個のケースでも、pはinoutじゃないのでは?
普通の引数で型がUnsafeMutableBufferPointerですよね
koher
- 2018/03/13
それが inout なんですよ。意味わかんないんだけど。
あれ?違う??
omochimetaru
- 2018/03/13
public mutating func withUnsafeMutableBufferPointer<R>(_ body: (inout UnsafeMutableBufferPointer<Array.Element>) throws -> R) rethrows -> R
ほんとだ、あれ?なにこれ。
koher
- 2018/03/13
いや、あってた。
withUnsafeMutableBufferPointer(_:) - Array | Apple Developer Documentation
なんで inout なのかもよくわかんないんだけど、とりあえずそれのおかげで↑の asyncAfter
に渡すのはコンパイラが防いでくれる。
で、この inout
の out
の除いたようなものがほしいなぁと。
あと、↓みたいなのも防げるかと。
fooAsync { [weak self] in guard let zelf = self else { return } zelf.foo(...) barAsync { [weak zelf] in // ←この [weak zelf] を忘れないようにしたい guard let zelf = zelf else { return } zelf.bar(...) } }
rintaro
- 2018/03/13
クラッシャー
func after(_ fn: @escaping () -> Void) {} func with(_ fn: (inout Int) -> Void ) {} func test() { with { p in after { [p] in } } }
koher
- 2018/03/13
@rintaro それは明示的だから仕方ないですw
明示的にしかできなくできたらいいかなと。
malcommac/Hydra コードレビュー1 (Promise)
概要
- 軽量なPromiseライブラリ
- Async&Awaitもサポートしてる
- 便利な独自オペレータも豊富で使い勝手が良い
導入
軽量を謳ってるだけあって依存してるライブラリもなくシンプルにHydraAsyncのみの導入だけで良い
cocoapods
pod 'HydraAsync'
carthage
github 'HydraAsync'
定義を見ていく
まずは肝となるPromise.swiftから実装を見ていく
Hydra/Promise.swift at master · malcommac/Hydra · GitHub
クラス定義
返却される型をValueの型変数として指定する
public class Promise<Value>
typealias
Resolved
public typealias Resolved = (Value) -> ()
処理が解決した場合に使用される型定義、型変数として指定したValueが返ってくる
Rejector
public typealias Rejector = (Error) -> ()
処理が失敗した場合、もしくはBodyのスコープ内で例外をthrowした場合に使用される型定義
Body
public typealias Body = ( (_ resolve: @escaping Resolved, _ reject: @escaping Rejector, _ promise: PromiseStatus) throws -> () )
解決、失敗、キャンセルの操作をまとめて扱える型定義
イニシャライザ
通常
public init( in context: Context? = nil, token: InvalidationToken? = nil, _ body: @escaping Body )
指定したcontext上でどういう操作をするかを指定した上でPromiseの初期化を行う
即時解決
public init(resolved value: Value)
指定したValueで処理が解決した状態でPromiseの初期化を行う
即時失敗
public init(rejected error: Error)
指定したエラーの内容で処理が失敗した状態でPromiseの初期化を行う
実装を見ていく
定義を見たところよくあるPromiseの定義にキャンセル操作が付いただけなのでシンプルさがある
そこで具体的に内部の実装でどうやってPromiseを実現しているのかを掘っていってみる
Promiseの状態管理
まずHydraの内部として大まかにinternalなPromiseの状態が2種類ある、
- state: State
- pending
- まだ操作をしてない状態
- resolved(_: Value)
- 解決した状態
- rejected(_: Error)
- 失敗した状態
- cancelled
- PromiseStatusを通してキャンセルの動作を行った状態
- pending
- bodyCalled: Bool
- イニシャライザとしてbodyを渡す場合にbodyが実行されたかどうか
- 重複で呼び出されないように状態として保持している
これらを駆使して状態管理を実現している、そして状態を変化させる場合はqueueを通しており、
(例外としてキャンセルの操作のみ処理が走ってる場合でも割り込める必要があるのでqueueは通していない)
Promiseの状態変化専用のqueueとしてのDispatchQueueであるstateQueue
が定義されている
/// This is the queue used to ensure thread safety on Promise's `state`. internal let stateQueue = DispatchQueue(label: "com.mokasw.promise")
ドキュメントコメントにもある通りこのqueueがあるおかげでスレッドセーフにPromiseが扱えるということになる
Promiseの実行
HydraのPromiseは実行する際のフローとして最終的にPromise.swift内に定義されているrunBody
が呼ばれる
もちろんライブラリ使用者から呼ばれないようにするためにinternalなアクセスとなっていて、
基本的にはHydraが定義しているオペレータ群から叩かれることになる
internal func runBody() { self.stateQueue.sync { if !state.isPending || bodyCalled { return } bodyCalled = true // execute the body into given context's gcd queue self.context.queue.async { do { // body can throws and fail. throwing a promise's body is equal to // reject it with the same error. try self.body?( { value in self.set(state: .resolved(value)) // resolved }, { err in self.set(state: .rejected(err)) // rejected }, self.operation) } catch let err { self.set(state: .rejected(err)) // rejected (using throw) } } } }
まずは既に実行されてるかどうかを上述で説明したPromiseの状態を見て確認し、
その後に実際の実行処理に入りそれぞれBodyのclosure内で呼ばれた操作に従い
Promiseの状態をpendingから他の状態への遷移を決定している
(もちろんPromiseの仕様通りで、pending以外の状態から別の状態に切り替わることはない)
private func set(state newState: State) { self.stateQueue.sync { // a promise state can be changed only if the current state is pending // once resolved or rejected state cannot be change further. guard self.state.isPending else { return } self.state = newState // change state self.observers.forEach { observer in observer.call(self.state) } self.observers.removeAll() } }
observer.call
によりPromiseを呼び出した際に指定したclosureが発火する仕組み
ここでさらに内部の登場人物でobservers
が出てくる、これはresolve、reject、cancelなどの指定したclosureをObserverというenumの配列として管理してくれている
簡略化した定義は以下
public indirect enum Observer { public typealias ResolveObserver = ((Value) -> ()) public typealias RejectObserver = ((Error) -> ()) public typealias CancelObserver = (() -> ()) case onResolve(_: Context, _: ResolveObserver) case onReject(_: Context, _: RejectObserver) case onCancel(_: Context, _: CancelObserver) }
これだけ見ると色々拡張して追加できそうであるがpublicなので意図としてはこれ以上は拡張したくないとも取れそう
終わりに
HydraのPromiseのベースの仕組みに絞ってコードレビューをしてみたけど、やっぱりシンプルさは良い感じである
次はPromiseを使う上で毎回お世話になるthen
とcatch
オペレータについて深掘りしていきたい
Swift単語メモ
Objective-C
インスタンス変数 / ivar, instance variable
Objective-Cランタイム内での変数を表す構造体のポインタの定義のことを示す
グラフ理論 / graph theory
有向グラフ / directed graph
https://ja.wikipedia.org/wiki/グラフ理論#有向グラフ
ダイグラフ / digraph
有向グラフと同義
無向グラフ / undirected graph
https://ja.wikipedia.org/wiki/グラフ理論#無向グラフ
エッジ(枝・辺) / edge
https://ja.wikipedia.org/wiki/グラフ_(データ構造))
ノード(節点・頂点) / node
マルチエッジ, 多重辺 / multiple edge, multi-edges
セルフエッジ, ループ / loop, self-loop, self-edges
未分類
一時的な割り当て / treating transient allocations
サイクルコレクタ / cycle-collector
サイクルコレクティング / cycle-collecting
循環参照用のメモリ管理の仕組みのこと? TBD
apple/swift-clang/include/clang/AST コードリーディング(全体)
概要
Type.h
Qualifiers::TQ
これらのフラグはDeclSpec::TQ
と同期して保持する必要がある
definition | value | description |
---|---|---|
Const | 0x1 | ??? |
Restrict | 0x2 | ??? |
Volatile | 0x4 | ??? |
CVRMask | Const | Volatile | Restrict |
??? |
Qualifiers::GC
TBD: GCと定義名にあるが、意味としてはARC?要調査
definition | desc |
---|---|
GCNone | ??? |
Weak | ??? |
Strong | ??? |
Qualifiers::ObjCLifetime
definition | desc |
---|---|
OCL_None | この種類のlifetime はない |
OCL_ExplicitNone | このオブジェクトは保持、または解放を必要とせず変更できる |
OCL_Strong | このオブジェクトに割り当てるには、古い値を解放し、新しい値を保持する必要がある。 古い値の解放タイミングは不正確で、値が有効な最後の覚えてるポイントの直後に移動される可能性がある |
OCL_Weak | このオブジェクトから読み書きするにはbarrier call が必要 |
OCL_Autoreleasing | このオブジェクトに割り当てるにはlifetime の延長が必要 |
Type
型階層の基本クラス
Typeの中心的な概念は各Typeが常にcanonical type
を持つこと。
canonical type
は型定義名が全て削除された型、またはそれが参照する型のこと。
例えば次の点を考慮する
typedef int foo; typedef foo* bar; 'int *' 'foo *' 'bar'
「int」のために作成されたTypeオブジェクトがある、intはcanonicalなので、CanonicalTypeポインタはそれ自身を指す
「foo」の型(TypedefType)もある
CanonicalTypeポインタが「int」型を指している。
次に「int」のような「int *」を表すPointerType型があり、「int」のようにcanonicalである。
最後にcanonical typeが「int * 」である「foo * 」のPointerType型があり、canonical typeも「int * 」である「bar」のTypedefTypeがある。
Non-canonical typeは使用されるtypedefに関する情報を失うことなく診断を発行するのに便利。
canonical typeは型の比較(ポインタに透過性検査を可能にする)に有用であり、ある型から全ての型定義を暗黙的に再帰的に取り除くため、型が特定の型となる。(関数型など)
Typeらは一度作成すると変更できない。
Typeのクラス図
clang.llvm.orgより拝借
ClangImporter.cpp - ClangImporter::createで生成しているclangのコマンドを調べてみた
概要
コマンド生成 - モードによる分岐終了まで
フラグ一覧
参考:
swift-clang/Options.td at ba3b1c672105db284b1e425d4ab7cdc8411f29a3 · apple/swift-clang · GitHub
Swift関連で追加してるマクロ定義一覧
Swift bridging headerで使われるマクロ定義
(おそらくObjCからSwiftを利用する際のマクロ定義群なので、"<# ProductName #>-Swift.h
関連だと思われる)
#define SWIFT_NATIVE_ANNOTATION_STRING "__swift native"
macro definition | value | description |
---|---|---|
SWIFT_CLASS_EXTRA |
__attribute__((annotate (\""SWIFT_NATIVE_ANNOTATION_STRING "\"))) |
??? |
SWIFT_PROTOCOL_EXTRA |
__attribute__((annotate (\""SWIFT_NATIVE_ANNOTATION_STRING "\"))) |
??? |
SWIFT_EXTENSION_EXTRA |
__attribute__((annotate (\""SWIFT_NATIVE_ANNOTATION_STRING "\"))) |
??? |
SWIFT_ENUM_EXTRA |
__attribute__((annotate (\""SWIFT_NATIVE_ANNOTATION_STRING "\"))) |
??? |
SDK関連で追加してるマクロ定義一覧
macro definition | value | description |
---|---|---|
_ISO646_H_ |
- | Avoid including the iso646.h header because some headers from OS X frameworks are broken by it. oss apple: lib/Headers/iso646.h Source File |
__ISO646_H |
- | 同上 clang: lib/Headers/iso646.h Source File |
SWIFT_SDK_OVERLAY_APPKIT_EPOCH |
2 | AppKit |
SWIFT_SDK_OVERLAY_FOUNDATION_EPOCH |
8 | Foundation |
SWIFT_SDK_OVERLAY2_SCENEKIT_EPOCH |
3 | SceneKit |
SWIFT_SDK_OVERLAY_GAMEPLAYKIT_EPOCH |
1 | GameplayKit |
SWIFT_SDK_OVERLAY_SPRITEKIT_EPOCH |
1 | SpriteKit |
SWIFT_SDK_OVERLAY_COREIMAGE_EPOCH |
2 | CoreImage |
SWIFT_SDK_OVERLAY_DISPATCH_EPOCH |
2 | libdispatch |
SWIFT_SDK_OVERLAY_PTHREAD_EPOCH |
1 | libpthread |
SWIFT_SDK_OVERLAY_COREGRAPHICS_EPOCH |
0 | CoreGraphics |
SWIFT_SDK_OVERLAY_UIKIT_EPOCH |
2 | UIKit |