- 投稿日:2020-01-23T22:41:31+09:00
チェインオブレスポンシビリティをSwift5で実装する
※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。
The Chain Of Responsibility(チェインオブレスポンシビリティ)
0. チェインオブレスポンシビリティの意義
あるリクエストを処理する複数のオブジェクト(レシーバーまたはレスポンダーという)がある場合、レシーバーを線形リストで繋げてリクエストを処理させるパターンをChain of Responsibilityという。
各レシーバーは次のレシーバーへの参照を持っており、あるリクエストを処理するのに適切なレシーバーが見つかるまで(あるいはレシーバーがなくなるまで)リクエストを次のレシーバーへ渡していく。
このレシーバーを組み合わせた「鎖」をプログラム実行時に動的に作ることもでき、柔軟な処理が可能となる。
例えばiOSにおいては、画面をタップした時の処理がこのパターンによって処理されている。
リクエストを処理するレシーバが1, 2個しかない場合などは、わざわざこのパターンを使っても徒労なだけであまり意味がないことに注意。
1. 実装
まずリクエストハンドラ(レシーバー)に必要な実装の形式をプロトコル
RequestHandling
で定義する。イニシャライザで同じ型
RequestHandling
を渡す。これが「鎖」上の次のレシーバーを表すことになる。もしnilを渡した場合は、次のレシーバーがない(最後のレシーバーに到達した)事を意味する。RequestHandling.swiftpublic protocol RequestHandling { //リクエストを処理する関数 func handle(request: Any) //自分の次のレシーバーを渡して初期化する init(next: RequestHandling?)
自分の次に来るレシーバーを変数で保持するようにする。
ここでは、ジェネリクスを用いて様々な型を処理するレシーバーを表せるようにする。
RequestHandling.swiftpublic final class Handler<T>: RequestHandling, CustomStringConvertible { //自分の次のレシーバー。nilの場合は、このレシーバーが最後のレシーバーとなる private var nextHandler: RequestHandling? public init(next: RequestHandling?) { self.nextHandler = next } //ジェネリクスで渡した型Tを処理する関数 public func handle(request: Any) { if request is T { //Tが処理された場合 print("Request is processed by \(self)") } else { //処理が行われずレスポンダーチェインの最後まで到達した場合 guard let handler = self.nextHandler else { print("Reached the end of the responder chain.") return } //次のレシーバーに送る場合 print("\(self) can't handle \(type(of: request)) requests - forwarding to \(handler)") handler.handle(request: request) } }2.実行
以下のように、実行時にレスポンダーチェインを動的に生成することができる。
まず、「鎖」の最後に位置するレスポンダーから生成し、前のレスポンダーのイニシャライザに次々と渡していくことでレスポンダーチェインを作成する。
TheChainOfResposibilityMessageProcessingDemo.playgroundlet dataHandler = Handler<Data>(next: nil)//最後のレスポンダー let stringHandler = Handler<String>(next: dataHandler)//2番目のレスポンダー let dateHandler = Handler<Date>(next: stringHandler)//最初のレスポンダー鎖の最初のレスポンダーにリクエストを渡すと、適切に処理されるまで次々とリクエストがレスポンダー間を手渡されていくことがわかる。
TheChainOfResposibilityMessageProcessingDemo.playgroundlet data = Data(repeating: 8, count: 10) dateHandler.handle(request: data) /* 出力結果: Date Handler can't handle Data requests - forwarding to String Handler String Handler can't handle Data requests - forwarding to Data Handler Request is processed by Data Handler */どのレスポンダーもリクエストを処理できないと、レスポンダーチェインの最後まで到達してしまうことがわかる。
TheChainOfResposibilityMessageProcessingDemo.playgrounddateHandler.handle(request: 42) /* 出力結果: Date Handler can't handle Int requests - forwarding to String Handler String Handler can't handle Int requests - forwarding to Data Handler Reached the end of the responder chain. */https://github.com/Satoru-PriChan/ChainOfResponsiblityMessageProcessingDemo
参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ
- 投稿日:2020-01-23T17:36:02+09:00
初めてアプリを作ったので気になった点をまとめる
○初めてアプリを作った
・作ったアプリ
以下を参考にToDoアプリを作りました。とても勉強になりました。感謝です。
参照:Swiftで簡単なTODOアプリを作ってみよう
https://qiita.com/TD3P/items/8f474358d1dd789557f3・環境
Xcode 11.3
Swift 5.1.3○つまずいた点
・unrecognized selector sent to instanceのエラー
ビルドのタイミングでこのエラーが起きた。解決には以下の投稿が参考になりました。
参照:iOSアプリのunrecognized selector sent to instanceエラーについて
https://qiita.com/yoshiki-0428/items/80ea88f65f5a62ffcc11ViewContorolerにクラスファイルを紐つけていなかったことがエラー原因。
既存のProjectを流用する形でアプリを作り始めたので、紐つけを忘れておりました。
以下の要領で修正。
○アプリの動作をいじってみる
・画面遷移
ボタン操作で画面遷移すると下記の流れになっていた。
Top→遷移先→Topと動かすと遷移先が残る。勉強がてら遷移先が残らないようにしてみる。
①Top... ②遷移先... ③再度Top... 以下を参考にしました。
参照:ViewControllerを閉じる方法 (Unwind Segueを使う)
https://www.ikkitang1211.site/entry/2016/06/24/004258
遷移元(Top)のクラスファイルに下記の内容を追記しました。
遷移先ではなく遷移元という点がポイントでした。ViewController.swiftimport UIKit class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { //以下を追記 @IBAction func unwindTotop(segue: UIStoryboardSegue){ }以下の要領でButtonに上記の関数を割り当てます。
これで遷移先の画面が閉じられるようになりました。
① ② ③ ・Table Viewの更新
遷移先で追加したタスクがTopに戻っても追加されず、再起動したら表示されるようになっていた。
以下を参考に動作をいじってみます。
参照:【Swift,Xcode】Todoアプリを再起動しないとタスクが追加されない
https://teratail.com/questions/144037viewWillAppear(_:)の中で、データ取得と、テーブルのリロードを行うようにします。
TableViewのアウトレットを追加し忘れるとエラーが出ます。ViewController.swiftimport UIKit class ViewController:UIViewController,UITableViewDelegate,UITableViewDataSource { //以下を追記 TableViewのアウトレット @IBOutlet weak var TabView: UITableView! /* 中略 */ //以下を追記 override func viewWillAppear(_ animated: Bool) { if UserDefaults.standard.object(forKey: "TodoList") != nil { TodoKobetsunonakami = UserDefaults.standard.object(forKey: "TodoList") as! [String] TabView.reloadData() } } }しかし、挙動が変更されず...
調べると以下のサイトが参考になりました。
参照:iOS13のModal遷移はデフォルトだとDismiss時に遷移元のviewWillAppear等が呼ばれない
https://techblog.recochoku.jp/7215
とりあえず、segueのPresentationを Full Screenに変更することで応急処置とします。下図。
個人的にはシート型の方が好みの挙動なので、別途改修しようと思います。
○次はどうしよう
このアプリの機能追加と体裁を整えていこうと思います。
理解が不足している&間違って理解している点があるといけないの注意しなきゃ。
当分は調べればなんとかなる壁しかないと思うので、転んでも泣かずに前向きに取り組みます。○その他
Xcodeのシミュレーション機能が便利で感動しました。
パパっと挙動が確認できるのは理解の助けになります。
本業で使っているSimulinkの車両制御モデルは(モデルがでかすぎて)ビルドにも時間が掛かるし、
HILS回すのも手間で辟易としている。
分野が違うので一概に比較はできないですが、モデルベース開発にもXcodeみたいな統合開発環境があればいいのに。
- 投稿日:2020-01-23T12:10:59+09:00
[Swift5]端末からサーバへ画像のアップロード
はじめに
以前書いた[Android]端末からサーバへ画像のアップロードのios版です。
こちらもいろいろ詰まったので、参考になればと思います。環境
- Xcode 11.3
- Swift 5
ライブラリ
- Alamofire
そもそもこいつの導入でてこずりました。
参考:ライブラリの導入と、Alamofireの導入手順
https://qiita.com/0126/items/5d401acc219ac4f172d6この方の記事を読めば大体はできると思いますが、自分が詰まったところのみ書いていきたいと思います。
- ターミナルで
carthage update --platform iOS
の実行
- "Bad credentials"と表示され進まない。
- Macキーチェインアクセスを開き
github.com
の削除。- プロジェクト内にライブラリを追加
- Linked Frameworks and Libraryが無い。
- Framework,Libraries,and Embedded Content に変わっていた(?)
ViewController.swift
【Swift3.0】Alamofireで画像&パラメータを送信
https://qiita.com/tosaka07/items/eff35f410723d0e94f98
ほぼこちらの方と同じです変更点はData型で使用します。UIImageJPEGRepresentationなどを使い、UIImageから変換しましょう。
の部分がSwiftのバージョンアップに伴って使えなくなったので、
let data:Data! = UIImageJPEGRepresentation(image!, 0.5) //これではなく let data:Data! = image.jpegData(compressionQuality: 0.5) //これくらいでしょうか。完成形はこちらです。
ViewController.swiftimport Alamofire func PostImage(image:UIImage){ let data:Data! = image.jpegData(compressionQuality: 0.5) Alamofire.upload( multipartFormData: { multipartFormData in multipartFormData.append(data, withName: "test", fileName: "test.jpeg", mimeType: "image/jpeg") }, to: "送るURL", encodingCompletion: { encodingResult in switch encodingResult { case .success(let upload, _, _): upload.responseJSON { res in // 成功 let _ = response print(res ?? "成功") } case .failure(let Error): // 失敗 print(Error) } } )以上です。
PostImage(image:送りたいUIImage)で使います。
受け取る側は[Android]端末からサーバへ画像のアップロードと同じです。
- 投稿日:2020-01-23T00:08:03+09:00
#1 UI(Text)の配置 - はじめてのSwift UI
1.はじめに
Swift UIが出てきましたのでちょっと触ってみようと思います。
今回は登場しませんがNavigation Linkの戻るボタンが動かないなど不具合があるようで、もうちょっと様子みてもいいかなって感じですが。2.ファイルを作る
まずは[New File...]-[User Interface]-[SwiftUI View]より
SwiftUI Viewのファイルを作成します。SwiftUIView.swiftimport SwiftUI struct SwiftUIView: View { var body: some View { Text("Hello, World!") } } struct SwiftUIView_Previews: PreviewProvider { static var previews: some View { SwiftUIView() } }↑こんな感じのファイルができますね。
右上の辺りにある[Resume]を押下すると、Previewを見ることができます。
Previewで表示する端末は、シミュレーター同様に画面左上から選択することで変更が可能です。Previewにはど真ん中にHello, World!と表示されていると思います。
(たとえ世界が壊れても、もう一度、君に会いたい)3.Swift UIでUIを配置する
SwiftUIはコードで簡単にUIを配置できるようです。
触ってみた感じ、HTMLでテーブルを作るイメージに近いのかなと感じたのでWeb製作者などにとっつきやすくなっているのかな?3.1 上下に配置する
UIを上下に配置する場合は、VStack{}を使用します。
SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { VStack{ Text("Up") Text("Buttom") } } }画面中央に文字が二段で表示されます。
「もっと一番上と一番下にしたいんだけど!!」って思った人
怒らないでください。そういう人はSpacer()を使いましょう。SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { VStack{ Text("Up") Spacer() Text("Buttom") } } }これだけです。枠の一番上と一番下に文字が行きました。
Spacer()は画面の空き部分を埋めるよう自動でサイズが調整されます。正式名称忘れましたがSafe Area的なものがあって、
iPhoneX以降の上下曲線部分はここでいう一番上や一番下には含まれません。3.2 左右に配置する
「3.1 上下に配置する」が理解できた方ならここは簡単です。
VStack()ではなくHStack()を使います。SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { HStack{ Text("Left") Text("Right") } } }もちろんSpacer()も使えますので、
どうなるかは実際に試してみてください。3.3 上下左右の配置を組み合わせる
VStack()やHStack()は入れ子にすることができます。
以下のようにすることで、上下左右に配置できます。SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { HStack{ Text("Left") Spacer() VStack{ Text("Up") Spacer() Text("Down") } Spacer() Text("Right") } } }3.4 11個以上の項目を並べる
VStack()やHStack()には10個までのUI(View)しか入れることができません。
以下のようにするとコンパイルエラーとなります。SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { VStack{ Text("Text1") Text("Text2") Text("Text3") Text("Text4") Text("Text5") Text("Text6") Text("Text7") Text("Text8") Text("Text9") Text("Text10") Text("Text11") } } }そんな時は適宜Group{}にまとめて10個以内に収めましょう。
Group{}は以下のような感じで使うことができます。SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { VStack{ Text("Text1") Text("Text2") Text("Text3") Text("Text4") Text("Text5") Text("Text6") Text("Text7") Text("Text8") Text("Text9") Group{ Text("Text10") Text("Text11") } } } }また、ForEachを使うことで動的に並べることも可能です。
SwiftUIView.swiftstruct SwiftUIView: View { var body: some View { VStack{ ForEach(1 ..< 12){ Text("Text\($0)") } } } }4 おわりに
とりあえずファイル作成して文字を並べるところを簡単にまとめました。
文字以外のUIも同様に並べられるかと思いますので、色々試してみたらいいんじゃないでしょうか。ではまた。