higan96技術メモ

https://github.com/higan96

SwiftのGenericsについてのメモ(その1)

概要

SwiftのGenericsを勉強しているのでそのメモ。個人的に難しくて使いこなせていない機能です。 とりあえずThe Swift Programming LanguageのGenericsのページを読んでいる。

Genericsとは

Generic code enables you to write flexible, reusable functions and types that can 
work with any type, subject to requirements that you define.
You can write code that avoids duplication and expresses its intent in a clear, 
abstracted manner.
  • Google翻訳ジェネリックコードを使用すると、定義した要件に応じて、任意の型で機能する柔軟で再利用可能な関数と型を記述できます。重複を避け、その意図を明確で抽象的な方法で表現するコードを書くことができます。」とのこと。
  • そういうやつ、ぐらいの認識です。
  • ArrayやDictionaryもGenericsだよ!気づいてなかったかな??
  • Genericsで出来ることって広範だけど、それぞれできることがラベル付けされてないから理解が進まない印象があったりする
  • よくつかう<T>は「placeholder type name」というらしい
    • 具体的な型が何であるかはここでは問わない
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
  • Tの型は問わないが、2つの引数の型が同じであることを求めている
  • 引数に渡された値の型によってTの型が自動的に決まる

Type Parameters

Type Parametersとは

  • 先程のGenericsの手法はType Parametersと呼ばれる
    • 指定したplaceholderを引数の型、返り値、関数内で使う変数や定数などの型の指定(type annotation)、で使うことができる
  • カンマで区切れば複数のType Parameterを指定できる
  • extensionで拡張するときに特にType Parameterは指定しなくてもいい。

Type Parametersの名前つけ

  • 使われている場所とtype parameterとの間に関わりがあるならKey、Valueとか(Dictionary)Elementとか(Array)付けよう
  • とくに無ければT, U, Vがよく使われる

Type Constraints(型制約)

  • ParamaterTypeが特定のクラスを継承しているか、特定のprotocolに適合していることを制限することができる
    • DictionaryのkeyはHashableプロトコルに適合している必要がある(そういう制約をつけている)

Go TourのExercise

GoのTourのExerciseの回答

go-tour-jp.appspot.com

プログラミング言語 Goには、初学者が言語仕様を学べるTourというコンテンツがあります。

その中に練習問題(Exercise)があったので、自分の回答を学習メモとして記していきます。

Exercise: Slices

A Tour of Go

これちょっと説明文が分かりづらい気がしました。Pixメソッドの仕様のポイントを羅列します。

  • Picメソッドの引数で受け取るdx×dyのサイズの2次元配列を返す
  • 配列の中身は各座標のxyをもとにした整数値(uint8)を渡す
    • 別にxyをもとにしなくても整数値が0〜255の範囲であれば画像は生成される
      • 0を渡せば青一色の画像になる
    • 中身の整数値によって青色の濃さが変わる
      • 0に近いほど色が濃くなり、255に近いほど色が薄くなる
    • 例示されている整数値の計算パターン は使わなくてもよい
      • (x+y)/2x*yx^yが面白い画像ができるというだけ

コード

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
    // 2次元配列の初期化
    pic := make([][]uint8, dy)
    for y := 0; y < dy ;y++ {
        // 横列の配列を初期化
        row := make([]uint8, dx)
        for x := 0; x < dx ;x++ {
            // 色を表現する整数値を代入
            row[x] = uint8((x+y)/2)
        }
        // 横列の配列を保存
        pic[y] = row
    }
    return pic
}

func main() {
    pic.Show(Pic)
}

出力

f:id:higan_n:20190629144853p:plain

Exercise: Maps

A Tour of Go

コード

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    // 文字列をスライスに分割
    words := strings.Fields(s)
    // Mapsの初期化
    m := make(map[string]int)
    // intのゼロ値は0なので、同じ単語(同じKey)のたびに1を足す
    // m[value]++でも可
    for _, value := range words {
        m[value] = m[value] + 1
    }
    return m
}

func main() {
    wc.Test(WordCount)
}

出力

PASS
 f("I am learning Go!") = 
  map[string]int{"Go!":1, "I":1, "am":1, "learning":1}
PASS
 f("The quick brown fox jumped over the lazy dog.") = 
  map[string]int{"The":1, "brown":1, "dog.":1, "fox":1, "jumped":1, "lazy":1, "over":1, "quick":1, "the":1}
PASS
 f("I ate a donut. Then I ate another donut.") = 
  map[string]int{"I":2, "Then":1, "a":1, "another":1, "ate":2, "donut.":2}
PASS
 f("A man a plan a canal panama.") = 
  map[string]int{"A":1, "a":2, "canal":1, "man":1, "panama.":1, "plan":1}

Exercise: Fibonacci closure

A Tour of Go

コード

package main

import "fmt"

func fibonacci() func() int {
    // 保存されるスライスの`s`
    s := []int{}
    return func () int {
        length := len(s)
        value:= 0
        switch length {
            case 0:
                value = 0
            case 1:
                value = 1
            default:
                value = s[length - 1] + s[length - 2]
        }
        s = append(s, value)
        return value
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

出力

0
1
1
2
3
5
8
13
21
34

cocoapodsでRxSwiftをインストールした際に出てきた謎のエラー「Expected ',' separator」

Environment

  • Swift 4.2
  • Cocoapods 1.5.3
  • XCode 10.0

Problem

表題の通り、Expected ',' separatorっていうエラーがRxSwiftのパッケージ側に出てきて、ビルドがうまくいかなかった。 Cocoapodsで管理しているのですが、Pods以下はロックされていて自分でいじっては居ないし、じゃあRxSwiftのバグかなと思って調べても特に出てこない。

それで、エラーが出ている箇所のソースコードを見ると、引数によくわからないものを渡していることに気づきました。 それでリモートリポジトリのソースコードを確認すると、エラーが出ているローカルのソースコードと違っていました。

// my local library source cord(RxSwift/Observables/Timer.swift)

return _parent._scheduler.scheduleRelative(self, dueTime: _parent._dueTime) { (_f_`) -> Disposable in
// https://github.com/ReactiveX/RxSwift/blob/53cd723d40d05177e790c8c34c36cec7092a6106/RxSwift/Observables/Timer.swift#L78

return _parent._scheduler.scheduleRelative(self, dueTime: _parent._dueTime) { (`self`) -> Disposable in

😳!

Solution

まあ、たぶんXCodeのSwift4.2シンタックスへの自動変換のときに、気づかないで変更しちゃったのかな、と思います。 解決方法としては、以下のようにCocoapodsのキャッシュを削除した上で、プロジェクトのPodsディレクトリ以下を削除して再インストールしましょう。

念の為、DerivedData以下のビルドキャッシュも消したほうが良いかもしれません。

rm -rf ~/Library/Caches/CocoaPods
rm -rf [path to project]/Pods

// optional
rm -rf ~/Library/Developer/Xcode/DerivedData

pod install

訂正(2019/09/27 15:48)

これ、Build Phaseで設定していたswiftlint autocorrectの仕業でした。アホすぎる。。

WKWebViewで最初に読み込んだページに戻る

WKWebViewの最初に読みこんだページ(URL)に戻りたいことがあったので、いろいろ調べたらwebView.goToBackForwardListItem(item: WKBackForwardListItem)で実現できました。

let webView = WKWebView()

let count = webView.backForwardList.backList.count
guard let rootItem = webView.backForwardList.itemAtIndex(-count) else { return }
webView.goToBackForwardListItem(rootItem)

解説

backForwardListで戻る履歴の数を取得します。わざわざ履歴の数を取得するのは、最初のページのWKBackForwardListItemを取得するwebView.backForwardList.itemAtIndex(index: Int)で指定するindexが現在のページからの相対値になるので、履歴数をマイナスで指定する必要があります。 取得したらそのWKBackForwardListItemをgoToBackForwardListItemに渡すだけです。

loadRequestではだめな理由

この方法を使う以外だと、最初に読み込んだURLを保存しておいて、そのURLをloadRequestで読み込むという方法が思い付きました。 ですが、今回はUIとしてあくまで「履歴の最初に戻る」というイメージが持てるような動作にしたいという要件がありました。 loadRequestの場合、履歴のスタックに最新の履歴として、そのリクエストが載ってしまい要件が満たせません(goBackで戻れてしまう)。

まとめ

  • goToBackForwardListItem(rootItem)
    • 戻る履歴は「0」になる(履歴の最初に戻るので)
    • 進む履歴は維持される
    • イメージ:
      • ■□□□□□□□
  • loadRequest(firstPageRequest)
    • 履歴の先頭にfirstPageRequestが載るため、戻る履歴は維持される
    • このリクエストが履歴の先頭になるので、進む履歴は無い
    • イメージ:
      • □□□□□□□□■
      • goToBackForwardListItem(rootItem)の場合より、履歴の数は1つ多くなる

ERROR ITMS-90037: "This bundle is invalid. The Info.plist file is missing or could not be parsed. Please check it for embedded control characters." の対処

iTunesConnectへアップロードを試みたところ、以下のようなエラーが出てしまいアップロードに失敗しました。

ERROR ITMS-90037: "This bundle is invalid. 
The Info.plist file is missing or could not be parsed. 
Please check it for embedded control characters."

いろいろ調べたのですが、以下のStackOverflowがヒットしました。

stackoverflow.com

コメントに従いInfo.plistvim(vi)で開いたところ、<key>LSApplicationQueriesSchemes^P</key>といった感じで^Pという文字が<key>フィールド内にまぎれていました。

Open As -> Source Codeでは見つからないので、vim(vi)で開いて下さい。

それを消した所、無事アップロードできました。

エラーメッセージにPlease check it for embedded control characters.とあるのだから、ちゃんと読めって話ですね。

AlamofireでUser-Agentを編集する

概要

  • Alamofireを使用していてUser-Agentを編集したいとき、ManagerのイニシャライザにNSURLSessionConfigurationインスタンスを渡して対応しました。
  • NSURLSessionConfigurationの設定については、後から動的に変えることができないので、注意が必要です。
    • 変更したい場合は、Managerインスタンスを初期化するしかありません。

2パターンあります

  • User-Agentの末尾に任意の値を挿入
  • User-Agentをすべて入れ替え

User-Agentの末尾に任意の値を挿入

let customUserAgent = "Some-User-Agent-Value"

guard let defaultUserAgent = Manager.defaultHTTPHeaders["User-Agent"] else {
    fatalError("Header User-Agent value is nil")
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
configuration.HTTPAdditionalHeaders?["User-Agent"] = defaultUserAgent + " " + customUserAgent

let manager: Manager = Manager(configuration: configuration)

User-Agentをすべて入れ替え

let customUserAgent = "Some-User-Agent-Value"

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
configuration.HTTPAdditionalHeaders?["User-Agent"] = customUserAgent

let manager: Manager = Manager(configuration: configuration)

補足

  • 上記コードのmanager変数は、APIと通信するWebAPIClietのようなクライアントクラスのインスタンスで使用しています。
  • コード例だと省略してますが、上記WebAPIClientのインスタンス変数として使っています。

UITextFieldを含むUITableViewCellのNSIndexPathを取得する方法

UITableViewCellを含むCellが1つであれば、UITextFieldDelegatetextFieldDidEndEditingから取得するだけでいいのですが、これが複数になると引数のtextFieldがどのCellに帰属するのかわからないので面倒です。 悩んでいたら、textFieldDidEndEditingの引数のtextFieldからsuperViewを取得し、indexPathForCellに送ってindexPathを取得する方法に行き着きました。

stackoverflow.com

func textFieldDidEndEditing(textField: UITextField) {
  if let cell = textField.superview?.superview as? TextFieldCell, 
     let indexPath = tableView.indexPathForCell(cell){
    //some code
  }
}

TextFieldCellがUITextFieldを含むUITableViewCellのカスタムクラスです。