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のカスタムクラスです。

CompleteHandlerのないアニメーションにCATransactionでCompleteHandlerを追加する

タイトルの通りです。 CompleteHandlerのないアニメ―ションにCompleteHandlerを追加します。 下の例ではUITableViewediting modeの終了アニメーション後に処理を追加しています。

CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
// some code
}
setEditing(false, animated: true)
CATransaction.commit()

対象になるアニメーションの処理(setEditing)をCATransaction.begin()CATransaction.commit()で囲んで、CATransaction.setCompletionBlockにやりたい処理を書きます。