(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表現用の型とモデルの型をわけて・・・ってパターン、こうやると少し楽になると気づいた

gist.github.com


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書けって言われますね

あ、Hoge.JSONがInit適合してないからだ


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プロトコルに準拠している

他の準拠プロトコルとしてはCustomReflectableCustomStringConvertibleがあるが今回は省略する

Selector

定義

@_fixed_layout
public struct Selector : ExpressibleByStringLiteral

Selector型は文字列リテラルとして振る舞う為に、ExpressibleByBooleanLiteralプロトコルに準拠している

上述同様に他の準拠プロトコルとしてはCustomReflectableCustomStringConvertibleなどがあるが今回は省略する

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)
  }
}

CVarArgプロトコルに準拠することで機械語の変換を可能にする

(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 に渡すのはコンパイラが防いでくれる。

で、この inoutout の除いたようなものがほしいなぁと。

あと、↓みたいなのも防げるかと。

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)

概要

github.com

  • 軽量な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を通してキャンセルの動作を行った状態
  • 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を使う上で毎回お世話になるthencatchオペレータについて深掘りしていきたい

Swift単語メモ

Objective-C

インスタンス変数 / ivar, instance variable

Objective-Cランタイム内での変数を表す構造体のポインタの定義のことを示す

グラフ理論 / graph theory

有向グラフ / directed graph

directed graph

https://ja.wikipedia.org/wiki/グラフ理論#有向グラフ

ダイグラフ / digraph

有向グラフと同義

無向グラフ / undirected graph

undirected graph

https://ja.wikipedia.org/wiki/グラフ理論#無向グラフ

エッジ(枝・辺) / edge

https://ja.wikipedia.org/wiki/グラフ_(データ構造))

ノード(節点・頂点) / node

マルチエッジ, 多重辺 / multiple edge, multi-edges

セルフエッジ, ループ / loop, self-loop, self-edges

multi-edge and self-edge

未分類

一時的な割り当て / treating transient allocations

サイクルコレクタ / cycle-collector

サイクルコレクティング / cycle-collecting

循環参照用のメモリ管理の仕組みのこと? TBD

apple/swift-clang/include/clang/AST コードリーディング(全体)

概要

swift-clang/include/clang/AST at 0a8078f3c1190c7b6c2e021ad6b102971bca5413 · apple/swift-clang · GitHub

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のコマンドを調べてみた

概要

moaible-swift-memo.hateblo.jp

コマンド生成 - モードによる分岐終了まで

フラグ一覧

参考:
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

コマンド生成 - 共通箇所