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
プロトコルに適合している必要がある(そういう制約をつけている)
- Dictionaryのkeyは
Go TourのExercise
GoのTourのExerciseの回答
プログラミング言語 Goには、初学者が言語仕様を学べるTourというコンテンツがあります。
その中に練習問題(Exercise)があったので、自分の回答を学習メモとして記していきます。
Exercise: Slices
これちょっと説明文が分かりづらい気がしました。Pix
メソッドの仕様のポイントを羅列します。
Pic
メソッドの引数で受け取るdx
×dy
のサイズの2次元配列を返す- 配列の中身は各座標の
x
とy
をもとにした整数値(uint8
)を渡す- 別に
x
とy
をもとにしなくても整数値が0〜255の範囲であれば画像は生成される- 0を渡せば青一色の画像になる
- 中身の整数値によって青色の濃さが変わる
- 0に近いほど色が濃くなり、255に近いほど色が薄くなる
- 例示されている整数値の計算パターン
は使わなくてもよい
(x+y)/2
、x*y
、x^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) }
出力
Exercise: Maps
コード
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
コード
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
の仕業でした。アホすぎる。。
[改訂新版]Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)
- 作者: 石川洋資,西山勇世
- 出版社/メーカー: 技術評論社
- 発売日: 2018/01/17
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
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がヒットしました。
コメントに従いInfo.plist
をvim(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)
補足
UITextFieldを含むUITableViewCellのNSIndexPathを取得する方法
UITableViewCellを含むCellが1つであれば、UITextFieldDelegate
のtextFieldDidEndEditing
から取得するだけでいいのですが、これが複数になると引数のtextField
がどのCellに帰属するのかわからないので面倒です。
悩んでいたら、textFieldDidEndEditing
の引数のtextField
からsuperView
を取得し、indexPathForCell
に送ってindexPathを取得する方法に行き着きました。
func textFieldDidEndEditing(textField: UITextField) { if let cell = textField.superview?.superview as? TextFieldCell, let indexPath = tableView.indexPathForCell(cell){ //some code } }
TextFieldCellがUITextFieldを含むUITableViewCellのカスタムクラスです。