のほほん停留所

つぶやきのなりそこないの溜まり場

ムサニの第一線から学ぶチャンスをもらう・任せるということ

この記事は SHIROBAKO Advent Calendat 2017 5日目の記事です。

こんにちわ、のほほん(@nonchalant0303)です。去年に引き続き5日目を書きます!

はじめに

f:id:Nonchalanttan:20171123232321p:plain

この記事を書こうと思ったのは、SHIROBAKOの13, 16話の『第三飛行少女隊』のキャラデに関わる話を見返したときでした。『えくそだすっ!』でキャラデをした小笠原さんが『第三飛行少女隊』のキャラデを井口さんに任せて、井口さんは未経験ながらもキャラデを進めていくというものです。自分もちょうど仕事で新しいサービスをリリースするときに一部プラットフォームのリードエンジニアを任されて、自分もリード未経験でしたが何とかサービスをリリースできた経験を思い出したため、この主題で書いてみたくなりました。

ちなみに『ムサニの第一線』とは12話で杉江さんに協力を申し出た小笠原さんと井口さんに大した言った遠藤さんのセリフです。

チャンスをもらうということ

f:id:Nonchalanttan:20171123232544j:plain

井口さんはキャラデの話をもらったときの驚いた顔のシーンです。その後、井口さんは小笠原さん話を聞いて依頼を受けます。このシーンがすごい印象に残っています。自分も同じチームの経験豊富なエンジニアからリードを任せたいと言われときに、井口さんと同じ反応をした記憶があります。その後に言ったことも同じニュアンスでした。

「〇〇さんがいるのになぜ自分なんですか?」

自信がないながらもチャンスをもらえるならと自分もリードの仕事を受けるのですが、井口さんのように最初の基礎設計(プログラミングにおけるキャラデのようなもの?)で色々と悩んだ記憶があります。井口さんの気持ちとは違うと思いますが、自分の気持ちとしては

「仕事が任せられた」→「期待されている」→「期待に応えたい」→「うまくやりたい(失敗したくない)」

のような今思うとダメな気持ちになっていた気がします。その後、進捗を聞かれても作りかけのものを見せたくなくて「まだ考えている最中なのでもう少し待ってください」と言ってしまい、なかなか成果物が出せませんでした。少し状況は違いますが、みゃーもりが井口さんにキャラデの進捗を聞いたときにひたすらに「ごめん」と言っているシーンが印象的でした。このときの井口さんも言っていますが、自分も何が正しいか分からないという気持ちでした。今考えると早く相談したほうが全体のために良かったなと思っています。

チャンスを任せるということ

f:id:Nonchalanttan:20171123235352j:plain

小笠原さんが監督とナベPに井口さんのサポートが足りないと言ったシーンです。小笠原さんは井口さんを推薦したときに「微力ながらサポートします」と言ったように、チャンスを任せるということはその仕事を任せっぱなしにするわけではなくて、任せたという責任が発生すると思います。しかし、上でも書きましたがチャンスをもらう側は全部やらねばと思ってしまうことがある気がします。ただ、小笠原さんが井口さんに言ったように誰もが過去に失敗や苦労した経験があるということです(そう信じたい)。その事実をつい忘れてしまい完璧にこだわってしまい悩んでしまいます。そのギャップを埋めるために、小笠原さんは悩んでいる井口さんを見つつも必要なタイミングでしっかりとサポートできるのは素直にすごいなと感じました。

自分はまだ大きなチャンスを人に任せた経験がないのですが、小笠原さんのようなチャンスの任せ方をしたいと思います。

まとめ

自分なりにチャンスをもらう・任せるということについて考えてみました。井口さんと小笠原さんの話をもっと書きたいと思ったいたのですが、だいたいは自分の話になってしまいました...すみません。SHIROBAKOはリアルすぎてついつい自分のことを考えて見てしまいます。そういうSHIROBAKOが自分は大好きです。

f:id:Nonchalanttan:20171124010227j:plain f:id:Nonchalanttan:20171124010131j:plain

チャンスをもらう・任せるということで、22話の井口さんが推薦してえまっちが作監を受けるかどうか悩んでいるときに杉江さんと井口さんがアドバイスをしているシーンを見るとジーンとします。ぜひ見直して見てください!

iOSDC楽しかった + 来年の決意

9/15(Fri) - 17(Sun)に早稲田大学理工学部 西早稲田キャンパスで開催されたiOSDCに参加してきました!

Top | iOSDC Japan 2017

控えめに言っても最高のカンファレンスでした!本当にスタッフ、発表者、参加者の方ありがとうございました!とても楽しませていただいきました!自分の感じたことをまとめさせていただきます!とりとめのない文章になると思いますがチラ裏ぐらいに思っていただけると幸いです!(気になる点があればTwitterとかで教えていただけると 🙏

参加者と発表者の距離が近い

発表者のプレゼン能力が皆高くてすごく分かりやすい上、発表後の質問タイムに加えてその後のAsk the speakerといった休憩時間を使って1 on 1で質問できる仕組みがあって、大人数の中で質問するのはちょっと恥ずかしいなと思った自分にも気軽に聞けてすごく嬉しかった!(聴き逃しっちゃったかなとかこれみんな知ってることかなとか考えちゃっていつも質問をしそびれしてしまう… 😅

また会場の司会のスタッフの方の場の盛り上げ方が本当に上手で楽しかったです!😆

パックマンルール

懇親会で初めての人に話しかけるのは自分には少し苦手だなといつも思う。自分1人だけだったら振り切れてガンガン話しかけることが出来るけど、知ってる人がいればついその人と話して終わってしまうことがしばしばある。。。

オープニングで実行委員長の@tomzohさんが言っていたパックマンルール、これがとても素晴らしいものだと感じた!ざっくりすると説明すると、パックマンのように輪になって話す時は一人分入ってこられるスペースを作ろうというものです。このルールのおかげで今まで話したことのない人にも話しかけやすくてとても助かりました! 🙇

さらに素晴らしいなと感じたのは、このルールが参加者の方が意識して実施している割合がとても多かったことです!全員で作るカンファレンス最高!!!

LT最高

テンポ感が本当によく、発表者のみんなが発表のテンポをかなり意識して喋っているように感じてとても楽しいと同時にすごいなーと感心しきりだった。また発表の合間に飲み物を配ったりスタッフのホスピタリティも素敵でした!ありがとうございました、お酒美味しかったです! 🍺

来年の決意

今回、自分もプロポーザルを提出したのだが残念なことに採択されなかった。ちょっといやかなり悔しかったけど、参加して多くの発表を聞いたらこれは自分の力量不足だなと痛感した。いろいろと自分に足りない点が見つかったので来年は必ず発表するぞという強い気持ちを持てた!がんばるぞー

Layered Architecture x RxSwiftを活用した適切なエラーハンドリング | iOSDC Japan 2017


予想通りとりとめのない文章になってしまったけど、まあいいか。みなさん本当にお疲れ様でした!

ididblog

Xcodeの検索機能まとめ

f:id:Nonchalanttan:20170326174253p:plain

Xcodeの検索機能には様々な機能があります。意外と知らない人も多いかもしれないのでまとめます。

検索範囲

f:id:Nonchalanttan:20170326174812p:plain

検索ボックスの下のプロジェクト名の部分を選択すると選択範囲をファルダ/ファイル単位で設定ができます

大文字と小文字の区別

f:id:Nonchalanttan:20170326174355p:plain

Ignoring Case

大文字と小文字を区別しないで検索

Matching Case

大文字と小文字を区別して検索

一致条件

f:id:Nonchalanttan:20170326174947p:plain

Containing

部分一致

Matching

完全一致

Starting With

前方一致

Ending With

後方一致

検索対象・検索方法

f:id:Nonchalanttan:20170326175111p:plain

Text

文字列検索

Reference

f:id:Nonchalanttan:20170326180625p:plain

プロパティ名やクラス名などを対象に検索

Definitions

f:id:Nonchalanttan:20170326180636p:plain

検索範囲内で定義されたプロパティ名やクラス名などを対象に検索

Regular Expression

正規表現

Call Hierarchy

f:id:Nonchalanttan:20170326182427p:plain

メソッド名やプロパティ名の依存関係

特殊文字の検索

f:id:Nonchalanttan:20170326183807p:plain f:id:Nonchalanttan:20170326183815p:plain

検索ボックスの虫眼鏡アイコンをタップして、Insert Patternを押すと特殊文字を検索ボックスに追加できます

ファイル名の検索

f:id:Nonchalanttan:20170326184228p:plain

Command + Shift + O を押すとファイル名の検索窓を表示できます

おまけ

f:id:Nonchalanttan:20170326180843p:plain

コード内でも右クリックで検索できます。上に挙げたような様々な検索方法が呼び出せます

minHeightを保ちつつContentViewに合わせたUIScrollViewをStoryboard上で定義する

やりたいこと

デフォルトは画面の高さで、中の要素が画面の高さを越えたらスクロールするViewを設定したい

  • 中の要素が画面の高さに収まる場合 f:id:Nonchalanttan:20170325015352p:plain
  • 中の要素が画面の高さに収まらない場合 f:id:Nonchalanttan:20170325015301p:plain

Storyboardの設定

f:id:Nonchalanttan:20170325014416p:plain

  1. UIScrollViewを追加する
    • UIScrollViewの上下左右の制約を画面に合わせる f:id:Nonchalanttan:20170325014524p:plain
  2. UIScrollViewの中にUIViewを追加する
    • UIViewの上下左右の制約をUIScrollViewに合わせる。Paddingを設定したければ、ここでConstantを設定する
    • UIViewの高さ・幅の制約をUIScrollViewに合わせる。Paddingを設定している場合は逆算した値をConstantに設定する(ここではPaddingを上下左右でそれぞれ8で設定しているので、逆算した値は-16になる) f:id:Nonchalanttan:20170325014608p:plain
  3. UIViewの中にUILabelを追加する
    • UILabelの上下左右の制約をUIViewに合わせる。Paddingを設定したければ、ここでConstantを設定する f:id:Nonchalanttan:20170325014738p:plain
  4. [重要] UIViewの高さの制約のremove at build timeを有効にする!
    • これを設定することで、中の要素が画面の高さを越えたらスクロール可能になる f:id:Nonchalanttan:20170325014918p:plain
  5. [重要] UIViewにgreaterThanOrEqualToの高さの制約を設定する!
    • これを設定することで、UIViewの高さの最小値を設定することができる f:id:Nonchalanttan:20170325015101p:plain

上の手順を踏めば、コードを書かずに中の要素に合わせて伸縮するScrollViewを実現できます! 本当はUIViewの高さのEqual制約は設定しなくても動くのですが、Storyboard上でエラーが出てしまうので、それを回避するためにremove at build timeを有効にした制約を設定しています。

おまけ

remove at build timeを有効にした制約はStoryboard上では普通の制約とは異なって表現されます。

f:id:Nonchalanttan:20170325015512p:plain

お分かりいただけたでしょうか?制約を表す線が青ではなくて黒になってますね!わかりやすい! :thinking_face:

UILabelの改行まとめ

UILabelの改行の種類をいつも調べてる気がするので備忘録代わりにまとめた

let label = UILabel(frame: .zero)
label.lineBreakMode = .byWordWrapping

lineBreakModeの型であるNSLineBreakModeのドキュメントを見ると、改行方法は6種類ある

NSLineBreakMode - UIKit | Apple Developer Documentation

byWordWrapping

Wrapping occurs at word boundaries, unless the word itself doesn’t fit on a single line. See Characters and Grapheme Clusters in String Programming Guide for a discussion of issues related to determining word boundaries.

f:id:Nonchalanttan:20170317195406p:plain

単語の境界で折り返す

byCharWrapping

Wrapping occurs before the first character that doesn’t fit.

f:id:Nonchalanttan:20170317200447p:plain

文字で折り返す

byClipping

Lines are simply not drawn past the edge of the text container.

f:id:Nonchalanttan:20170317200325p:plain

折り返さず、端を超えた文字は表示されない

byTruncatingHead

The line is displayed so that the end fits in the container and the missing text at the beginning of the line is indicated by an ellipsis glyph. Although this mode works for multiline text, it is more often used for single line text.

f:id:Nonchalanttan:20170317200802p:plain

文字列末尾が表示され、行の先頭に三点リーダ

byTruncatingTail

The line is displayed so that the beginning fits in the container and the missing text at the end of the line is indicated by an ellipsis glyph. Although this mode works for multiline text, it is more often used for single line text.

f:id:Nonchalanttan:20170317200954p:plain

文字列先頭が表示され、行の末尾に三点リーダ

byTruncatingMiddle

The line is displayed so that the beginning and end fit in the container and the missing text in the middle is indicated by an ellipsis glyph. This mode is used for single-line layout; using it with multiline text truncates the text into a single line.

f:id:Nonchalanttan:20170317201055p:plain

文字列先頭と末尾が表示され、中央に三点リーダ

「Swiftデザインパターン」に出てきたパターンまとめ

「Swiftデザインパターン」をパラパラと読んだので、せっかくなので出てきたパターンをまとめてみた。基本的に概要と実装しか書いていないので、細かいところは書籍を参照していただければ。

https://www.amazon.co.jp/Swift-Programmers-SELECTION/dp/4798142492

生成に関するパターン

Prototypeパターン

プロトタイプ と呼ばれる既存のオブジェクトをコピーすることで、新しいオブジェクトを作成するデザインパターンである。これにより、オブジェクトを使用するコンポーネントからオブジェクト作成するコードを隠蔽できる。

Classだと参照渡しになるが、NSCopyingプロトコルに準拠させてcopy()を呼ぶとディープコピーが可能になる。

class Appointment: NSObject, NSCopying {
    var name: String
    var day: String
    var place: String

    func printDetails(label: String) {
        print("\(label) with \(name) on \(day) at \(place)")
    }

    func copyWithZone(zone: NSZone) -> AnyObject {
        return Appointment(name: self.name, day: self.day, place: self.place)
    }
}

var beerMeeting = Appointment(name: "Bob", day: "Mon", place: "Joe's Bar")

var workMeeting = beerMeeting.copy() as! Appointment
workMeeting.name = "Alice"
workMeeting.day = "Fri"
workMeeting.place = "Conference Rm 2"

beerMeeting.printDetails(label: "Social") // Social with Bob on Mon at Joe's Bar
workMeeting.printDetails(label: "Work")  // Work with Alice on Fri at Conference Rm 2

Singletonパターン

アプリケーションにおいて特定の型のオブジェクトを1つだけ存在させるデザインパターンである。オブジェクトを作成しても利用可能な現実のリソースが増えない、またはロギングといったアクティビティを統一したい場合に使用される。

class Logger {
    private var data = [String]()

    private init() {

    }

    private func doLog(msg: String) {
        data.append(msg)
    }

    private func doPrintLog() {
        for msg in data {
            print("Log: \(msg)")
        }
    }

    func log(msg: String) {
        Logger.sharedInstance.log(msg: msg)
    }

    func printLog() {
        Logger.sharedInstance.printLog()
    }

    private class var sharedInstance: Logger {
        get {
            struct SingletonWrapper {
                static let singleton = Logger()
            }
            return SingletonWrapper.singleton
        }
    }
}

Object Poolパターン

単一のインスタンスではなく、複数の同じ型のオブジェクトへのアクセスを可能にするデザインパターンである。まったく同じオブジェクトがいくつかあり、新しいインスタンスの生成にコストがかかるために、それらの作成を管理しなければならない場合に使用される。CocoaフレームワークのUITableViewCellの再利用などが代表例である。

class Pool<T> {
    private var data = [T]()

    init(items: [T]) {
        data.reserveCapacity(items.count)
        data.append(contentsOf: items)
    }

    func getFromPool() -> T? {
        guard !data.isEmpty else {
            return nil
        }
        return self.data.remove(at: 0)
    }
}

Factory Methodパターン

共通のプロトコルを実装する、または同じベースクラスを共有するクラスの中からどれかを選択できる場合に使用されるデザインパターンである。

class RentalCar {
    private var nameBV: String
    private var passengersBV: Int
    private var priceBV: Float

    init(name: String, passengers: Int, price: Float) {
        self.nameBV = name
        self.passengersBV = passengers
        self.priceBV = price
    }

    final var name: String {
        return nameBV
    }

    final var passengers: Int {
        return passengersBV
    }

    final var price: Float {
        return priceBV
    }

    class func createRentalCar(passengers: Int) -> RentalCar? {
        switch passengers {
        case 0...3:
            return Compact()
        case 4...8:
            return SUV()
        default:
            return nil
        }
    }
}

class Compact: RentalCar {
    init() {
        super.init(name: "VM Golf", passengers: 3, price: 20)
    }
}

class SUV: RentalCar {
    init() {
        super.init(name: "Cadillac Escalade", passengers: 8, price: 75)
    }
}

Abstract Factoryパターン

Factory Methodパターンに似ているが、呼び出し元のコンポーネントが関連するオブジェクトのファミリまたはグループを取得できるという違いがある。

class CarFactory {

    func createFloorplan() -> Floorplan {
        fatalError("Not implemented")
    }

    func createSuspension() -> Suspension {
        fatalError("Not implemented")
    }

    func createDrivetrain() -> Drivetrain {
        fatalError("Not implemented")
    }

    final class func getFactory(cars: Cars) -> CarFactory? {
        switch cars {
        case .compact:
            return CompactCarFactory()
        case .suv:
            return SUVCarFactory()
        }
    }
}

class CompactCarFactory: CarFactory {
    override func createFloorplan() -> Floorplan {
        return StandardFloorplan()
    }

    override func createSuspension() -> Suspension {
        return RoadSuspension()
    }

    override func createDrivetrain() -> Drivetrain {
        return FrontWheelDrive()
    }
}

class SUVCarFactory: CarFactory {
    override func createFloorplan() -> Floorplan {
        return LongFloorplan()
    }

    override func createSuspension() -> Suspension {
        return OffRoadSuspension()
    }

    override func createDrivetrain() -> Drivetrain {
        return AllWheelDrive()
    }
}

let factory = CarFactory.getFactory(cars: Cars.compact)

if let factory = factory {
    let car = Car(carType: Cards.compact, floor: factory.createFloorplan(), suspension: factory.createSuspension(), drive: factory.createDrivetrain())
}

Builderパターン

オブジェクトの設定をその作成から切り離すために使用されるデザインパターンである。

class Burger {
    let customerName: String
    let veggieProduct: Bool
    let patties: Int
    let pickles: Bool
    let mayo: Bool
    var ketchup: Bool
    let lettuce: Bool
    let cook: Cooked

    enum Cooked: String {
        case rare = "Rare"
        case normal = "Normal"
        case welldone = "Well Done"
    }

    init(name: String, veggie: Bool, patties: Int, pickles: Bool, mayo: Bool, ketchup: Bool, lettuce: Bool, cook: Cooked) {
        self.customerName = name
        self.veggieProduct = veggie
        self.patties = patties
        self.pickles = pickles
        self.mayo = mayo
        self.ketchup = ketchup
        self.lettuce = lettuce
        self.cook = cook
    }
}

class BurgerBuilder {
    private var veggie = false
    private var pickles = true
    private var mayo = true
    private var ketchup = true
    private var lettuce = true
    private var cooked = Burger.Cooked.normal
    private var patties = 2

    func setVeggie(choice: Bool) { self.veggie = choice }
    func setPickles(choice: Bool) { self.pickles = choice }
    func setMayo(choice: Bool) { self.mayo = choice }
    func setKetchup(choice: Bool) { self.ketchup = choice }
    func setLettuce(choice: Bool) { self.lettuce = choice }
    func setCooked(choice: Burger.Cooked) { self.cooked = choice }
    func setPatties(choice: Int) { self.patties = choice }

    func buildObject(name: String) -> Burger {
        return Burger(name: name, veggie: veggie, patties: patties, pickles: pickles, mayo: mayo, ketchup: ketchup, lettuce: lettuce, cook: cooked)
    }
}

構造に関するパターン

Adapterパターン

関連する機能を提供する2つのオブジェクトをーそれらのAPIに互換性がなかったとしてもー組み合わせることができる。

SalesDataSource, NewCoStaffMemberはそれぞれに互換性がないが、NewCoStaffMemberにEmployeeDataSourceを適用したExtensionを生やして組み合わせることを可能にしている。またラッパークラスを用意することでも組み合わせることを可能にできる。

struct Employee {
    var name: String
    var title: String
}

protocol EmployeeDataSource {
    var employees: [Employee] { get }
    func searchByName(name: String) -> [Employee]
    func searchByTitle(title: String) -> [Employee]
}

class DataSourceBase: EmployeeDataSource {
    var employees = [Employee]()

    func searchByName(name: String) -> [Employee] {
        return search(selector: { e -> Bool in
            return e.title.range(of: name) != nil
        })
    }

    func searchByTitle(title: String) -> [Employee] {
        return search(selector: { e -> Bool in
            return e.title.range(of: title) != nil
        })
    }

    private func search(selector: (Employee) -> Bool) -> [Employee] {
        var results = [Employee]()
        for e in employees {
            if selector(e) {
                results.append(e)
            }
        }
        return results
    }
}

class SalesDataSource: DataSourceBase {
    override init() {
        super.init()
        employees.append(Employee(name: "Alice", title: "VP of Sales"))
        employees.append(Employee(name: "Bob", title: "Account Exec"))
    }
}

class NewCoStaffMember {
    private var name: String
    private var role: String

    init(name: String, role: String) {
        self.name = name
        self.role = role
    }

    func getName() -> String {
        return name
    }

    func getJob() -> String {
        return role
    }
}

class NewCoDirectory {
    private var staff: [String: NewCoStaffMember]

    init() {
        staff = [
            "Hans": NewCoStaffMember(name: "Hans", role: "Corp Counsel"),
            "Greta": NewCoStaffMember(name: "Greta", role: "VP, Legal")
        ]
    }

    func getStaff() -> [String: NewCoStaffMember] {
        return staff
    }
}

extension NewCoDirectory: EmployeeDataSource {
    var employees: [Employee] {
        return getStaff().values.map { sv -> Employee in
            return Employee(name: sv.getName(), title: sv.getJob())
        }
    }

    func searchByName(name: String) -> [Employee] {
        return createEmployees() {
            return $0.getName().range(of: name) != nil
        }
    }

    func searchByTitle(title: String) -> [Employee] {
        return createEmployees() {
            return $0.getJob().range(of: title) != nil
        }
    }

    private func createEmployees(filter filterClosure: @escaping ((NewCoStaffMember) -> Bool)) -> [Employee] {
        return getStaff().values
            .filter { filterClosure($0) }
            .map { Employee(name: $0.getName(), title: $0.getJob())}
    }
}

Bridgeパターン

Adapterパターンと同じように見えるが、Adapterパターンとの最大の相違点は意図であり、実装ではない。

主に「クラス階層の爆発」などの問題を解決する。

protocol ClearMessageChannel {
    func send(message: String)
}

protocol SecureMessageChannel {
    func sendEncryptedMessage(encryptedText: String)
}

class Communicator {
    private let clearChannel: ClearMessageChannel
    private let secureChannel: SecureMessageChannel

    init(clearChannel: ClearMessageChannel,
         secureChannel: SecureMessageChannel) {
        self.clearChannel = clearChannel
        self.secureChannel = secureChannel
    }

    func sendCleartextMessage(message: String) {
        self.clearChannel.send(message: message)
    }

    func sendSecureMessage(message: String) {
        self.secureChannel.sendEncryptedMessage(encryptedText: message)
    }
}

protocol Message {
    init(message: String)
    func prepareMessage()
    var contentToSend: String { get }
}

class ClearMessage: Message {
    private var message: String

    required init(message: String) {
        self.message = message
    }
    func prepareMessage() {
         // do nothing.
    }

    var contentToSend: String {
        return message
    }
}

class EncryptedMessage: Message {
    private var clearText: String
    private var cipherText: String?

    required init(message: String) {
        self.clearText = message
    }

    func prepareMessage() {
        cipherText = String(clearText.characters.reversed())
    }

    var contentToSend: String {
        return cipherText!
    }
}

protocol Channel {
    func sendMessage(msg: Message)
}

class LandlineChannel: Channel {
    func sendMessage(msg: Message) {
        print("Landline: \(msg.contentToSend)")
    }
}

class WirelessChannel: Channel {
    func sendMessage(msg: Message) {
        print("Wireless: \(msg.contentToSend)")
    }
}

class CommunicatorBridge: ClearMessageChannel, SecureMessageChannel {
    private var channel: Channel

    init(channel: Channel) {
        self.channel = channel
    }

    func send(message: String) {
        let msg = ClearMessage(message: message)
        sendMessage(msg: msg)
    }

    func sendEncryptedMessage(encryptedText: String) {
        let msg = EncryptedMessage(message: encryptedText)
        sendMessage(msg: msg)
    }

    private func sendMessage(msg: Message) {
        msg.prepareMessage()
        channel.sendMessage(msg: msg)
    }
}

var bridge = CommunicatorBridge(channel: LandlineChannel())
var comms = Communicator(clearChannel: bridge, secureChannel: bridge)

comms.sendCleartextMessage(message: "Hello!")
comms.sendSecureMessage(message: "This is a secret")

Decoratorパターン

オブジェクトを作成するために使用されるクラスを変更せずに、個々のサブジェクトの振る舞いを変更できるようにするパターンである。

通常は、クラスを直接変更するほうが簡単なのだが、サードパーティーのライブラリなどの理由で変更が困難な場合に有効である。

class Purchase {
    private let product: String
    private let price: Float

    init(product: String, price: Float) {
        self.product = product
        self.price = price
    }

    var description: String {
        return product
    }

    var totalPrice: Float {
        return price
    }
}

class BasePruchaseDecorator: Purchase {
    init(purchase: Purchase) {
        super.init(product: purchase.description, price: purchase.totalPrice)
    }
}

class PurchaseWithGiftWrap: BasePruchaseDecorator {
    override var description: String {
        return "\(super.description) + giftwrap"
    }
    override var totalPrice: Float {
        return super.totalPrice + 2
    }
}

class PurchaseWithRibbon: BasePruchaseDecorator {
    override var description: String {
        return "\(super.description) + ribbon"
    }
    override var totalPrice: Float {
        return super.totalPrice + 1
    }
}

let purchase = PurchaseWithRibbon(purchase:
                    PurchaseWithGiftWrap(purchase:
                        Purchase(product: "Sunglasses", price: 25)))

print(purchase.description) // Sunglasses + giftwrap + ribbon
print(purchase.totalPrice)  // 28.0

Compositeパターン

個々のオブジェクトとオブジェクトのコレクションからなるツリーを一貫した方法で扱えるようにするパターンである。

protocol CarPart {
    var name: String { get }
    var price: Float { get }
}

class Part: CarPart {
    let name: String
    let price: Float

    init(name: String, price: Float) {
        self.name = name
        self.price = price
    }
}

class CompositePart: CarPart {
    let name: String
    let parts: [CarPart]

    init(name: String, parts: [CarPart]) {
        self.name = name
        self.parts = parts
    }

    var price: Float {
        return parts.reduce(0) { $0 + $1.price }
    }
}

Facadeパターン

共通のタスクを実行するための複雑なAPIの使用を単純にするパターンである。

class TreasureMap {
    enum Treasures {
        case galleon
        case buried_gold
        case sunken_jewels
    }

    struct MapLocation {
        let gridLetter: Character
        let gridNumber: UInt
    }

    func findTreasure(type: Treasures) -> MapLocation {
        switch type {
        case .galleon:
            return MapLocation(gridLetter: "D", gridNumber: 6)
        case .buried_gold:
            return MapLocation(gridLetter: "C", gridNumber: 2)
        case .sunken_jewels:
            return MapLocation(gridLetter: "F", gridNumber: 12)
        }
    }
}

class PirateShip {
    struct ShipLocation {
        let NorthSourh: Int
        let EastWest: Int
    }

    var currentPosition: ShipLocation
    var movementQueue = DispatchQueue(label: "shipQ")

    init() {
        currentPosition = ShipLocation(NorthSourh: 5, EastWest: 5)
    }

    func moveToLocation(location: ShipLocation, callback: @escaping (ShipLocation) -> Void) {
        movementQueue.async {
            self.currentPosition = location
            callback(self.currentPosition)
        }
    }
}

class PirateCrew {
    let workQueue = DispatchQueue(label: "crewWorkQ")

    enum Actions {
        case attackShip
        case digForGold
        case diveForJewels
    }

    func performAction(action: Actions, callback: @escaping (Int) -> Void) {
        workQueue.async {
            var prizeValue = 0
            switch action {
            case .attackShip:
                prizeValue = 10000
            case .digForGold:
                prizeValue = 5000
            case .diveForJewels:
                prizeValue = 1000
            }
            callback(prizeValue)
        }
    }
}

enum TreasureTypes {
    case ship
    case buried
    case sunken
}

class PirateFacade {
    private let map = TreasureMap()
    private let ship = PirateShip()
    private let crew = PirateCrew()

    func getTreasure(type: TreasureTypes) -> Int? {
        var prizeAmount: Int?

        var treasureMapType: TreasureMap.Treasures
        var crewWorkType: PirateCrew.Actions

        switch type {
        case .ship:
            treasureMapType = .galleon
            crewWorkType = .attackShip
        case .buried:
            treasureMapType = .buried_gold
            crewWorkType = .digForGold
        case .sunken:
            treasureMapType = .sunken_jewels
            crewWorkType = .diveForJewels
        }

        let treasureLocation = map.findTreasure(type: treasureMapType)

        let sequence: [Character] = ["A", "B", "C", "D", "E", "F", "G"]
        let eastWestPos = sequence.enumerated().filter({ $0.1 == treasureLocation.gridLetter}).map { $0.0 }.first!
        let shipTarget = PirateShip.ShipLocation(NorthSourh: Int(treasureLocation.gridNumber), EastWest: eastWestPos)

        let semaphore = DispatchSemaphore(value: 0)

        ship.moveToLocation(location: shipTarget) { location in
            self.crew.performAction(action: crewWorkType, callback: { prize in
                prizeAmount = prize
                semaphore.signal()
            })
        }

        semaphore.wait(timeout: .distantFuture)
        return prizeAmount
    }
}

Flyweightパターン

複数の呼び出し元のコンポーネントに同じデータオブジェクトを共有させるパターンである。

class Owner: NSObject, NSCopying {
    var name: String
    var city: String

    init(name: String, city: String) {
        self.name = name
        self.city = city
    }

    func copy(with zone: NSZone? = nil) -> Any {
        return Owner(name: self.name, city: self.city)
    }
}

class FlyweightFactory {
    class func createFlyweight() -> Flyweigt {
        return Flyweigt(owner: ownerSingleton)
    }

    private class var ownerSingleton: Owner {
        get {
            struct SingletonWrapper {
                static let singleton = Owner(name: "Anonymous", city: "Anywhere")
            }
            return SingletonWrapper.singleton
        }
    }
}

class Flyweigt {
    private let extrinsicOwner: Owner
    private var intrinsicOwner: Owner?

    init(owner: Owner) {
        self.extrinsicOwner = owner
    }

    var name: String {
        get {
            return intrinsicOwner?.name ?? extrinsicOwner.name
        }
        set (value) {
            decoupleFromExtrinsic()
            intrinsicOwner?.name = value
        }
    }

    var city: String {
        get {
            return intrinsicOwner?.city ?? extrinsicOwner.city
        }
        set (value) {
            decoupleFromExtrinsic()
            intrinsicOwner?.city = value
        }
    }

    private func decoupleFromExtrinsic() {
        if intrinsicOwner == nil {
            intrinsicOwner = extrinsicOwner.copy(with: nil) as? Owner
        }
    }
}

Proxyパターン

オブジェクトを別オブジェクトまたはリソースへのインターフェイスとして機能させる必要がある場合に使用される。

protocol HttpHeaderRequest {
    func getHeader(url: String, header: String) -> String?
}

class HttpHeaderRequestProxy: HttpHeaderRequest {
    private let semaphore = DispatchSemaphore(value: 0)

    func getHeader(url urlString: String, header: String) -> String? {
        var headerValue: String?

        let url = URL(string: urlString)
        let request = URLRequest(url: url!)
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let httpResponse = response as? HTTPURLResponse {
                headerValue = httpResponse.allHeaderFields[header] as? String
            }
            self.semaphore.signal()
        }.resume()
        semaphore.wait(timeout: .distantFuture)
        return headerValue
    }
}

let url = "http://www.apress.com"
let headers = ["Content-Length", "Content-Encoding"]

let proxy = HttpHeaderRequestProxy()

for header in headers {
    if let val = proxy.getHeader(url: url, header: header) {
        print("\(header): \(val)")
    }
}

FileHandle.standardInput.availableData

振る舞いに関するパターン

Chain of Responsibilityパターン

呼び出し元のコンポーネントからのリクエストを処理することが可能な複数のオブジェクトを数珠つなぎにするパターンである。

struct Message {
    let from: String
    let to: String
    let subject: String
}

class Transmitter {
    var nextLink: Transmitter?

    required init() {}

    func sendMessage(message: Message) {
        guard let nextLink = nextLink else {
            print("End of chain reached. Message not send.")
            return
        }
        nextLink.sendMessage(message: message)
    }

    class func createChain() -> Transmitter? {
        let transmitterClasses: [Transmitter.Type] = [
            PriorityTransmitter.self,
            LocalTransmitter.self,
            RemoteTransmitter.self
        ]

        var link: Transmitter?

        for tClass in transmitterClasses.reversed() {
            let existingLink = link
            link = tClass.init()
            link?.nextLink = existingLink
        }

        return link
    }

    fileprivate class func matchEmailSuffix(message: Message) -> Bool {
        guard let index = message.from.range(of: "@") else {
            return false
        }
        return message.to.hasSuffix(message.from.substring(with: Range(uncheckedBounds: (lower: index.lowerBound, upper: message.from.endIndex))))
    }
}

class LocalTransmitter: Transmitter {
    override func sendMessage(message: Message) {
        if Transmitter.matchEmailSuffix(message: message) {
            print("Message to \(message.to) sent locally")
        } else {
            super.sendMessage(message: message)
        }
    }
}

class RemoteTransmitter: Transmitter {
    override func sendMessage(message: Message) {
        if !Transmitter.matchEmailSuffix(message: message) {
            print("Message to \(message.to) sent remotely")
        } else {
            super.sendMessage(message: message)
        }
    }
}

class PriorityTransmitter: Transmitter {
    override func sendMessage(message: Message) {
        if message.subject.hasPrefix("Priority") {
            print("Message to \(message.to) sent as priority")
        } else {
            super.sendMessage(message: message)
        }
    }
}

let messages = [
    Message(from: "bob@example.com", to: "joe@example.com", subject: "Free for lunch?"),
    Message(from: "joe@example.com", to: "alice@acme.com", subject: "Nex Contracts"),
    Message(from: "pete@example.com", to: "all@example.com", subject: "Priority: All-Hands Meeting")
]

if let chain = Transmitter.createChain() {
    for msg in messages {
        chain.sendMessage(message: msg)
    }
}

/*
 Message to joe@example.com sent locally
 Message to alice@acme.com sent remotely
 Message to all@example.com sent as priority
 */

Commandパターン

オブジェクトのメソッドを呼び出す方法をカプセル化し、そのメソッドを別のタイミングで呼び出せるようにするか、異なるコンポーネントで呼び出せるようにするために使用されるパターンである。

class Calculator {
    private(set) var total = 0
    private var hisotory = [Command]()

    func add(_ amount: Int) {
        addUndoCommand(method: Calculator.subtract, amount: amount)
        total += amount
    }

    func subtract(_ amount: Int) {
        addUndoCommand(method: Calculator.add, amount: amount)
        total -= amount
    }

    func multiply(_ amount: Int) {
        addUndoCommand(method: Calculator.divide, amount: amount)
        total *= amount
    }

    func divide(_ amount: Int) {
        addUndoCommand(method: Calculator.multiply, amount: amount)
        total /= amount
    }

    private func addUndoCommand(method: @escaping (Calculator) -> (Int) -> Void, amount: Int) {
        hisotory.append(GenericsCommand.createCommand(receiver: self, instructions: { calc in
            method(calc)(amount)
        }))
    }

    func undo() {
        guard !hisotory.isEmpty else {
            return
        }

        hisotory.removeLast().execute()
        hisotory.removeLast()
    }
}

protocol Command {
    func execute()
}

class GenericsCommand<T>: Command {
    private var receiver: T
    private var instructions: (T) -> Void

    init(receiver: T, instructions: @escaping (T) -> Void) {
        self.receiver = receiver
        self.instructions = instructions
    }

    func execute() {
        instructions(receiver)
    }

    class func createCommand(receiver: T, instructions: @escaping (T) -> Void) -> Command {
        return GenericsCommand(receiver: receiver, instructions: instructions)
    }
}

let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)

print("Total: \(calc.total)") // Total: 38

for _ in 0..<3 {
    calc.undo()
    print("Undo called. Total: \(calc.total)")
}

// Undo called: Total: 40
// Undo called: Total: 10
// Undo called: Total: 0

Mediatorパターン

オブジェクトのグループ同士のやり取りを単純かつ合理的なものにするために使用される。

struct Position {
    var distanceFromRunway: Int
    var height: Int
}

protocol Peer {
    var name: String { get }
    func otherPlaneDidChangePosition(position: Position) -> Bool
}

protocol Mediator {
    func registerPeer(peer: Peer)
    func unregisterPeer(peer: Peer)
    func changePosition(peer: Peer, pos: Position) -> Bool
}

class AirplaneMediator: Mediator {
    private var peers: [String: Peer] = [:]

    func registerPeer(peer: Peer) {
        peers[peer.name] = peer
    }

    func unregisterPeer(peer: Peer) {
        peers.removeValue(forKey: peer.name)
    }

    func changePosition(peer: Peer, pos: Position) -> Bool {
        for storedPeer in peers.values {
            if peer.name != storedPeer.name && storedPeer.otherPlaneDidChangePosition(position: pos) {
                return true
            }
        }
        return false
    }
}

class Airplane: Peer {
    var name: String
    var currentPosition: Position
    var mediator: Mediator

    init(name: String, initiailPos: Position, mediator: Mediator) {
        self.name = name
        self.currentPosition = initiailPos
        self.mediator = mediator
        mediator.registerPeer(peer: self)
    }

    func otherPlaneDidChangePosition(position: Position) -> Bool {
        return position.distanceFromRunway == self.currentPosition.distanceFromRunway &&
            abs(position.height - self.currentPosition.height) < 1000
    }

    func changePosition(newPosition: Position) {
        currentPosition = newPosition
        guard !mediator.changePosition(peer: self, pos: self.currentPosition) else {
            print("\(name): Too close! Abort!")
            return
        }
        print("\(name): Position changed")
    }

    func land() {
        currentPosition = Position(distanceFromRunway: 0, height: 0)
        mediator.unregisterPeer(peer: self)
        print("\(name): Landed")
    }
}

let mediator = AirplaneMediator()

let british = Airplane(name: "BA706", initiailPos: Position(distanceFromRunway: 11, height: 21000), mediator: mediator)
let american = Airplane(name: "AA101", initiailPos: Position(distanceFromRunway: 12, height: 22000), mediator: mediator)

british.changePosition(newPosition: Position(distanceFromRunway: 8, height: 10000))
british.changePosition(newPosition: Position(distanceFromRunway: 2, height: 5000))
british.changePosition(newPosition: Position(distanceFromRunway: 1, height: 1000))

let cathay = Airplane(name: "CX200", initiailPos: Position(distanceFromRunway: 13, height: 22000), mediator: mediator)

british.land()

cathay.changePosition(newPosition: Position(distanceFromRunway: 12, height: 22000))


// BA706: Position changed
// BA706: Position changed
// BA706: Position changed
// BA706: Landed
// CX200: Too close! Abort!

Observerパターン

Observerパターンは、オブジェクトでの変化に関する通知の受け取りを別のオブジェクトが登録できるようにするパターン

protocol Observer: class {
    func notifiy(user: String, success: Bool)
}

protocol Subject {
    func addObservers(observers: [Observer])
    func removeObserver(observer: Observer)
}

class SubjectBase: Subject {
    private var observers = [Observer]()
    private var collectionQueue = DispatchQueue(label: "colQ", attributes: .concurrent)

    func addObservers(observers: [Observer]) {
        collectionQueue.async {
            for newOb in observers {
               self.observers.append(newOb)
            }
        }
    }

    func removeObserver(observer: Observer) {
        collectionQueue.async {
            self.observers = self.observers.filter({ $0 !== observer })
        }
    }

    func sendNotification(user: String, success: Bool) {
        collectionQueue.sync {
            for ob in self.observers {
                ob.notifiy(user: user, success: success)
            }
        }
    }
}

class AuthenticationManager: SubjectBase {
    func authenticate(user: String, pass: String) -> Bool {
        var result = false
        if user == "bob" && pass == "secret" {
            result = true
            print("User \(user) is authenticated")
        } else {
            print("Failed authentication attempt")
        }
        sendNotification(user: user, success: result)
        return result
    }
}

class ActivityLog: Observer {
    func notifiy(user: String, success: Bool) {
        print("Auth request for \(user). Success: \(success)")
    }

    func logActivity(activity: String) {
        print("Log: \(activity)")
    }
}

class FileCache: Observer {
    func notifiy(user: String, success: Bool) {
        if success {
            loadFiles(user: user)
        }
    }

    func loadFiles(user: String) {
        print("Load files for \(user)")
    }
}

class AttackMonitor: Observer {
    func notifiy(user: String, success: Bool) {
        monitorSubpiciousActivity = !success
    }

    var monitorSubpiciousActivity: Bool = false {
        didSet {
            print("Monitoring for attack: \(monitorSubpiciousActivity)")
        }
    }
}

let monitor = AttackMonitor()
let log = ActivityLog()
let cache = FileCache()

let authManager = AuthenticationManager()
authManager.addObservers(observers: [log, cache, monitor])

authManager.authenticate(user: "bob", pass: "secret")
print("-----")
authManager.authenticate(user: "joe", pass: "shhh")

// User bob is authenticated
// Auth request for bob. Success: true
// Load files for bob
// Monitoring for attack: false
// -----
// Failed authentication attempt
// Auth request for joe. Success: false
// Monitoring for attack: true

Mementoパターン

オブジェクトの完全な状態をメメントに取り込み、あとからオブジェクトをリセットをするために使用できるようにするパターンである。

protocol Memento {}

protocol Originator {
    func createMemento() -> Memento
    func applyMemento(memento: Memento)
}

class LedgerEntry {
    let id: Int
    let counterParty: String
    let amount: Float

    init(id: Int, counterParty: String, amount: Float) {
        self.id = id
        self.counterParty = counterParty
        self.amount = amount
    }
}

class LedgerMemento: Memento {
    private var entries: [LedgerEntry] = []
    private let total: Float
    private let nextId: Int

    init(ledger: Ledger) {
        self.entries = ledger.entries.values.map { $0 }
        self.total = ledger.total
        self.nextId = ledger.nextId
    }

    func apply(ledger: Ledger) {
        ledger.total = self.total
        ledger.nextId = self.nextId
        ledger.entries.removeAll(keepingCapacity: true)
        for entry in self.entries {
            ledger.entries[entry.id] = entry
        }
    }
}

class Ledger: Originator {
    fileprivate var entries: [Int: LedgerEntry] = [:]
    fileprivate var nextId = 1
    var total: Float = 0

    func addEntry(counterParty: String, amount: Float) {
        let entry = LedgerEntry(id: nextId, counterParty: counterParty, amount: amount)
        nextId += 1
        entries[entry.id] = entry
        total += amount
    }

    func createMemento() -> Memento {
        return LedgerMemento(ledger: self)
    }

    func applyMemento(memento: Memento) {
        if let m = memento as? LedgerMemento {
            m.apply(ledger: self)
        }
    }

    func printEntries() {
        for id in entries.keys.sorted(by: { $0 < $1 }) {
            if let entry = entries[id] {
                print("#\(id): \(entry.counterParty) $\(entry.amount)")
            }
        }
        print("Total: $\(total)")
        print("----")
    }
}

let ledger = Ledger()

ledger.addEntry(counterParty: "Bob", amount: 100.43)
ledger.addEntry(counterParty: "Joe", amount: 200.20)

let memento = ledger.createMemento()

ledger.addEntry(counterParty: "Alice", amount: 500)
ledger.addEntry(counterParty: "Tony", amount: 20)

ledger.printEntries()

ledger.applyMemento(memento: memento)

ledger.printEntries()

// #1: Bob $100.43
// #2: Joe $200.2
// #3: Alice $500.0
// #4: Tony $20.0
// Total: $820.63
// ----
// #1: Bob $100.43
// #2: Joe $200.2
// Total: $300.63
// ----

Strategyパターン

明確に定義されたプロトコルに準拠するアルゴリズムオブジェクトを利用して、修正せずに拡張することが可能なクラスを作成するために使用されるパターンである。

protocol Strategy {
    func execute(values: [Int]) -> Int
}

class SumStrategy: Strategy {
    func execute(values: [Int]) -> Int {
        return values.reduce(0) { $0 + $1 }
    }
}

class MultiplyStrategy: Strategy {
    func execute(values: [Int]) -> Int {
        return values.reduce(1) { $0 * $1 }
    }
}

final class Sequence {
    private var numbers: [Int]

    init(_ numbers: Int...) {
        self.numbers = numbers
    }

    func addNumber(_ value: Int) {
        self.numbers.append(value)
    }

    func compute(strategy: Strategy) -> Int {
        return strategy.execute(values: self.numbers)
    }
}

let sequence = Sequence(1, 2, 3, 4)
sequence.addNumber(10)
sequence.addNumber(20)

let sumStrategy = SumStrategy()
let multiplyStrategy = MultiplyStrategy()

print("Sum: \(sequence.compute(strategy: sumStrategy))") // Sum: 40
print("Multiply: \(sequence.compute(strategy: multiplyStrategy))") // Multiply: 4800

Visitorパターン

異種のオブジェクトからなるコレクションを操作するための新しいアルゴリズムを定義できるようにするパターンである。

protocol Shape {
    func accept(visitor: Visitor)
}

protocol Visitor {
    func visit(shape: Circle)
    func visit(shape: Square)
    func visit(shape: Rectangle)
}

class AreaVisitor: Visitor {
    var totalArea: Float = 0

    func visit(shape: Circle) {
        totalArea += (3.14 * powf(shape.radius, 2))
    }

    func visit(shape: Square) {
        totalArea += powf(shape.length, 2)
    }

    func visit(shape: Rectangle) {
        totalArea += (shape.xLen * shape.yLen)
    }
}

class Circle: Shape {
    let radius: Float

    init(radius: Float) {
        self.radius = radius
    }

    func accept(visitor: Visitor) {
        visitor.visit(shape: self)
    }
}

class Square: Shape {
    let length: Float

    init(length: Float) {
        self.length = length
    }

    func accept(visitor: Visitor) {
        visitor.visit(shape: self)
    }
}

class Rectangle: Shape {
    let xLen: Float
    let yLen: Float

    init(x: Float, y: Float) {
        self.xLen = x
        self.yLen = y
    }

    func accept(visitor: Visitor) {
        visitor.visit(shape: self)
    }
}

class ShapeCollection {
    let shapes: [Shape]

    init() {
        shapes = [
            Circle(radius: 2.5),
            Square(length: 4),
            Rectangle(x: 10, y: 2)
        ]
    }
    func accept(visitor: Visitor) {
        for shape in shapes {
            shape.accept(visitor: visitor)
        }
    }
}

let shapes = ShapeCollection()
let areaVisitor = AreaVisitor()
shapes.accept(visitor: areaVisitor)
print("Area: \(areaVisitor.totalArea)") // Area: 55.625

Template Methodパターン

アルゴリズムの特定のステップをサードパーティによって提供される実装に置き換えることができるパターンである。

struct Donor {
    let title: String
    let firstName: String
    let familyName: String
    let lastDonation: Float
}

class DonorDatabase {
    private var donors: [Donor]
    var filter: (([Donor]) -> [Donor])?
    var generate: (([Donor]) -> [String])?

    init() {
        donors = [
            Donor(title: "MS", firstName: "Anne", familyName: "Jones", lastDonation: 0),
            Donor(title: "Mr", firstName: "Bob", familyName: "Smith", lastDonation: 100),
            Donor(title: "Dr", firstName: "Alice", familyName: "Doe", lastDonation: 200),
            Donor(title: "Prof", firstName: "Joe", familyName: "Davis", lastDonation: 320)
        ]
    }

    func generate(maxNumber: Int) -> [String] {
        var targetDonors = filter?(donors) ?? donors.filter { $0.lastDonation > 0 }
        targetDonors.sort { $0.0.lastDonation > $0.1.lastDonation }
        if targetDonors.count > maxNumber {
            targetDonors = Array(targetDonors[0..<maxNumber])
        }
        return generate?(targetDonors) ?? targetDonors.map { donor in
            return "Dear \(donor.title). \(donor.familyName)"
        }
    }
}

let donorDb = DonorDatabase()

let galaInvitations = donorDb.generate(maxNumber: 2)
for invite in galaInvitations {
    print(invite)
}

donorDb.filter = { $0.filter { $0.lastDonation == 0 } }
donorDb.generate = { $0.map { "Hi \($0.firstName)" } }

let newDonors = donorDb.generate(maxNumber: Int.max)
for invite in newDonors {
    print(invite)
}

// Dear Prof. Davis
// Dear Dr. Doe
// Hi Anne

読書の記録をつけていて良かったこと

2013年から初めて4年の間読んだ本の記録をつけてて、色々と良かったことがあったのでちょっと書く

どうやって記録してるか

良かったことの前にまずはどうやって記録してるか。記録が出来ればなんでもいいと思うけど、自分は読書メーターというサービスを使っている

elk.bookmeter.com

簡単に呼んだ本を記録できて感想も書ける、また簡単なSNSにもなっていてお気に入りに追加した他のユーザーの読んだ本や感想なども見ることが出来て良い

f:id:Nonchalanttan:20170106040823p:plain

記録をつけてて良かったこと

記録をつけるとはデータを蓄積することなのでその人間の傾向などが見えてきて、それはそれで面白いのだがどっちかというと副作用的な良かったことについて書く。以下、良かったこと箇条書き

  1. 自分の好みの変遷を知れる
  2. 自分が普段読まないジャンルの本を知れる
  3. 読んだ本の他の人の意見が知れる
  4. 本がきっかけになって話題になる

1. 自分の好みの変遷を知れる

これは記録をつけているので過去のデータを遡れば当たり前なので省略

2. 自分が普段読まないジャンルの本を知れる

自分はミステリばっかり読んでて他のジャンルは全然わからないが、知り合いのSFマニアが読んだ本が読書メーターのTLに流れてくるので、ちょっと興味が沸いてくる。実際に会ったときにオススメ本を教えあって感想を話すのもかなり盛り上がれて良い。どうしてもAmazonだと同じジャンルのものしか出てこないので、便利なのだが他のジャンルには手が出しづらいので他人は偉大!

3. 読んだ本の他の人の意見が知れる

ミステリばっかり読んでるので、最後まで読んだときに「これ、みんな納得してるの!?自分の頭が悪いだけ!?」と思ってよく感想とかを探すのに検索していた。ただ、そういうのも読書メーター上で完結できるので良い

4. 本がきっかけになって話題になる

読破した本をTwitterに連動させて流すことができるので、前に知り合いから「これ面白いよねー」とリプがきてそれが話題になって会話が弾んで楽しかった。個人的にその知り合いの方はその本を読むタイプだと思っていなかったので、かなりビックリした印象。ついでにオススメ本も教えてもらったのでラッキー!

まとめ

他のジャンルに手をだすキッカケになってほしいので自分も本を勧めておく。どちらもかなり読みやすいミステリ物なのでオススメ

ロートレック荘事件 空飛ぶ馬

読書は基本的に1人でするものだと思っているが、記録をつけるのとそれを公開すればそれが話題になって他人との交流が深まりかなり良かったなと感じた。ハッピー読書ライフ!!

今まで読んだ本をひたすら読書メーターに登録するのはかなりツラかった、なぜ今後読む本だけにしなかったのか...