Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

Arrayにおけるmap, flatMapの動作(swift3)

ググると引っかかるのか今だにswift2のドキュメントなので、swift3用のメモ

let a = [1, 2, 3]
let b = a.map { Array(repeating: $0, count: $0) }
print(b)
// [[1], [2, 2], [3, 3, 3]]

let c = [1, 2, 3]
let d = c.flatMap { Array(repeating: $0, count: $0) } 
// [1, 2, 2, 3, 3, 3]
print(d)

これはちゃんとメソッド通りの動作で安心。

enum Optional<Wrapped> の map, flatMapメソッド

swiftのソースでmapとflatMapというメソッドがよく出てくるのだが、このメソッド名からは想像できない動作をするのでメモしておく。 (メソッド名変えてくんないかな。。)

Optionalの定義を見ると次のようになっている。

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
  case none
  case some(Wrapped)
  public init(_ some: Wrapped)
  public init(nilLiteral: ())

  // mapメソッドはオプショナルではない値を返すクロージャを引数に取る。
  // 戻り値はオプショナル。
  public func map<U>(_ transform: (Wrapped) throws -> U)
      rethrows -> U?

  // flatMapメソッドはオプショナルの値を返すクロージャを引数に取る。。
  // 戻り値はオプショナル。
  public func flatMap<U>(_ transform: (Wrapped) throws -> U?)
      rethrows -> U?
}

mapの例

let a: Int? = Int("3")
let b: Int? = a.map { $0 * $0 }
print(b)
// Prints "Optional(9)"

let c: Int? = nil
let d: Int? = c.map { $0 * $0 }
print(d)
// Prints "nil"

flatMapの例

let a: Int? = Int("3")
let b: Int? = a.flatMap { x -> Int? in
  let (result, overflowed) = Int.multiplyWithOverflow(x, x)
  return overflowed ? nil : result
}
print(b)
// Prints "Optional(9)"

mapもflatMapもどちらも同じような動作をするが、クロージャの戻り値がnilになる場合がある時はflatMapを使い、それ以外はmapを使うという使い分けになるだろうか。

swift 2つの連続する矢印

swiftでこんなソースが出てきた。-> が2つ連続しているが意味がすぐには分からない。

func multi(val:Int) -> (val:Int) -> Int {
    return { a_val in return a_val * val }
}

省略されている()を書くとこうなる。

func multi(val:Int) -> ((val:Int) -> Int) {
    return { a_val in return a_val * val }
}

これは省略できない方が可読性がいいのではないだろうか。

swift ComparableIndex

swiftのComparableプロトコルはEquatableプロトコルを継承しているので、Comparableと同じことがしたいがEquatableは実装したくないという時に使えない。
その場合、Comparableに相当するプロトコルだけを自分で実装すれば良い。

public protocol ComparableIndex {
    static func LessIndex(_ lhs: Self, _ rhs: Self) -> Bool
}

extension Array where Element : ComparableIndex {
    mutating func sortWithIndex() {
        self.sort(by: { (a, b) in Element.LessIndex(a, b) })
    }
}

これは、Arrayを独自の関数LessIndexで比較してソートする関数を追加するエクステンションである。

PromiseKit4.0 (swift3)を使ってみた 2016年11月

swift3で作ったiOSアプリでサーバーへHTTPリクエストを複数回投げる場合に、 普通に作ると、正常ケース、異常ケースの処理があちこちにばらけて、ソースが汚くなります。

PromiseKitを使ったところ、ソースが短く、処理も追いやすくて素晴らしかったので、メモを残しておきます。

ここでは以下のような処理フローを想定します。

1) ブログ記事のテキストをポストする(request)
2) 生成したブログ記事のIDをjsonで返す(response)
3) 投稿したブログ記事のIDに対して画像を2つポストする(request)
画像ポスト処理は並列に行っても良い。
4) 2つの画像ポストが両方成功したらブログ生成処理正常完了として画面を更新する。
5) いずれかのHTTPの応答が失敗(404エラー等)した場合と、
応答のJSONが意図した内容でなかった場合は、ブログ生成失敗として画面を更新する。

HTTPリスエストを行う部分は、URLSession、Alamofire、APIKit なんでも良いです。

正常応答の場合に渡す情報を持つクラスTと異常応答の場合に渡すエラー情報の2つが必要となります。 クラスTの部分はリクエスト毎に別のクラスを割り当てることができます。 エラー情報はErrorを継承したクラスならなんでも良いですが、ここでは以下のようなenumとしました。 私が使っているHTTPリクエストライブラリは古くてNSErrorをHTTPResponseのエラーとして返すので、 このようにしました。

enum APIError: Error {
    case HTTPResponseError(nserror:NSError)  // 404エラー等
    case JSONParseError // レスポンスJSONのパースに失敗
}

そして、メインの処理は以下のような感じになります。

firstly {
    return Promise<[String:Any]> { fulfill, reject {
        self.sendRequest(r as URLRequest, completion: {(json: Any?, error:NSError?) in
            if let error = error {
                return reject(APIError.HTTPResponseError(nserror: error))
            } else {
                if let json = json as? [String:Any] {
                    return fulfill(json)
                }
            }
            return reject(APIError.JSONParseError)
        })
    }
}.then { json -> Promise<[Bool]> in
    if let blogid:Int = json["id"] as? Int {
        return when(fulfilled: [
            self.postImagePromise(blog:blogid, photo: 1),
            self.postImagePromise(blog:blogid, photo: 2),
        ])
    } else {
        return Promise<[Bool]>(error:APIError.JSONParseError)
    }
}.then { r in
    self.showSuccess()
}.catch { error in
    self.showFail()
}

func postImagePromise(blood:Int, photo:Int) -> Promise<Bool>
{
    このタイミングで処理が成功したことを返す場合は return Promise<Bool>(value:true) とすればよい 

    return Promise<Bool> { fulfill, reject in
        ここでreject(APIError.hoge)またはfulfill(true)をreturnする
    }
}

HTTPでよくある処理を表現するのに適していてだいぶすっきりしています。
JSON周りや、Resultの処理などまだ改良の余地があるソースですが、PromiseKitとは関係ありません。
複数のAPIを投げる場合は、使うとソースのメンテナンスが上がる、むしろ使わないと機能追加やメンテで苦労しそうだと思いました。