ソフトウェアエンジニアの不定期ログ

雑多 作業、学習ログ多め。

MVVM, RxSwiftの学習メモ その1 RxExampleのコードを読む

MVVM及びRxSwiftの学習を始めたので勉強したことをメモしています。 RxExampleのうち、GitHubSignup1はRxSwiftでつかわれるDriverなどを使用していないとのこと。全くRxSwiftのこともMVVMのことも理解していない状態で手探りで進めています。何か誤りがあればお知らせください。

GitHubSignup1での各層の役割

ViewController

ViewModelの役割

  • API用のロジックなどをイニシャライズのタイミングで外部から用意
  • イニシャライズで受け取ったObservableを出力に変換

コードを読む

ViewController初期化時に行われていることの概要

GitHubSignupViewController1はViewDidLoadのタイミングでViewModelを初期化しています。viewDidLoad内でのみ使用されていてGitHubSignupViewController1はプロパティとしてViewModelを保持していないです。 ViewModelの初期化時には引数inputにIBOutletで接続した各UIコンポーネントのプロパティをObservableに変換して渡してます。引数outputには依存するモジュールをまとめて渡しています。引数は両方共tupleです。 その後ViewModelの必要なプロパティにバインドして結果をViewに反映させています。

※今この時点でわからないこと - ViewModel破棄しても大丈夫なの?出力のストリームは破棄されないなのだろうか(ViewModel自体は持つべき情報がもうないからすぐに破棄してるんだろうと思います。)

ViewModelの出力をViewにバインドさせる

この処理を各部分でsubscribeとbind(to:)を使い分ける基準んがわかりませんでした。 bind(to:)を使う方が簡潔に書けるようです。 しかし、bind(to:)を使うには条件があるみたいで、

extension ObservableType {
  public func bindTo<O>(_ observer: O) -> Disposable where O : ObserverType, Self.E == O.E {
  //
  }
}

引数OがObserverTypeにconformしていて、かつObservableTypeにconformしているSelfのEとO.Eで==の関係が成り立つ場合に限るとなっています。 今回bind(to:)でバインドされているのを一つ取り出してみると

viewModel.validatedUsername
            .bind(to: usernameValidationOutlet.rx.validationResult)
            .disposed(by: disposeBag)

レシーバーがObservableで引数には usernameValidationOutlet.rx.validationResultが割り当てられています。usernameValidationOutlet.rx.validationResultはBinderで、Binderはpublic struct Binder: ObserverTypeでObserverTypeにconformしているのでbind(to:)メソッドによるバインドが可能になります。値をそのまま反映させるだけならこれで十分ということでしょうか。

Binderについて

  • エラーのバインドは不可
  • バインディングが特定のスケジューラで確実に実行されるようにしてくれる
  • Binderはターゲットを保持しない ターゲットが解放された場合には要素はバインドされない。

DisposeBagについて

disposeBagオブジェクト自身が破棄されるタイミングで登録されたsubscriptionをまとめて破棄する仕組みです。

Observableを変換

validatedUsername = input.username
            .flatMapLatest { username in
                return validationService.validateUsername(username)
                    .observeOn(MainScheduler.instance)
                    .catchErrorJustReturn(.failed(message: "Error contacting server"))
            }
            // ここでHot Observableに変換してる
            .share(replay: 1)  

上のコードを見ると、input.username: Observableからmapで文字列を取り出してvalidatenServiceの処理を適用してその結果をreturnしてストリームに変換されています。

shareが何をしているのか全く想像できなかったため、調べているとHot ObservableとCold Observableという表現が多く目に付きました。これについて解説しているのがRxSwiftのdocumentationにあります。 RxSwift/HotAndColdObservables.md at master · ReactiveX/RxSwift

Hot Observable

  • ストリームの途中に挟むとsubscribeするより前にストリームを稼働させることが出来る
  • 上流のCold Observableの起動と値の発行要求
  • ストリームを分岐させる
  • 自分から値を発行

Cold Observable

  • 自分から値の発行は不可
  • subscribeしてから動作
  • ストリームの分岐不可

share(replay:)はCold ObservableをHot Observableに変換します。subscribeされるたびに動作してしまうのを避けるためにHot Observableに変換しているようです。

shareメソッドを呼び出さずにsubscribeした際にflatMapLatestが2度呼ばれていることを以下のコードをinitializerに挿入することで確認します。

let hoge = input.username
            .flatMapLatest { username -> Observable<ValidationResult> in
                print("called flatMapLatest")
                return validationService.validateUsername(username)
                    .observeOn(MainScheduler.instance)
                    .catchErrorJustReturn(.failed(message: "Error contacting server"))
        }
        
        _ = hoge.subscribe({ _ in
            print("subscribed count 1")
        })
        _ = hoge.subscribe({ _ in
            print("subscribed count 2")
        })

ログには

called flatMapLatest
subscribed count 1
called flatMapLatest
subscribed count 2

と表示されました。shareメソッドを追記すると

called flatMapLatest
subscribed count 1
subscribed count 2

と表示されました。やはりCold ObservableからHot Observableへの変換が行われているようです。そのメリットがあることもわかりました。

flatMapLatestメソッド

initializer入力シーケンスから出力シーケンスへの変換の際、usernameの入力を出力に変換する部分のみmapではなくflatMapLatestを使用しています。

flatMapメソッドは元のObservableのイベントをObservableに変換して、その発行するイベントをマージするのに対して、flatMapLatestは次のイベントが来た時に前のイベントの処理をキャンセルします。

public func flatMapLatest<O>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E> where O : ObservableConvertibleType

まとめ

わからないことをそれぞれ章立てしてメモしていきました。初めてコードを読んだ時は全く何をやっているか把握できなかったのですが、一通り読んだ後は何をしているか理解できるようになりました。メモの途中にあげた疑問点が解決したら記事を新たに作り、リンクを貼るようにします。

Swiftのextension内でのmethodのoverrideについて

@_monoさんのツイートがきっかけでdocumentationやstackoverflowを調べたりplaygroundで遊んだりしたのでそのログ。

extensionでoverride出来る時と出来ない時がある

UIViewControllerのSubclassだとmethodのoverrideが出来る。UIViewControllerのSubclassだと出来るのでは?と思ってObjective-Cのクラスを継承していないSuperclassを用意してそれを継承したSubclassでmethodのoverrideを行ってみたら警告が出た。

Overriding non-@objc declarations from extensions is not supported

SuperClassにNSObjectを継承させてobjc由来のmethodを定義して、SubClassにextension内でoverrideさせると警告はなくなる。

// 警告が出ないコード
class Animal: NSObject {
    @objc dynamic func say() -> String {
        return "Animal"
    }
}

class Cat: Animal {
    
}


extension Cat {
    override func say() -> String {
        return "Cat"
    }
}

objc由来の型に関してはSwiftで書いていても動作はObjective-Cのruntimeのよう。 classとmethodの療法がObjective-Cでないと動かないのはobjc_msgSendでメソッドコールをしている。

ここまでの参考 Swiftにおけるmethod dispatchについて qiita.com

Objective-Cの動的なメソッド呼び出しについて qiita.com

StackoverflowでのOverriding methods in Swift extensionsについての質問

stackoverflow.com

Appleのdocumentationには

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code (known as retroactive modeling). Extensions are similar to categories in Objective-C. (Unlike Objective-C categories, Swift extensions do not have names.) Extensions can add new functionality to a type, but they cannot override existing functionality.

https://docs.swift.org/swift-book/LanguageGuide/Extensions.html

とあるのでこれらのことを踏まえると今まで通りSwiftでは、基本的にextension内でmethodのoverrideを行わない方針で問題なさそう。 何かご指摘があればにt__nabe(なべ) (@t__nabe) | Twitterにお願いします。

Swift実践入門を再読する

iOS開発を初めた当初に一度Swift実践入門を一気に通読してから業務でアプリ開発に入った。それまで半年ほどRailsでWebアプリケーションの開発をしていきなりのモバイルアプリ開発なのでかなり苦労した。それでも少しずつ開発を勧めてやっとこの前ストアにリリースできた。社内で使ってもらう状態にできて新しいデザインと新機能の追加の要望が来たので、宣伝込みの正式なリリースへ向けたアップデートを現在進めている。その過程でSwiftらしいコードを書いて少しでも楽に機能追加やリファクタリングを行いながらSwiftの言語仕様についてきちんと理解したいと考えて以前読んだSwift実践入門を再読することにした。この記事をそのログとして残そうと思う。基本的に自分がうろ覚えになっていた所を残す。

また、記載されているコードはいずれも本とは異なるコードです。 blog.jnito.com を読んで大いに納得したのが理由だが、ブログを書くときにコードも考えているため書籍に紹介されているコードと比べて意味が理解しづらいものも多いかもしれない。playgroundで動作確認済み。

コードの実行環境

Swift4.2

Xcode Version 10.1

制御構文

caseのwhereによる条件指定

caseのwhere。case文、主にenumに使ってwhere句を使った柔軟な分岐をそもそもしなくて済むようにしたくて使っていなかったが、UILabelのtextプロパティに入れる文字列を投稿時間で分けるDate型のextensionを実装する時に使った。

let score = (70, 60)
switch score {
case (let english, let math) where english == 100 && math == 100:
    print("満点")
case (let english, let math) where english >= 70 && math >= 70:
    print("両方70点以上")
case (let english, _) where english >= 70:
    print("英語は70点以上")
default:
    print("それ以外")
}

for case

もっとはやく使えば良かった。where以降の条件に一致する場合、ループ内の処理を実行する。UIStackView内を実データの配列を使ってaddArrangedSubview(_:)していく時に、forやifを使っていたのを思い出してこれを読んだ後書き直した。

let mediaList: [Media] = [
    .book(title: "ドラえもん 本1", author: "藤子・F・不二雄", year: 1997),
    .book(title: "ドラえもん 本2", author: "藤子・F・不二雄", year: 1999),
    .book(title: "ドラえもん 本3", author: "藤子・F・不二雄", year: 1999),
    .movie(title: "ドラえもん 映画1", director: "藤子・F・不二雄", year: 2004),
    .movie(title: "ドラえもん 映画2", director: "藤子・F・不二雄", year: 2007),
    .website(urlString: "https://ja.wikipedia.org/wiki/%E8%97%A4%E5%AD%90%E3%83%BBF%E3%83%BB%E4%B8%8D%E4%BA%8C%E9%9B%84")
]

print("映画版列挙:")
for case let Media.movie(title, _, year) in mediaList {
    print(" - \(title) (\(year))")
}

// 映画版列挙:
//  - ドラえもん 映画1 (2004)
//  - ドラえもん 映画2 (2007)

repeat-while

通常のwhile文だとwhileの条件によっては一度{}内の処理が実行されないかもしれない。条件式の正否を問わず{}内の処理を必ず行う場合にこれを使う。

var a = 10
repeat {
    print("実行されました")
    a += 1
} while a == 10

クロージャ

クロージャの属性

両方使いどころはわかっていたが使い方をなんとなく知っていただけだった。読むまでautoClosure属性は引数をクロージャで包むという処理を暗黙的に行っているのを知らなかったので、 これをつけたら遅延評価される程度の理解だった。実際に遅延評価をのメソッドを実装してからそれを簡単に書けるautoclosure属性を使った実装に変更するとなるほどとなった。

型の構成要素

サブスクリプト

サブスクリプトを自分で定義できる。サブスクリプトオーバーロードも普段意識せずに当然のように使っていた。サブスクリプトオーバーロードの例としてArray型が挙げられていて(Intを引数に取ってElement型の要素を返すサブスクリプトとRangeを取ってArraySlice型のスライスを返すサブスクリプト)わかりやすかった。

// サブスクリプトの独自実装サンプル
struct StudentNumber {
    var numbers: [[Int]]
    subscript(classNumber: Int, studentNumber: Int) -> Int {
        get {
            return numbers[classNumber][studentNumber]
        }
        set {
            numbers[classNumber][studentNumber] = newValue
        }
    }
}

let studentNumber = StudentNumber(numbers: [
    [1, 2, 3, 4],
    [1, 2, 3, 4],
    [1, 2, 3, 4]
    ])


let number = studentNumber[1, 2]

extension

いつも使っているので真新しいことはなかったが、使い方や使う時の考え方、注意点などが言語化されているのがこの本の良いところだと思う。曖昧な理解なことに気付かされる。エクステンションでstored propertyを定義することができないがそれを解決するためのワークアラウンドはいくつも出てくるが自分は当面使いたくない。

Stored Properties In Swift Extensions - Marco Santa Dev

Swiftのextensionでstored propertyを追加する?(黒魔術は閉じ込める) • Yuta Tokoro

疑問に思ったことはextensionにstored propertyが定義できない理由。書籍にも書いてなくて自分で調べたが確実にこれだというのものは見つけられなかった。

ドキュメント

他にはextensionで既存の型にinitializerを追加することでアプリケーション固有の情報から既存の型のインスタンスを生成するなどがある。initializerを定義するだけなのでコードは貼らない。実務で開発しているアプリでAPIリクエスト投げた時のエラーメッセージを使う時にUIAlertControllerを使っているが、その時に毎回UIAlertControllerを初期化するのは面倒なのでエラー内容をEnumで受け取るconvenience initializerをextensionで定義すると毎回書かなくて済むの楽だ。

型の種類

classとstatic違いと使い分け

その値がサブクラスで変更される可能性があるかどうかで使い分ければ良さそう。staticがつくとoverride出来ないので。 書籍ではその後例としてコードを記載して終わったが、staticとfinal classはどう違うのか気になってしまった。 staticとclassのみ変更した2つのswiftファイルのSILを観て比較してもアクセスの仕方が違うだけでclass funcもstatic funcと同じ関数が用意されていた。今度はfinal funcとclass funcを比較してみると、SILレベルで同じファイルが生成された。つまりclass funcとstatic funcはどっちを使っても良いのではと思った。

自分はここで調べるのを辞めてしまったが、Qittaの記事でclass funcとfinal class funcを比較している記事がありとても勉強になった。 https://qiita.com/YutoMizutani/items/040d8af5bfc38d9f1fac#_reference-173b518dffeeba29ba18

参照型の比較

==しか使っていなかった。参照元の比較が1回で済む所を、一意なプロパティを定義して値で比較していた。

class HogeClass: Equatable {
    static func == (lhs: HogeClass, rhs: HogeClass) -> Bool {
        return true
    }
    var value: String
    init(value: String) {
        self.value = value
    }
}


let hoge1 = HogeClass(value: "hoge")
let hoge2 = HogeClass(value: "hoge")

// 値の比較
hoge1 == hoge2 // true

// 参照の比較
hoge1 === hoge2 // false

protocol

protocol composition

whereで条件指定する時に&は使っているが関数の引数で2つのprotocolにconformしていることを要求したことがあんまりなかった。大抵の場合2つのprotocolにconformしたprotocolを用意してそれを引数にしていたと思う。

protocol HogeProtocol {
    var hoge: String { get }
}

protocol FugaProtocol {
    var fuga: String { get }
}

struct HogeFuga: HogeProtocol, FugaProtocol {
    var hoge: String
    var fuga: String
}

func hogeFugaFunction(x: HogeProtocol & FugaProtocol) -> String {
    return x.hoge + x.fuga
}

let hogefuga: HogeFuga = HogeFuga(hoge: "hoge", fuga: "fuga")
hogeFugaFunction(x: hogefuga)

標準ライブラリのプロトコル

前回読んだ時も思ったが、protocolで1番おもしろいのはここだった。実際に使っている"=="などをprotocolを使ってどのように実装しているか知ることができる。サンプルコードで実際に標準ライブラリのプロトコルにconformした型を定義するので、読んで自分で書いてみながらprotocolの便利さが実感できる。Swift触ったばかりの時はRubyで覚えたOOPしか知らなかったのでprotocolを中心に実装していく利点がいまいちわからなかったのを覚えている。 

ジェネリクス

戻り値からの型推論による特殊化

いつも引数からの型推論による特殊化を行っていたので両方選択肢があった時に戻り値からの型推論による特殊化を採用するイメージがまだ湧かない。property宣言する時に明示的に型を明示したい時とかかなあ。

func getAnyValue<T>(_ any: Any) -> T? {
    return any as? T
}

let a: String? = getAnyValue("abc")
let b: Int? = getAnyValue(2)
let c = getAnyValue("hogehoge") // Generic parameter 'T' could not be inferred

型の設計指針

copy on write

var ids = [1, 2, 3]
var ids2 = ids
ids.append(4)
ids
ids2

idsをids2に代入した時点でコピーが行われているように見えるが、実際はarray1.append(4)の時。違いが生じた時。

その他構造体とクラス、プロトコルと継承の使い分けの基準についての説明等があったが、この点は実務で疑問に思って何度も調べて今の所疑問もないので割愛。

イベント通知

ここで気になったのは細かい所。

#selectorの引数として渡すmethodに@objcをつけないといけない理由

公式のdocumantationに#selectorについての記載がある。

developer.apple.com

SelectorはObjective-CではObjective-Cメソッドの名前を参照する型らしい。Swiftでは、Objective-CのSelectorはSelector strunctで表され、"#selector" 式を使用して構築できる。 "#selector"を使ってメソッドを呼び出す時、Objective-CのMethod dispatchの方式、Messageを使用したdispatchを行うため、@objcが必要になる。数日前にMethod dispatchについて調べたおかげですんなり理解できた。

書籍にもSelector型について説明がある。

セレクタObjective-Cの概念であるため、Selectorgata wo生成するにはメソッドがObjective-Cから参照可能である必要があります。

とのこと

参考

stackoverflow.com

github.com

ja.stackoverflow.com

qiita.com

エラー処理

ここで知りたかったそれぞれのエラー処理のやり方をどのように使い分けるか。そのことが丁寧に説明されている。

do-catch

Result<T, Error>との使い分けの部分勉強になった。Result<T, Error>は型引数Errorと同じ型のエラーしか扱えないのに対し、do-catchではErrorプロトコルに準拠する型であればどのような型でも扱える。それぞれエラーを予測できるResult<T, Error>と複数の種類のエラーを1ヶ所で扱えるdo-catchには明確なメリットとデメリットがあることを理解できた。とはいえ実際のコーディングの時に読んだだけで使えるわけがないのでエラー処理を書くたびにしばらく読み返すことになりそう。

アサーション

一度も使ったことなかった。テストでAssert〜って書くぐらい。

func countHoge(count: Int) -> String {
    var strings: [String] = []
    assert(count >= 5, "引数の数字は5以上に設定してください")
    for _ in 0..<count {
        strings.append("hoge")
    }
    return strings.joined()
}


print(countHoge(count: 5))   // hogehogehogehogehoge
print(countHoge(count: 4))   // Assertion failed: 引数の数字は5以上に設定してください: file ios.playground, line 7

リリース時に無効になるのは良い。たまにprint文が残ったままになっていることがあったので。 

2018年振り返りと2019年の抱負

丸一年プログラマとして働いた

2017年の5月にWebメディア運営を行っていた部署の管理職からプログラマに転職したので2017年は会社にとってほぼ使い物にならない状態だったと思う。 2018年は一年間を通して1つのサービスの開発に携わってApp Storeにリリースすることができ、一通りリリースまで自分一人で経験させてもらえたのは幸運だった。

また、社内で運営しているWordpressで構築したWebサイトの保守と要望対応も行っていた。 プライベートではSwiftを使って年末に急いでパーソナルはてなフィルタを個人用に開発した。とりあえずはプライベートで使えるようになった。

また、ずっとSwiftを書いていたのでよりSwiftらしいコードの書き方にこだわるようになり、Swiftの型システムやジェネリクスプロトコルがどのように実現されているかにも関心を持つようになった。

2019年

  • 今宣伝に向けてアプリのブラッシュアップとデザインの刷新を進めているので春先には完成させて宣伝のフェーズに移りたい。 Wordpressで構築したWebサイトの改善要望への対応はPHPJavaScriptjQueryCSSを扱うが、普段使ってる言語と違って対応が遅くなりがちなのでプライベートでも時間を取って抵抗がなくなるように学習を進めたい。

  • プライベートで作っているアプリの完成度は世に出すのはありえないぐらいのクオリティなので使いながら不満点を改善しつつデザインを整えてApp Storeでリリース出来るように進めていきたい。

  • より良いコードがSwiftで書けるようにもっとたくさんコードを読んで書いていきたい。 Swift言語そのものやコンパイラにせっかく興味を持ったのでまずはわいわいswiftcの動画がYoutubeに公開されているので年始に一通り観てどういう風に学習を進めるか決めていきたい。わいわいswiftc行きたいけど福岡からだと厳しい…。

自作キーボードにハマった

電子工作初心者ではんだ付けも全く経験がない状態だったが、わからないことは調べたり質問させていただきながらいくつかキーボードを組み立てた。キーマップを自由に変更出来るキーボードが手に入ったのは非常に良かった。

いくつか撮影した写真は以下

自作キーボード - works

今の所組み立てたキーボード一覧

  • Ergo42
  • Ergo42 Towel
  • Helix
  • Let's Split
  • Ergodash
  • Planck
  • Lhaplus
  • Fourier
  • crkbd
  • iris

積んでいるキーボード一覧

  • Fortitude60

2019年

キーボードは結構作ったもののキーキャップの味気なさに悲しくなったので最近はキーキャップに関心がある。好みのキーキャップを探して今年は散財していきたい。良い値段のする60%キーボードも2019年には作りたい。

総括と2019年の抱負まとめ

仕事が深刻な状況にあるとプライベートも充実しないタイプなので、自作キーボードだけでなくゲームにも熱中出来た今年は公私共に良い年だったんだと思う。 とはいえ、初心者として入社して多少上がったものの、現職の給与には満足していないので、現職で給与を上げられるように現職で働きながら転職の準備も少しずつ進めていきたい。 2018年、大変お世話になりました。2019年もよろしくお願いします。

Fallout76のメインシナリオを友人と2人でプレイした後の感想

f:id:nabeatsu1:20181231172130j:plain Fallout76を2人でプレイした。きちんとしたレビューは以下の記事などを参照するのが良いと思う。今回書くのはライターでもない1ユーザーがプレイした感想。

arcadia11.hatenablog.com

jp.ign.com

世界観

Falloutの過去作をプレイしていた人ならご存知の魅力的なNPCが今作では一切登場しない。広大なマップにユーザーが放り出されてスコーチやスーパーミュータントしかいない世界を放浪して食料や武器、クラフトの素材を求めて放浪するゲーム。

ロケーション、グラフィックはさらに良くなっていた。ソロでプレイしているときに風景に目を奪われて周りを見渡して雰囲気に浸っていた時間は短くない。拾ったホロテープを再生して終わっていく世界に自分の記録を残そうとしている音声にしんみりしながらカバー版「Country Roads(カントリー・ロード)」を聴いてプレイしていた。

ストーリー

と同時に前作Fallout4のレビューでよく見られたworld feels “empty”という指摘をより強く感じさせられる内容だった。魅力的なNPCが削除され本当のサバイバルになったは良いもののメインのシナリオもサブのシナリオもさらにお使い感が強くなった。2箇所を何度も往復させられ、さらにバグで全く先に進めずロードしなおして単調で長いクエストを周回させられた時は本当に虚しかった。ロードが非常に長いことがさらにストレスだった。

秀逸な世界設定とそれを舞台にした示唆に富んだストーリーと魅力的なキャラクター、勢力にユーザーが選択した行動によって主人公の在り方を定義出来るところが好きだった。

今回のFallout76は過去作の好きだったそういうところをプレイしていてほとんど感じなかった。

エストや世界観にユーザーが関与できる部分は殆どなく、基本的にはお使いを繰り返すだけ。それでもあのFalloutの世界観で友達とプレイするのは非常に楽しい。ただ協力プレイ中に会話やシューティングの部分にばかりフォーカスしてシナリオや雰囲気について考える暇もなくストーリーが進行していったのは惜しいことをしたなと反省している。

オンラインで遭遇する他ユーザーについて

特定の役割をこなすNPC以外は敵しかいない世界観なのでたまに野良のユーザーと遭遇するとお互いにエモートを送り合ってコミュニケーションを取ることが多かった。特にソロでプレイしているユーザーは親切にも資源や武器を落としてくれることもあった。自分もレベルがまだ低い人がいたら積極的に助けたり資源を目の前で落としてあげたりしていた。

また、レベル150ぐらいのユーザーがゴロゴロいるので発売されてそう時間も立っていないのに何時間プレイしているんだと最初は思ったものの理由は簡単だった。というのも、伝説級の武器収集やレベル上げに非常に効率が良い場所がいくつかあり、そこには常に高レベル帯のユーザーが常に集まっている。オンラインゲームではよくあることだ。

クラフト要素や武器について

メインクエストやサブクエストの報酬として、他には各MAPにオブジェクトとして配置されていたり収納箱に格納されていたりするのでそれを回収していくのは今作の醍醐味の一つだと思う。とはいえ、収集しなくても全く問題ないのでシナリオをクリアしたいだけなら気にしなくて良い。収集した素材や武器を収納しておく収納箱の容量はこれから拡大されていく予定らしい。  

買って後悔するようなものではない

バグは多いしロードは遅いし上記のような不満あるもののFallout独特の世界観の中で少ない資源でやりくりしながらクエストをこなしていくのは本当に楽しかった。メインクエストと特定のクラフトアイテムの設計図を手に入れるためのサブクエスト、デイリーのイベントしかやっていないが買う価値は十分にあった。

一通りクリアしたら今度はFactorioをプレイすることになっているのでしばらくプレイしなくなるだろうが、今後MOD対応も予定されているのでその時にまたFallout76を起動したい。

Arrayに対する処理をglobal queueで行った時の順序について

SwiftのDiscordコミュニティはレベルが高い。実務経験1年弱の自分の今のスキルと大きく乖離があるので自分で調べ直して理解しないといけない現状。 そこでSwiftのDiscordコミュニティで話をされていたことで自分がすぐに理解出来なかったこと且つ興味の湧いた所を定期的に残していきたいと思う。Scrapboxとの使い分けにいまだに悩む

配列に対するメインスレッドでの並列処理

import Foundation

var array = Array(repeating: 1, count: 100000)

DispatchQueue.global().async {
  array.insert(3, at: 99999)
  print("inserted")
}

print("start")
let total = array.reduce(0, +)
print(total)
print(array.count)

これの実行結果が以下。Command line executable

# 順番は実行するたびに変わる恐れあり
start
inserted
100000
100001

Command line executableの場合のmain()は生のmain。 dispachMain()を自分で呼ぶなどしないとDispachQueueの支配するメインスレッドにならない。のでlet totalの行は指定しているqueueの中で実行されていない。

DispatchQueue.asyncの並列実行は、発火しても、スケジュールされるかどうかは運次第なので、asyncが実行される前にarray.reduceが終わってしまう可能性があります。 https://discordapp.com/channels/291054398077927425/291211035438874625/504148932818108419

これに関しては今まで開発で使ってきたので納得。

global dispatch queueはconcurrent queue。オブジェクトが1つだとしても。QoSについてはEnergy Efficiency Guide for iOS Apps: Prioritize Work with Quality of Service Classesを参照。

Arrayはスレッドセーフなのか

明確にスレッドセーフと書かれているわけではないらしい。デフォルトで非スレッドセーフで特に何も書かれていないから、スレッドセーフではないと考えられる、とのこと。

Collection Types — The Swift Programming Language (Swift 4.2)

OwnershipManifestoには

The most important consequence of this is that two different array elements cannot be simultaneously accessed. This will interfere with certain common idioms for working with arrays, although some cases (like concurrently modifying different slices of an array) are already quite problematic in Swift.

[OwnershipManifesto.md#subscripts] (https://github.com/apple/swift/blob/master/docs/OwnershipManifesto.md#subscripts)

arrayの異なるsubscriptにconcurrentに同時にアクセスするのはNGという言及がされている。Discordでは同時にという表現ではなく、concurrentにという表現だったがドキュメントからconcurrentにとは読み取れなかった。

そもそも自分がスレッドセーフという概念を正確に理解しているのか心配になってきた。

スレッドセーフ(Thread-safe)は、マルチスレッドプログラミングにおける概念である。あるコードがスレッドセーフであるという場合、そのコードを複数のスレッドが同時並行的に実行しても問題が発生しないことを意味する。特に、ある共有データへの複数のスレッドによるアクセスがあるとき、一度に1つのスレッドのみがその共有データにアクセスするようにして安全性を確保しなければならない。

スレッドセーフ - Wikipedia

Thread-Unsafeは任意のオブジェクトが同時に複数のスレッドによって変更を許可する場合。 Thread-Safeは任意のオブジェクトが同時に複数のスレッドで変更を許可しない場合。 Immutableなobjectsは基本スレッドセーフって考えて良さそうだなとか考えてたがAppleの公式ドキュメントがあるのでこっち読んだほうが良い。 納得したのでArrayの話に戻る。といってもスレッドセーフじゃないことを確認するコードを書くだけ。

こんな風に複数のスレッドで変更しようとすると警告が出る

import Foundation

var a = [1,2,3]
DispatchQueue.global().async {
    a.append(5)
}
DispatchQueue.global().async {
    a.append(6)
}
// Fatal error: UnsafeMutablePointer.deinitialize with negative count

別の変数に同期をとると回避出来る。

import Foundation

var a = [1, 2, 3]
var b = a
DispatchQueue.global().async {
    a.append(4)
}

DispatchQueue.global().async {
    b.append(5)
}

Arrayはスレッドセーフじゃないと認識して良さそう。

SwiftではArrayやDictionaryはCopy on Writeになっているがスレッドセーフじゃないのか?

In Swift, Array, String, and Dictionary are all value types. They behave much like a simple int value in C, acting as a unique instance of that data. You don’t need to do anything special — such as making an explicit copy — to prevent other code from modifying that data behind your back. Importantly, you can safely pass copies of values across threads without synchronization. In the spirit of improving safety, this model will help you write more predictable code in Swift. Value and Reference Types - Swift Blog - Apple Developer

同期をとることなく安全にスレッド間で値のコピーを渡すことが出来るとのこと。CoWという仕組み自体はスレッドセーフと考えてよさそう。

CoWの実装方法は@omochimetaruさんのSwift での Copy on Write の実装方法の解説 - Qiitaという記事がわかりやすかった。

WordpressのCSSにタイムスタンプを追加して古いキャッシュを読み込まないようにする

WordpressCSSにタイムスタンプを追加して古いキャッシュを読み込まないようにする

PHPの経験もなく、殆どWordPressを触らないので備忘録代わりに残す。

開発環境で問題なく表示されたCSSを本番環境にも適用した後に反映されないことがあるのを解決したかった。 ブラウザでキャッシュを削除したら変更が反映されるのだが、自分のブラウザのキャッシュを削除するのは手間がかかるし、利用者にそれを強いるのは問題外。 WordPressについて調べていると、CSSのURLのクエリパラメータにタイムスタンプを付与することで、CSSの変更の度に再読み込みが行われるようにするのが一般的なようだった。 以下のようにするらしい。

header.phpに追記する方法

【修正前】

<link rel=”stylesheet” href=<?php bloginfo (‘stylesheet_url’); ?>type=”text/css” />

【修正後】

<link rel=”stylesheet” href=<?php bloginfo(‘stylesheet_url’); echo?. filemtime( get_stylesheet_directory() ./style.css’); ?>type=”text/css” />

これで解決したというサイトが多かった。 しかし、自分が保守することになったWordpressのサイトはwp_head関数を通してCSSを読み込んでいた。

  • wp_head関数で読み込んでいるCSSにタイムスタンプを付与する
  • wp_head関数ではメインのCSSは読み込まないように変更してwp_head関数にタイムスタンプ付きのCSSを読み込む処理をフックさせるか

上記2つのどちらかで対応しようと考えた。 とりあえず解決するのが目的で、アプリの開発に戻りたかったので後者の方で進めることにした。

以下のコードをfunction.phpに追記。

// wp_head()ではstyle.cssは読み込まない
function delete_css() {
    // ブラウザのコンソールで読み込まないようにしたいcssのidを確認してwp_dequeue_styleの引数に渡す。
    wp_dequeue_style('sitename-style');
}

add_action( 'wp_enqueue_scripts', 'delete_css' );

// style.cssをタイムスタンプ付きで読み込む関数
function get_timestamp_include() {
  // style.cssのパスを取得
  $styleurl = get_bloginfo("stylesheet_url");  
  //style.cssのタイムスタンプ取得
  $styletime = filemtime( get_stylesheet_directory() . '/style.css'); 
  //タイムスタンプ付きstyle.cssを読み込む
   echo '<link rel="stylesheet" id="sitename-style-css" href="',$styleurl,'?',$styletime,'" />'; 
}
// wp_headにフックさせる。
add_action('wp_head', 'header_stylecss_include');

終わり。