- 投稿日:2019-03-05T23:01:54+09:00
【SwiftChaining】 処理を確定する関数
SwiftChainingの解説記事その5です。
SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。
前回の記事では、監視対象のオブジェクトの
chain()
を呼んだ後に繋げて書く「処理を構築する関数」の解説をしました。今回は、「処理を構築した後に確定させて監視を開始する関数」について少し詳しく解説しようと思います。
今まで何度も出てきたものになるのですが、
end
とsync
についてです。処理を確定する関数
end
end
は、chain()
の後に繋げて書いて構築した処理を確定し、監視を開始します。let notifier = Notifier<Int>() let chain1 = notifier.chain() // ここではまだ監視は始まらず処理が追加できる let chain2 = chain1.do { _ in } // endを呼んで処理が確定し監視が始まる let observer = chain2.end() // endの後に処理を追加しようとするとfatalErrorになる // chain2.do { _ in } observer.invalidate() // invalidateされたので監視は止まる
end
は返り値にObserver
のインスタンスを返し、それ以降はもう処理をつなげることはできません。処理を追加しようとしてもfatalError
になります。なお、
end
を呼んだ時点では何もイベントは起きず、その後監視対象のオブジェクトからイベントが発生したらchain()
以降で書いた処理が実行されます。バインディングという意味では、監視を開始したタイミングで値を知りたくなるのですが、その場合は次で紹介するsync
を呼ぶことになります。sync
sync
は、chain()
の後に繋げて書いて構築した処理を確定し、1度イベントを送信させた上で監視を開始します。let sender = ValueHolder(1) let receiver = ValueHolder(0) let observer = sender.chain() .sendTo(receiver) .sync() // syncが呼ばれたことによってreceiverは1になっている print(receiver.value) sender.value = 2 // 変更を監視しreceiverは2になっている print(receiver.value)監視対象のオブジェクトが常に値を返すことのできる場合に使うことができ、
sync
を呼ぶと内部的にend
を呼びつつ、監視対象のオブジェクトから一度イベントを送らせる事が出来ます。
end
だと変更があったら送られるだけですが、sync
で監視し始めにも送ることによって繋げた値を同期させられるという感じです。なお、監視対象のオブジェクトからはいくつも
chain()
を呼んで複数監視ができ、普通に対象の値が変更された時のイベントはその全部に対して送られますが、sync
を呼んだ際のイベントはそのひとつだけに送られます。ちなみに、ここで出てきた「常に値を返すことのできる」オブジェクトというのは何かと言うと、今までの記事で紹介したものの中では…
- ValueHolder
- KVOAdapter
というクラスのオブジェクトになります。
逆に「値を返す事ができない」クラスは…
- Notifier
- NotificationAdapter
- UIControlAdapter
などです。後者のような値を持っていなくて返すことができないオブジェクトからのみ処理が繋がっている場合は
end
でしか終わらせることができません。
merge
などで値を返せるオブジェクトと返せないオブジェクトの処理を混ぜた場合にはsync
を呼ぶことはできますが、無い袖は振れないので値を返せるオブジェクトからのみイベントが送信されます。なお、「値を返せる」「値を返せない」の違いは何かと言うと、
SwiftChaining
的には適合しているプロトコルがFetchable
かSendable
かということなのですが、これはまた別の記事で解説しようと思います。
- 投稿日:2019-03-05T22:43:08+09:00
CS193p class note - 3 Swift programming language
Memo:
- There is a global function that will create a CountableRange from floating point values.
for i in stride(from: 0.5, through: 15.25, by: 0.3) { }Tuple:
- It is nothing more than a grouping of values. You can use it anywhere you can use a type.
- You can use tuples to return multiple values from a function or method
Computed properties:
- The value of a property can be computed rather than stored.
- "set" is optional. You can create a read-only computed property and you don't need to specify the keyword "get".
- Lots of times a "property" is derived from other state. That's why we use computed properties.
Access control:
- internal: this is the default, it means "usable by any object in my app or framework"
- private: this means "only callable from within this object"
- private(set): this means "this property is readable outside, but not settable"
- fileprivate: accessible by any code in this source file
- public: (for framework only)this can be used by objects outside my framework
- open: (for framework only)public and objects outside my framework can subclass this.
Extensions:
- You can add methods/properties to a class/struct/enum even if you don't have the source.
- The properties you add have no storage associated with them (computed only)
- You can't re-implement methods or properties that are already there (only add new ones)
Enum:
- It can only have discrete states
- An enum is a value type, so it is copied when it is passed around.
- Each state can (but doesn't have to) have its own "associated data".
- When you set the value of an enum you must provide the associated data (if any).
- Swift can infer the type on one side of the assignment or the other (but not both).
- An enum's state is checked with a switch statement.
- If you don't want to do anything in a given case, use "break".
- You must handle all possible cases (although you can default uninteresting cases).
- Each case in a switch can be multiple lines and does not fall through the next case.
- Associated data is accessed through a switch statement using "let syntax"
- An enum can have methods and computed properties but no stored properties
- "mutating" keyword is required because enum is a value type.
ARC (Automatic Reference Counting):
- Reference types (classes) are stored in the heap. The system "counts reference" to each of them and when there are zero references, they get tossed automatically. This process is known as "Automatic Reference Counting" and it is not garbage collection.
- You can influence ARC by how you declare a reference-type var with these keywords: strong, weak, unowned
- strong: normal reference counting
- weak: weak means "if no one else is interested in this, then neither am I, set me to nil in that case". We usually use weak in IBOutlet and delegate.
- unowned: "don't reference count this. Crash if I am wrong". This is very rarely used. Usually only to break memory cycles between objects.
- 投稿日:2019-03-05T22:21:28+09:00
【SwiftChaining】処理を構築する関数
SwiftChainingの解説記事その4です。
SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。
今までの記事で、
SwiftChaining
で用意しているオブジェクトのchain()
という関数を呼ぶと、そのあとにオブジェクトの値が変更された時などに実行される処理を書ける、ということを紹介しました。今回はその
chain()
を呼んだ後に書く部分について少し詳しく解説しようと思います。Chainとは
監視対象となるオブジェクトの
chain()
関数を呼ぶとChain
という型のインスタンスを返します。Chain
は監視対象のオブジェクトがイベントを送信する時に実行される処理を構築するクラスです。(監視対象となるオブジェクトというのは、
SwiftChaining
的にはSendable
というプロトコルに適合したオブジェクトということになるのですが、プロトコルに関しては別の記事で解説する予定です。)
Chain
の持つ関数を呼ぶとまたその返り値にChain
を返すという感じで、処理をメソッドチェーンでどんどん追加して繋げて書くことができるようになっています。
Chain
の関数はいろいろあるのですが、大きく分けると「処理を構築する」関数と「処理を確定する」関数があります。例えば以下のようなものです。処理を構築する関数
- do
- map
- guard
- merge
- tuple
- combine
- sendTo
処理を確定する関数
- end
- sync
メソッドチェーンで処理をいくつもつなげて書いていけるのは「処理を構築する関数」の方です。今回はこの「処理を構築する関数」について解説します。
処理を構築する関数
do
do
は、イベントを受け取った時に登録したクロージャの処理を単に実行するものです。クロージャの引数には、受け取ったイベントの値が入ってきます。イベントの値はそのまま次の処理に渡されます。let notifier = Notifier<Int>() let observer = notifier .chain() .do({ (value: Int) in // 何か処理をする }) .end() // "1"が送信されるので、doのクロージャの引数valueに1が渡され実行される notifier.notify(value: 1)真面目に書くとこのような感じになるのですが、クロージャの記述は普通に省略して書く事が出来るので、通常は以下のコードくらい省略する方が書きやすいでしょう。
.do { value in // 何か処理をする }ちなみに、これまでのサンプルコードに出てきた
sendTo
でオブジェクトに値を反映させるというのも、内部的にはdo
を使って値をセットしているだけです。何かしらイベントが来た時に、最終的にバインドするような処理を書くのは、基本的にdo
を使うということになります。また、開発中に「この時点ではどんな値が来ているのか」と調べたい時も、
do
を挟み込んで確認するという使い方もできると思います。map
map
は、受け取ったイベントの値を変化させて次の処理に渡すことができます。Arrayのmap
と同じようなものと思っていただいて良いと思います。例として
Int
をString
に変換する場合は、以下のようになります。let notifier = Notifier<Int>() let observer = notifier .chain() .map({ (intValue: Int) -> String in return String(intValue) }) .do({ (strValue: String) in // 変換されたStringの値を受け取る }) .end() notifier.notify(value: 1)こちらもクロージャの記述を省略する方が書きやすいでしょう。最大限まで省略すると以下のようになります。
.map { String($0) }
map
もdo
と同じくクロージャを実行するものではあるので、何でも書きたい処理は書けるのですが、map
では単に値を変換をする処理を書くだけに留めておくほうが良いと思います。guard
guard
は、受け取ったイベントをそこで止めることができます。登録したクロージャでtrue
を返すとそのまま値を次の処理へ渡しますが、false
を返すと処理を打ち切ります。例として、
Int
が奇数の場合だけイベントを通すコードは以下のようになります。let notifier = Notifier<Int>() let observer = notifier .chain() .guard({ (value: Int) -> Bool in return value % 2 != 0 }) .do({ value in // valueが奇数の時だけ実行される }) .end()
guard
もmap
と同じく関係のない処理を書かずに、単にイベントを通すか通さないかという条件だけを書いたほうが良いと思います。merge
merge
は、2つの同じ型のChain
の処理の流れを同じ型のまま1つにまとめます。let notifier1 = Notifier<Int>() let notifier2 = Notifier<Int>() let chain1 = notifier1.chain() let chain2 = notifier2.chain() let observer = chain1 .merge(chain2) .do { value in // 両方の値を受け取る } .end() // どちらから送ってもdoが実行される notifier1.notify(value: 1) notifier2.notify(value: 2)
merge
が使えるのは同じ型のChain
同士の場合のみです。型が違う場合は以下に紹介するtuple
やcombine
を使うことになります。tuple
tuple
は、2つのChain
の処理の流れをひとつのタプルにまとめます。こちらは
merge
と違い別の型でもまとめることができます。その代わり2つのOptionalの要素を持ったタプルに変換されます。let chain1 = notifier1.chain() let chain2 = notifier2.chain() let observer = chain1 .tuple(chain2) .do { (value1: Int?, value2: String?) in // value1かvalue2のどちらかだけ値が入って来る }.end() // doが実行されvalue1に値が渡される notifier1.notify(value: 1) // doが実行されvalue2に値が渡される notifier2.notify(value: "2")
tuple
から出力されるタプルは、2つの要素両方に値が入ることはありません、どちらか一方に値があればもう一方は必ずnil
になります。2つの処理の流れが混ざらずに並走しているだけとイメージしてもらうと良いかもしれません。ただ、2つの要素がバラバラに送られてきてもそのままでは扱いにくいと思うので、実際には次に紹介する
combine
を使うことが多くなるかもしれません。combine
combine
は、2つのChain
の流れをひとつのタプルにまとめます。tuple
と違うのは、受け取った値を内部で保存しており、必ず2つの値が揃った状態でタプルのイベントが送信されることです。ただ、タプルの中身はオプショナルではなく、どちらか一方がイベントを一度も受け取っていなければ
combine
からイベントが送信されることはありません。let holder1 = ValueHolder(1) let holder2 = ValueHolder("2") let chain1 = holder1.chain() let chain2 = holder2.chain() let observer = chain1 .combine(chain2) .do { (value1: Int, value2: String) in // 両方の値が揃って実行される } .sync()上記のコード例のように、
combine
でまとめる送信元がValueHolder
のように値を常に送信できる種類のものだと、sync()
を呼んだ時点で2つともから1度値が送信されるので、以後どちらかだけが変わってもcombine
の後に値が送信されます。これが
Notifier
のように送信元が値を持たない種類のものだと、敢えて明示的に値を送信しておかないとcombine
での値が揃わないので、注意が必要です。let notifier1 = Notifier<Int>() let notifier2 = Notifier<String>() let observer = notifier1.chain() .combine(notifier2.chain()) .do { (value1: Int, value2: String) in // 両方の値が揃ってから実行される } .end() // この時点ではdoは実行されない notifier1.notify(value: 0) // この時点でも片方しか値がないのでdoは実行されない notifier1.notify(value: 1) // 2つとも値が揃ったのでdoが実行される notifier2.notify(value: "2")sendTo
sendTo
は、引数に渡したオブジェクトにイベントの値を送る関数です。let notifier = Notifier<Int>() let holder = ValueHolder<Int>(0) // notifierから値を送信したらholderが値を受け取るようにする let observer = notifier.chain().sendTo(holder).end() // notifierから値が送信されholderに1がセットされる notifier.notify(value: 1)具体的にどのようなオブジェクトが受け取れるのかというと、
SwiftChaining
的にはReceivable
というプロトコルに適合したクラスということになります。プロトコルの具体的な説明はまた別のPRでしようと思いますが、今まで出てきたクラスでは以下のものが適合しています。
- ValueHolder
- KVOAdapter
- Notifier
ValueHolder
やKVOAdapter
は値を保持しているものなので、イベントを受け取ったら値をセットします。Notifier
はそれ自身が値を保持するものではないので、受け取った値をそのまま送信する感じの動作になります。また、前回の記事にも書きましたが、
sendTo
で送るイベントの型と受け取るオブジェクトの型がオプショナルの有無の違いだけであれば、map
などで変換しなくてもそのままつなげることができます。
- 投稿日:2019-03-05T22:20:38+09:00
【Swift】 カスタムキーボードを無効化する
アプリ内でサードパーティー製のカスタムキーボードを制限、無効化する方法を紹介します
脆弱性診断NG
事の発端は、↓でした。
Input Interception: Keyboard Extensions Allowedこれは、fortifyという脆弱性診断の静的解析ツールによって吐かれた脆弱性です。
サードパーティのカスタムキーボードの利用が無効化されていません。カスタムキーボードはユーザ入力を全て取得でき、悪意の有無に関わらずキーロギングを行っている可能性があります。
という脆弱性のようです。カスタムキーボードの脆弱性とは?
予測変換などに活用するため、ユーザの入力情報を取得しているため、それによって、パスワードやクレジットカード情報などが送信されてしまう脆弱性があるようです。
例えば、有名なアプリsimejiでは下記のサイトのようなリスクが有るようです。
使うのは危険!?Simejiを安全に使う方法対処法
対処法は、以下のコードをAppDelegate.swiftに書くだけです。
これで、simejiアプリを無効化することができました。AppDelegate.swiftfunc application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool { if extensionPointIdentifier == .keyboard { return false } return true }参考資料
- http://blogs.quovantis.com/best-practices-to-avoid-security-vulnerabilities-in-your-ios-app/
- 自分のアプリでカスタムキーボードを使われないように制限する
おまけ
「カスタムキーボード 無効化」とぐぐっても出てこなくて苦労しました、、
「カスタムキーボード 制限」とググればよかったんですね、、泣
- 投稿日:2019-03-05T20:11:10+09:00
SwiftからPHPへファイルをPOSTする
iOS側のコード
//アップロードしたいファイルのパスを取得 let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("test").appendingPathExtension("csv") var req = URLRequest(url: URL(string: "http://{ホストのアドレス}/upload.php")!) req.httpMethod = "POST" URLSession.shared.uploadTask(with: req, fromFile: path) { (data, res, err) in print(data, res, err) }.resume()PHP側のコード
<?php date_default_timezone_set('Asia/Tokyo'); //タイムゾーンを設定する $date = date("YmdHis"); //時刻の取得 $file = file_get_contents("php://input"); //RequestのBodyを取得 $fileName = "./${date}.csv"; //保存する名前を取得 file_put_contents($fileName,$file); //保存 ?>Swift、PHPともにエラーハンドリングなどは全て省略されていますのでプロダクションとして使う場合はもう少し手を加えてあげる必要がありますのでご注意を!!
- 投稿日:2019-03-05T19:34:36+09:00
笑顔判定でAmazonPrimeのドキュメンタルを正確に判定する。
はじめに
こんにちは、drama(@1901drama)です!
Amazon Prime Videoの ドキュメンタル が好きなのですが、
シーズン1から「フジモンの判定甘いんじゃないか」問題が評価を下げる要因として話されています。。。場回し的な人がいないと展開が少なくなったりしますし、ある程度仕方ないかな とは思うのですが、
そもそも 本当に笑っているのでしょうか? この問題をARで解決しました!出来たもの
ドキュメンタルヘルパー
Documental HELP その1 pic.twitter.com/C5NppqChsm
— drama(ARエンジニア) (@1901drama) 2019年3月5日
・カメラ画像から顔検出をする
・笑顔かどうかを判定する
・笑ってたらアウトにする(顔をドキュメンタルのロゴに変える/画面のふちに赤っぽいフレームを追加する)
Documental HELP その2 pic.twitter.com/q3UKT4qxbB
— drama(ARエンジニア) (@1901drama) 2019年3月5日
これでどこでもドキュメンタルが出来ます。
※1/30 フレーム単位なのでジャッジが厳しいです。
尚、やってみてわかったのですが、他の人も結構笑顔判定されます。仕様
以下の流れでドキュメンタルをしています。
1.カメラで取得した動画リアルタイムで、バッファに入れます。
2.バッファに入った動画を、フレーム(1/30)単位で画像に変換します。
3.画像ごとに顔検出を行います。
4.顔検出された場合、顔の特徴点から笑顔か否かを判定します。
5.笑顔と判定された場合、顔の位置と大きさを、描写用のコントローラに受け渡します。
6.定められた位置と顔の大きさのアウトマークを画面に追加(表示)します。
7.01秒後に追加したアウトマークを削除します。
8.上記1~7をループ▼因みに、顔検出&笑顔判定はとても簡単でした。
//顔検出 let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) // 取得するパラメーター(笑顔判定)を指定する let options = [CIDetectorSmile : true] // 画像からパラメーター(笑顔判定)を抽出する ※ciimage = フレーム単位で取得した画像 let features = detector.featuresInImage(ciImage, options: options)そして、それ以上に驚いたのが、
CPU使用率がすごい。
デバイスがiPhone SEというのもありますが、検出精度をlowにしていても、基本100%以上でした。
これを6時間見続ける審査員って相当疲れるんだろうなっと思いました。難しかった点
1.ARAnchor->画面位置への変換
当初は遠近を把握する為に、ARFaceAnchorを利用して顔判別をしようと思ってました。class ARFaceAnchor : ARAnchorしかしフレーム単位では処理が重過ぎたので、アウトマークの画像サイズと場所を高速で更新する方法に変えました。
因みにiPhoneX以上の機種であればARkit2の顔に自動でマスクをつける機能が使えます。
顔を猫に変えるとか、アウトマーク以外の追加であれば、その方が簡単に導入出来ると思います。2.カメラの向き(横か縦)を、cgimageに引継げない。
UIimageは、撮影時のデバイス向きを保持しているのですが、
動画から取り出したimageの cgimage は、向き情報を持ってなかったです。
顔検出や笑顔判定には向き情報が必要な為、ここを解決出来ず縦でしか使え無いアプリとなってしまいました...。一般的には画面の向きも 別変数で取得して一緒に利用しているのでしょうか?
このあたりご存知の方いれば教えて欲しいです。まとめ
ドキュメンタルの結論
「笑ってるけど、皆 まあまあ 笑ってる!!」 でした。なので楽しく見ましょう。ここまで読んで頂きありがとうございました!
動画:ドキュメンタル シーズン5
引用中の動画は他のものに置き換えるかも知れません。
- 投稿日:2019-03-05T19:34:36+09:00
CIDetectorでAmazonPrimeのドキュメンタルを正確に判定する。
はじめに
こんにちは、drama(@1901drama)です!
Amazon Prime Videoの ドキュメンタル が好きなのですが、
シーズン1から「フジモンの判定甘いんじゃないか」問題が評価を下げる要因として話されています。。。場回し的な人がいないと展開が少なくなったりしますし、ある程度仕方ないかな とは思うのですが、
そもそも 本当に笑っているのでしょうか? この問題をARで解決しました。出来たもの
ドキュメンタルヘルパー
Documental HELP その1 pic.twitter.com/C5NppqChsm
— drama(ARエンジニア) (@1901drama) 2019年3月5日
・カメラ画像から顔検出をする
・笑顔かどうかを判定する
・笑ってたらアウトにする(顔をドキュメンタルのロゴに変える/画面のふちに赤っぽいフレームを追加する)
Documental HELP その2 pic.twitter.com/q3UKT4qxbB
— drama(ARエンジニア) (@1901drama) 2019年3月5日
これでどこでもドキュメンタルが出来ます。
やってみてわかったのですが、他の人も結構笑顔判定されます。
※1/30 フレーム単位なのでジャッジは厳しめです。仕様
以下の流れでドキュメンタルをしています。
1.カメラで取得した動画リアルタイムで、バッファに入れます。
2.バッファに入った動画を、フレーム(1/30)単位で画像に変換します。
3.画像ごとに顔検出を行います。
4.顔検出された場合、顔の特徴点から笑顔か否かを判定します。
5.笑顔と判定された場合、顔の位置と大きさを、描写用のコントローラに受け渡します。
6.定められた位置と顔の大きさのアウトマークを画面に追加(表示)します。
7.0.1秒後に追加したアウトマークを削除します。
8.上記1~7をループ▼因みに、顔検出&笑顔判定はとても簡単でした。
//顔検出 let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) // 取得するパラメーター(笑顔判定)を指定する let options = [CIDetectorSmile : true] // 画像からパラメーター(笑顔判定)を抽出する ※ciimage = フレーム単位で取得した画像 let features = detector.featuresInImage(ciImage, options: options)そして、それ以上に驚いたのが、
CPU使用率がすごい。
デバイスがiPhone SEというのもありますが、検出精度をlowにしていても、基本100%以上でした。
これを6時間見続ける審査員って相当疲れるんだろうなっと思いました。難しかった点
1.ARAnchor->画面位置への変換
当初は遠近を把握する為に、ARFaceAnchorを利用して顔判別をしようと思ってました。class ARFaceAnchor : ARAnchorしかしフレーム単位では処理が重過ぎたので、アウトマークの画像サイズと場所を高速で更新する方法に変えました。
因みにiPhoneX以上の機種であればARKit2の顔に自動でマスクをつける機能が使えます。
顔を猫に変えるとか、アウトマーク以外の追加であれば、その方が簡単に導入出来ると思います。2.カメラの向き(横か縦)を、cgimageに引継げない。
UIimageは、撮影時のデバイス向きを保持しているのですが、
動画から取り出したimageの cgimage は、向き情報を持ってなかったです。
顔検出や笑顔判定には向き情報が必要な為、ここを解決出来ず縦でしか使え無いアプリとなってしまいました...。一般的には画面の向きも 別変数で取得して一緒に利用しているのでしょうか?
このあたりご存知の方いれば教えて欲しいです。まとめ
ドキュメンタルの結論
「笑ってるけど、皆 まあまあ 笑ってる」 でした。なので楽しく見ましょう。ここまで読んで頂きありがとうございました!
動画:ドキュメンタル シーズン5
引用中の動画は他のものに置き換えるかも知れません
- 投稿日:2019-03-05T19:03:15+09:00
Terobosan Terbaru situs judi online rajasenangqq
Terobosan terbaru dari situs judi poker domino online terpercaya rajasenangqq
Rajasenangqq mempermudah para member untuk melakukan deposit dengan mudah tanpa harus ribet yaitu dengan system deposit menggunakan pulsa.
Jadi anda tidak perlu harus ke ATM untuk transfer dana deposit.Untuk anda para pecinta domino QQ, Kini hadir situs domino QQ :
- TERBAIK (Dalam hal PELAYANAN)
- TERPERCAYA (GAMPANG MENANG)
- TERCEPAT (Proses DEPO-WD).
Kami menyediakan 8 permainan dalam 1ID :
- POKER
- BANDAR POKER
- ADU Q
- BANDAR Q
- DOMINO 99
- CAPSA SUSUN
- SAKONG
- BANDAR 66MIN. DEPO HANYA RP. 10.000
DAPATKAN BONUS {TO - 0.5%, REFERAL - 10%, TRIK}
KLIK LINK :
http://bit.ly/RJ53nang
Whatsapp : http://bit.ly/w4hpRJQQ
- 投稿日:2019-03-05T17:52:51+09:00
SwiftのFlowControllerによる画面遷移の基本
FlowControllerの元祖はこちらのIssueで、このIssue提案者が英語で書いた説明記事がこちらです。しかし全て英語でコードが所々しかなく難しかったので、@shizさんの記事をベースにしてつくったサンプルアプリを元に、基本的な『FlowControllerによるpush遷移とpresent遷移のやり方』を解説します。
サンプルアプリ
今回は以下のようなアプリを@shizさんのサンプルコードをベースに作りました。
デモ
画面遷移の流れ
- 記事一覧(ListView)を表示
- push(DetailFirstView)
- push(DetailSecondView)
- push(DetailThirdView)
- present(ModalView)
- push(ModalDetail)
- 完了ボタンで全ての流れを終了
コード
FlowControllerとは何なのか
原文や@shizさんの記事ではCoordinatorパターンと比較する形でFlowControllerのメリットを紹介されています。ただペーペープログラマである僕らにとっては「まずCoordinatorパターンって何?」から入らなければならないため理解に苦しむと思います。
FlowControllerとは、簡単に言うと「通常はViewControllerが担っていた画面遷移の処理(責務)を、ViewControllerから切り分けて代わりに担当してくれるController」です。
具体的には「UIViewControllerを継承したViewControllerのラッパー」であり、構造的にはAppFlowController>HogeFlowController>(NavigationController)>ViewControllerという親子関係になっています。
今までViewControllerで行なっていたpushやpresentの画面遷移を親のHogeFlowControllerに(delegateを使って)委任することで、ViewControllerの責務を減らし、コードを少なくスッキリさせています。
ちょっと何いってるかわかんない
実際のところ、色々な設計パターン(アーキテクチャ)や実装の経験を経てより良い設計に辿り着くのが本来の流れだと思う(知識は必要になった時に初めて身につく)ので、「ちょっと何いっているかわかんない」という人は無理にFlowControllerやCoordinatorパターンを使わなくて良いと思います(←説明の怠慢)。
何が良いのか
こういった設計パターンやアーキテクチャの主目的はどれも同じで「責務の切り分け」とそれによる「テストを容易にすること」です。
このFlowControllerの目的・良さも「ViewControllerの画面遷移の責務を切り分けてやることで、コードの可読性や保守性を良くし、テストも容易にできること」と言えると思います。
Coordinatorパターンでもほぼ同じメリットを得られる(はず)ですが、原文や@shizさんの記事にもあるように「Coordinatorパターンにはないメリットを得られるからFlowControllerを使う」わけで、その詳しい違いについては本記事では割愛します。
追記
僕なりにCoordinatorパターンとFlowControllerパターンの違いをまとめたので最後におまけとして付けておきます)。
今回のサンプルの処理流れ
今回作成したサンプルの構造としては以下の画像のようになっています。
AppFlowController
がすべてのFlowControllerの親です(アプリ起動後にAppDelegate
のapplication(_:didFinishLaunchingWithOptionsAppFlowController)
でAppFlowController
のstart()
を呼んでフローをスタートしています)。初期画面
最初に
ListViewController
を表示したいので、まずListFlowController
を生成します(この過程でembeddedNavigationController = UINavigationController()
も生成している)。次に
ListFlowController
の中でListViewController
を生成し、embeddedNavigationController.viewControllers = [listViewController]
とすることで初期画面を表示します。push遷移
そして
Next
ボタンが押された時にdelegate
であるListFlowController
に画面遷移を依頼し、そこで今度はDetailFirstViewController
を生成し、embeddedNavigationController.viewControllers = [firstViewController]
とすることで画面遷移をするわけです。present遷移
present遷移の場合は新たなナビゲーションを開始(新しいNavigationControllerを生成)しなければならないため、
ThirdViewController
からdelegate
であるListFlowController
に依頼し、さらにListFlowController
がdelegate
であるAppFlowController
に新たなフロー(ModalFlowController
)を開始するように依頼します。ここでpresent遷移をするのは
ModalFlowController
です。ModalFlowController
もUIViewControllerを継承しているのでpresent遷移が可能で、その子が上下左右ぴたぴたに張り付いているだけなので、見た目としてはModalViewController
がpresent遷移したのと同じになります。ModalFlowController.swiftoverride func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() children.first?.view.frame = view.bounds }flowのremove
最後の
FifthViewController
で「完了」ボタンを押した時は、同様にdelegateを使ってNewModalFlowController
を経由して一番親であるAppFlowController
に「フローを終了して!」と依頼する形になります。フロー開始時に
AppFlowController
でadd(childController: listFlowController)
のようにaddしているだけなら、同じくAppFlowController
でself.children.forEach { remove(childController: $0) }
を実行してやれば、今のフローを全て削除してホーム画面などに戻ることができます(サンプルではListFlowを再度startして記事一覧画面を表示しています)。しかし今回はpresent遷移(下からViewが上がってくる遷移)なので
AppFlowController
でadd(childController: newModalFlowController)
とせずviewController.present(newModalFlowController, animated: true)
としています(ここでのviewControllerはpresent遷移する直前のThirdViewControllerのインスタンスを、delegateで依頼する時に引数としてAppFlowControllerまで渡してきたものです)。なので画面を消すときもdismiss(上から下に下がる消え方)をしなければなりません。そこで
ModalFlowController
がAppFlowController
に「フローを終了して」と依頼した直後にself.dismiss(animated: true)
で自分自身をdismissしています。Coordinatorパターンとの違い(おまけ)
@shizさんの記事の各見出しを僕なりに整理してみました。@shizさんの記事や原文を読んで良くわからなかったときに、僕なりの補足を見ることで皆さんの理解の助けになればと思います(ListFlowControllerのフローの前にもうひとつ、LoginFlowControllerのフローが開始されているので、AppFlowController>LoginFlowController>ViewControllerという構造になっている前提で以下は読んでいただければと)。
3.FlowControllerは依存関係を管理
LoginDependencyContainerなどに依存すべきプロパティ?を追加してやることで簡単に依存性を注入できる?←Coordinatorパターンは?
4. 子FlowControllerの管理が簡単
FlowControllerはUIViewControllerを継承しているので、 addとremoveのメソッド(Extension)だけで子FlowControllerできる。←Coordinatorパターンは?
5.UIWinowを保持する必要がない
Coordinatorパターンの場合はinitで都度UIWindowを触っている。
FlowControllerならUIViewControllerを継承しているので、UIWindowをいじるコードを各必要がない。
6.個々のフローを疎結合にできる
疎結合とはシンプルな結合という意味です(対義語は密結合)。Coordinatorパターンでは親のAppCoordinatorでNavigationControllerを生成して、子であるLoginCoordinatorをinitする時に引数で渡さなければならないから結合度が高い(密結合である)。
FlowControllerなら子であるLoginFlowController自身でNavigationControllerを生成できる。
7. UIResponderを継承している
Coordinatorパターンと違って、FlowControllerはUIViewControllerを継承しているので
touchesBegan
を始めとした様々なイベントのデリゲートメソッドを使用することができる 。8. FlowControllerから画面の表示などを管理できる
本来は子VCにさせる表示関係の仕事も、親であるFlowControllerも一応できるよってことではないかと思います(でもそれをしてしまうと役割切り分けた意味なくね?臨時策? )。
9. NavigationControllerの戻るボタン
CoordinatorパターンはNavigationの「戻る」ボタンを手動で設定しなければならない。
FlowControllerは(UIViewControllerを継承しているので?)自動でやってくれる。
10. コールバックで画面遷移を依頼する
よく分からない。Coordinatorパターンがどうやって画面遷移をしているか知らない(おい)。
FlowControllerは「ViewControllerが、delegate(親であるLoginFlowController)に依頼して画面遷移する」のですが、コールバックは使ってないんじゃないのかな…(画面遷移完了したらViewControllerでバック後の処理を実行するとかにはなっていないと思うのだが)。
まとめ
まだ記事を書いてる途中で理解がうやむやだったりするので、リプいただければ随時補足・修正していきます!
- 投稿日:2019-03-05T16:11:20+09:00
[Tips] UITextviewの行数とかを取得する
概要
UITextviewの表示状況取得のためのTips。ちなみに設定系は、[Tips] UITextviewをUILabelっぽく使うで。
0,取得タイミングについて
同じtextでも表示領域がちがえば行数も変わってくるわけで、例えば表示前に情報を取得しようと思ってもうまくいかないことが多い。AutoLayoutがベースになっているなら、UIViewControllerライフサイクルの
viewWillLayoutSubViews
タイミングで取得するのをオススメ。1,行数を取得する
UITextViewの行数を取得するextension UITextView { var numberOfLines: Int { // prepare var computingLineIndex = 0 var computingGlyphIndex = 0 // compute while computingGlyphIndex < layoutManager.numberOfGlyphs { var lineRange = NSRange() layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } // return if textContainer.maximumNumberOfLines > 0 { return min(textContainer.maximumNumberOfLines, computingLineIndex) } else { return computingLineIndex } } }Apple公式ドキュメントに方法(Objective-C!!!)がでていたのでこれを参考に実装。ただし、素直に実装してみると、「最大行数設定しているのに改行の入り方によってはそれよりも大きい数が返ってきてしまう」という不具合があったため、上記のように返り値に少し工夫をした。
参考:Apple: Text Layout Programming Guide: Counting Lines of Text
2,表示省略されているかを取得する
UITextViewの表示省略されているかextension UITextView { var isTruncated: Bool { // prepare let lineMax = textContainer.maximumNumberOfLines > 0 ? textContainer.maximumNumberOfLines : Int.max var computingLineIndex = 0 var computingGlyphIndex = 0 // compute & return while computingGlyphIndex < layoutManager.numberOfGlyphs { // analyze line let overLineMax = computingLineIndex > lineMax let existsTruncatedGlyph: Bool = { let truncatedGlyphRange = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: computingGlyphIndex) return truncatedGlyphRange.location != NSNotFound }() // guard guard !overLineMax, !existsTruncatedGlyph else { return true } // prepare for next var lineRange = NSRange() self.layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } return false } }1の応用。指定行の省略文字位置を取得するファンクション
truncatedGlyphRange(inLineFragmentForGlyphAt:)
がlayoutManagerにあるためこれを用いる。ちなみに、設定系の話だが、
.textContainer.lineBreakMode = .byTruncatingTail
とかの設定をしないと3点リーダー等の省略文字は出ない。備考
(特になし)
- 投稿日:2019-03-05T16:11:20+09:00
[Tips] UITextViewの行数などを取得する
概要
UITextviewの表示状況取得のためのTips。ちなみに設定系は、[Tips] UITextviewをUILabelっぽく使うで。
0,取得タイミングについて
同じtextでも表示領域がちがえば行数も変わってくるわけで、例えば表示前に情報を取得しようと思ってもうまくいかないことが多い。AutoLayoutがベースになっているなら、UIViewControllerライフサイクルの
viewWillLayoutSubViews
タイミングで取得するのをオススメ。1,行数を取得する
UITextViewの行数を取得するextension UITextView { var numberOfLines: Int { // prepare var computingLineIndex = 0 var computingGlyphIndex = 0 // compute while computingGlyphIndex < layoutManager.numberOfGlyphs { var lineRange = NSRange() layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } // return if textContainer.maximumNumberOfLines > 0 { return min(textContainer.maximumNumberOfLines, computingLineIndex) } else { return computingLineIndex } } }Apple公式ドキュメントに方法(Objective-C!!!)がでていたのでこれを参考に実装。ただし、素直に実装してみると、「最大行数設定しているのに改行の入り方によってはそれよりも大きい数が返ってきてしまう」という不具合があったため、上記のように返り値に少し工夫をした。
参考:Apple: Text Layout Programming Guide: Counting Lines of Text
2,表示省略されているかを取得する
UITextViewの表示省略されているかextension UITextView { var isTruncated: Bool { // prepare let lineMax = textContainer.maximumNumberOfLines > 0 ? textContainer.maximumNumberOfLines : Int.max var computingLineIndex = 0 var computingGlyphIndex = 0 // compute & return while computingGlyphIndex < layoutManager.numberOfGlyphs { // analyze line let overLineMax = computingLineIndex > lineMax let existsTruncatedGlyph: Bool = { let truncatedGlyphRange = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: computingGlyphIndex) return truncatedGlyphRange.location != NSNotFound }() // guard guard !overLineMax, !existsTruncatedGlyph else { return true } // prepare for next var lineRange = NSRange() self.layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } return false } }1の応用。指定行の省略文字位置を取得するファンクション
truncatedGlyphRange(inLineFragmentForGlyphAt:)
がlayoutManagerにあるためこれを用いる。ちなみに、設定系の話だが、
.textContainer.lineBreakMode = .byTruncatingTail
とかの設定をしないと3点リーダー等の省略文字は出ない。備考
(特になし)
- 投稿日:2019-03-05T13:51:30+09:00
UICollectionViewFlowLayoutのestimatedItemSizeを指定するとiOS12で表示がおかしい
問題
UICollectionViewFlowLayoutのestimatedItemSizeに以下を設定する。
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize(width: 1, height: 1) }もしくは
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize }本来有効になるUICollectionViewCellのSelf-Sizing CellsがiOS12未満だと問題ないが、iOS12ではうまく動かない。
対応
class CollectionViewCell: UICollectionViewCell { @IBOutlet weak internal var label: UILabel! override func awakeFromNib() { super.awakeFromNib() self.contentView.translatesAutoresizingMaskIntoConstraints = false let leftConstraint = self.contentView.leftAnchor.constraint(equalTo: self.leftAnchor) let rightConstraint = self.contentView.rightAnchor.constraint(equalTo: self.rightAnchor) let topConstraint = self.contentView.topAnchor.constraint(equalTo: self.topAnchor) let bottomConstraint = self.contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor) NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint]) } }UICollectionViewCellのawakeFromNib()で上記の設定を行うことでうまく動作するようになる。
- 投稿日:2019-03-05T13:18:33+09:00
NSObject-Rxの実装を読み解く(二番煎じ)
NSObject-Rxの実装を紐解きます。
RxSwiftを利用していれば、おそらく利用したことのあるライブラリ、
RxSwiftCommunity/NSObject-Rx
。一応解説しておくと、
NSObject
のサブクラスであれば、DisposeBag
を宣言する必要なく利用することができるという便利な代物です。RxSwiftで開発をしているととても良く使うライブラリの一つではないでしょうか。
今回、ひょんなことからこのライブラリがどのようにして
disposeBag
を実装しているのかを確認してみたくなって、びっくりしたので、まとめておきます。Swiftの機能のみを利用した実装ではない。
今回話している
RxSwiftCommunity/NSObject-RX
は、Swiftの機能のみを利用した実装にはなっていません。
NSObject-Rx
の実体ファイルは下記ファイルなのですが、結構無茶苦茶やってますね。https://github.com/RxSwiftCommunity/NSObject-Rx/blob/master/NSObject%2BRx.swift
大まかな流れは、
Reactive
Protocolのデフォルト拡張をBase: AnyClass
に指定(すべてのクラスでデフォルト拡張される)Objective-Cの
メンバ変数追加
ができるAssociated Object
というびっくり黒魔術を利用してDisposeBag
を宣言
(SwiftでObjective-Cの黒魔術ってどうなった?: https://qiita.com/fmtonakai/items/e9036dec4af2609b5715)初回アクセス時には生成、セット時には
setter
内部でセットされたdisposeBagをAssociated Object経由でプロパティかのように保持disposeBagの生成、アクセス処理は
objc_sync_enter
、objc_sync_exit
で強制同期。結構すごい実装になってました。
ちなみになんですが、
Associated Object
はピュアSwiftだと動作しないようなので、Ubuntuとかで開発する場合は、うまくいかない感じだと思われます。読み解いてみて
Objective-C
由来の黒魔術がこんなところで生かされていたのが結構驚きでした。
- 投稿日:2019-03-05T04:53:16+09:00
バグに気づきやすくなる??演算子を改造したカスタムオペレータ 【Swift】
はじめに
確かに,
??
をむやみに使うとデバッグ段階で気づくべきバグに気付けなくなる可能性があるので,下記の記事で述べられていることはとても有意義に感じた.
一方で,下記の記事で紹介されている方法を毎回行うのも面倒なので,カスタムオペレータを作ってみた.安易な気持ちで
as? Int ?? 0
みたいなコードを書いてはいけません(戒め
https://qiita.com/lovee/items/a82087f8c3663f207c7f作ったカスタムオペレータ
!?
二項演算子で表現した.
エラー分は関西人風味でお届け.infix operator !? func !?<T>(lhs: T?, rhs: T) -> T { return lhs ?? { assertionFailure("って,アンラップ出来んやないか~~~~い?") return rhs }() } let text: String? = nil // デバッグ環境ならエラー, プロダクション環境なら"hello world"が返却されます print(text !? "hello world")
- 投稿日:2019-03-05T04:53:16+09:00
バグに気づきやすくなる`??`演算子を改造したカスタムオペレータ 【Swift】
はじめに
確かに,
??
をむやみに使うとデバッグ段階で気づくべきバグに気付けなくなる可能性があるので,下記の記事で述べられていることはとても有意義に感じた.
しかし,毎回記述するのも大変そうなので,簡単に扱えるカスタムオペレータを作ってみた.安易な気持ちで
as? Int ?? 0
みたいなコードを書いてはいけません(戒め
https://qiita.com/lovee/items/a82087f8c3663f207c7f作ったカスタムオペレータ
!?
二項演算子で表現した.
エラー文は関西人風味でお届け.これでバグを出ても決して苛立つ事は無くなるので一石二鳥である.ぜひ採用してみてほしい.infix operator !? func !?<T>(lhs: T?, rhs: T) -> T { return lhs ?? { assertionFailure("って,アンラップ出来んやないか~~~~い?") return rhs }() } let text: String? = nil // デバッグ環境ならエラー, プロダクション環境なら"hello world"が返却されます print(text !? "hello world")最後に
プログラマが真にnil許容を許容している場合は
??
を,コンパイラに翻弄されてしまっている場合は!?
を使う,といったような使い分けが出来ると良いと思った.