- 投稿日:2021-05-16T23:46:49+09:00
俺得保存版navigationviewでTable Viewの作り方(cellをxibでつくり、画面遷移で値を渡す)
なんかサイト通りに作っても動かん こんな経験があるので、自分のために確実に動作するTableViewをメモしておきます。 navigationでtableviewを作ってcellを拡張しやすいxibで作る。 ViewController.swift(tableviewがあるnavigationview) ViewController.swift import UIKit class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource{ @IBOutlet weak var tableView: UITableView! //cellListの中身が出力されます var cellList : [menu] = [menu]() override func viewDidLoad() { super.viewDidLoad() //tableviewのデータをどこで扱うか tableView.dataSource = self tableView.delegate = self tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell") setData() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //何個入れるよ!って指示 return cellList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //つかうcellを定義 let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell //そのcellに値を入れる cell.setCellData(menu: cellList[indexPath.row]) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // セルの選択を解除 tableView.deselectRow(at: indexPath, animated: true) // 別の画面に遷移(ストーリーボードのIdentityに"secondと書いてある") let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "second") as! secondViewController //次の画面の.swiftに値を渡す secondViewController.number = String(indexPath.row) //navigation遷移なのでfullScreenに変更 secondViewController.modalPresentationStyle = .fullScreen //self.navigationController?.pushViewController(,animated)はnavigation限定 self.navigationController?.pushViewController(secondViewController, animated: true) } } TableViewCell.swift(xibでcellを作るために必要なもの) TableViewCell.swift import UIKit class TableViewCell: UITableViewCell { @IBOutlet weak var nameLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } //値をセットする //引数にクラスを指定する。 func setCellData(menu:menu){ nameLabel.text = menu.name as String } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } menu.swift(Jsonとかを使ってtableviewに値入れるなら絶対必要) menu.swift import Foundation class menu : NSObject{ var name :String init(name:String){ self.name = name as String } } secondViewController.swift(cellを押したときに情報をもって出てくる次の画面) secondViewController.swift import UIKit class secondViewController: UIViewController { @IBOutlet weak var label: UILabel! var number :String! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. label.text = number } }
- 投稿日:2021-05-16T23:17:27+09:00
暇なのでJsonをswiftで扱いやすくするサイト作ってみた
Decodableって書くのだるいね ってことでjsonをどう扱えばいいかを出してくれるサイトをjavascriptで作ってみました。 Object型とArray型の部分を連結しようとしたんだけれども、メモリの圧迫が(私のPCだと)不安があるので、 見たければボタン押して?って感じにしました。 このくらいのサイトならたぶん説明するより使ってみてもらったほうが楽かもしれないので一回試してみてください。
- 投稿日:2021-05-16T18:56:05+09:00
VIPER学ぼう
目次 1.はじめに 2.VIPERとは 3.VIPERのそれぞれの役割 4.View 5.Interactor 6.Presenter 7.Entity 8.Router 9.最後に 参考 1.はじめに iosアプリ開発を勉強していくうちに必ずぶち当たるアーキテクチャの一つであるVIPERについて記事を書きたいと思います。 今回は地図アプリを作成します。 僕はVIPERのサンプルをコーディングをしていくうちに感動しました。 2.VIPERとは VIPERとはView・Interactor・Presenter・Entity・Routerのそれぞれの頭文字をとったものです。 また基本としてDI(Dependency Injection)で外部から注入し、単一原則のもとに分割します。 MVC、MVVMなどのアーキテクチャはViewが画面遷移も担当する必要があります。 3.VIPERのそれぞれの役割 役割 View UIの更新やPresenterにViewイベントの通知 Interactor ビジネスロジックを担当 Presenter ハブの役割※ Entity データ構造の定義(Model) Router 画面遷移 ※ViewにUISearchBarがあったとしてエンターを押したときにPresenterへ通知し、PresenterはInteractorにデータの取得を依頼するなど 4.View 早速Viewのコードを見ていきます。 import UIKit import MapKit ① protocol MapView: AnyObject { func updatePin(coordinate: CLLocationCoordinate2D) func showErrorAlert() } final class MapViewController: UIViewController, Storyboardable { ② var presenter: MapPresentation! private let pin = MKPointAnnotation() @IBOutlet private weak var mapView: MKMapView! private var searchBar: UISearchBar = { let searchBar = UISearchBar() searchBar.placeholder = "住所" searchBar.autocorrectionType = .no searchBar.autocapitalizationType = .none return searchBar }() override func viewDidLoad() { navigationItem.titleView = searchBar searchBar.delegate = self } } extension MapViewController: MapView { func updatePin(coordinate: CLLocationCoordinate2D) { guard let adress = searchBar.text else { return } updateScreenCenter(coordinate: coordinate) pin.title = adress pin.coordinate = coordinate mapView.addAnnotation(pin) } func showErrorAlert() { let alertController = UIAlertController(title: "No Result", message: "該当する検索結果がありませんでした", preferredStyle: .alert) let ok = UIAlertAction(title: "OK", style: .default) alertController.addAction(ok) present(alertController, animated: true) } } extension MapViewController: UISearchBarDelegate { ③ func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { guard let searchText = searchBar.text else { return } presenter.searchButtonPushed(adress: searchText) } } private extension MapViewController { func updateScreenCenter(coordinate: CLLocationCoordinate2D) { let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) let region = MKCoordinateRegion(center: coordinate, span: span) mapView.region = region } } ①はPresenterから呼び出す関数をprotocolで定義?します。 関数名 機能 updatePin(coordinate: ) 検索した住所の位置にピンをさす func showErrorAlert() エラーが発生したらアラートを出す ②はpresenterにイベントを通知するために定義します。 ③で検索したい住所をpresenterに渡しています。 5.Interactor import CoreLocation protocol MapUsecase: AnyObject { func searchAdress(adress: String, completion: @escaping (Result<CLLocationCoordinate2D, Error>) -> Void) } final class MapInteractor { } extension MapInteractor: MapUsecase { func searchAdress(adress: String, completion: @escaping (Result<CLLocationCoordinate2D, Error>) -> Void) { Geocoder.fetchAdress(adress: adress) { result in switch result { case .success(let coordinate): completion(.success(coordinate)) break case .failure(let error): completion(.failure(error)) break } } } } Interactorはビジネスロジックを担当します。 特に説明はいらないと思いますが、APIを叩くなどです。 6.Presenter import Foundation protocol MapPresentation: AnyObject { func searchButtonPushed(adress: String) } final class MapPresenter { private weak var view: MapView? private let router: MapWriteframe private let interactor: MapUsecase ① init(view: MapView, router: MapWriteframe, interactor: MapUsecase) { self.view = view self.router = router self.interactor = interactor } } extension MapPresenter: MapPresentation { ② func searchButtonPushed(adress: String) { //interactorに住所の検索を依頼 interactor.searchAdress(adress: adress) {[weak self] result in switch result { case .success(let coordinate): self?.view?.updatePin(coordinate: coordinate) break case .failure: self?.view?.showErrorAlert() break } } } } ①DIの部分です。Presenterはハブの役割なのでそれぞれのインスタンスが必要になります。 ②viewでsearchBarSearchButtonClicked(_ searchBar: )が呼び出されたときにsearchBar.textを渡されてinteractorに検索を依頼します。 7.Entity 今回のアプリは自分で定義するEntityはありませんが(CLLocationCoordinate2D)、githubのAPIなどを叩くときにRepositoryEntityなどを定義しましょう。 8.Router protocol MapWriteframe: AnyObject { } final class MapRouter { ① private weak var viewControlelr: UIViewController? private init(viewController: UIViewController) { self.viewControlelr = viewController } ② static func assembleModules() -> UIViewController { let view = MapViewController.instantiate() let router = MapRouter(viewController: view) let interactor = MapInteractor() let presenter = MapPresenter(view: view, router: router, interactor: interactor) view.presenter = presenter return view } } extension MapRouter: MapWriteframe { } ①viewController使ってないのになんで宣言してるのと思われるかもしれないですが、画面遷移する場合に必要になります。(今回は必要ない) ②viewControllerの表示用の関数。 9.最後に 一つの画面ごとにView、Interactor、Presenter、Entity、Routerが必要でめんどくささもありますが、テストのしやすさ、可読性、修正しやすさなど多くのメリットがあります。 僕が一番感動したのはコードの綺麗さです。 自分ができる人みたいな感覚になれておすすめです。 今回なんとなく記事にしてみましたがまだまだ理解が足りていないと感じたので精進します。 アドバイスなどありましたらよろしくお願い致します。 参考 VIPERアーキテクチャ まとめ
- 投稿日:2021-05-16T13:58:58+09:00
Core Hapticsを用いて触覚フィードバック機能つきのメトロノームアプリを作ってみる
Core Hapticsは、iOS 13から追加された振動と音声を統合的に制御するフレームワークです。Core Hapticsを用いることで振動機能のついたメトロノームを作成することができます。 1. はじめに 1.1 本記事で作成するメトロノーム 本記事では以下の機能がついた簡単なメトロノームを作成します。なお、UIの配置にはSwiftUIを用います。 BPMを表示する機能 ボタンによりBPMを1ずつ増やす機能 設定したBPMのテンポで音を繰り返し鳴らす機能 音に合わせてiPhone本体を振動させる機能 1.2 本記事のソースコード 本記事のソースコードは、https://github.com/Ossamoon/sample-haptics に掲載してあります。 1.3 Core Hapticsについて この記事では、Core Hapticsフレームワークの基本的な使い方は省略します。基本的な使い方を学びたい方は、Qiitaの記事『Core Haptics - カスタムハプティックパターンの作成と再生』および、Appleの公式ドキュメントを参考にしてください。 1.4 動作環境 Xcode 12.5 Swift 5.4 2. アプリの作成 2.1 プロジェクトの作成 Xcodeで新規プロジェクトを作成します。SwiftUIが使えるようInterfaceにはSwiftUIを指定し、Life CycleにはSwiftUI Appを指定します。 プロジェクトを作成するとContentView.swiftファイルが自動生成されます。このファイル内のContentView構造体は2.4 Viewの作成にて書き直します。 2.2 音源の取り込み メトロノームの音として利用するための外部音源をXcodeに取り込みます。今回はフリーの効果音を配布されている効果音ラボさんの音源を利用させていただきます。 ボタン・システム音のページに移動し、"決定、ボタン押下2"の音源をダウンロードしてください。 ダウンロードした音源はXcode上にドラッグアンドドロップすることでXcodeに取り込むことができます。場所はどこでもいいですが、ContentView.swiftと同じ階層が分かりやすいでしょう。後で使いやすいように、音源のファイル名をsound.mp3に変更しておきましょう。 2.3 Controllerの作成 振動機能を制御するコントローラーを記述していきます。ContentView.swiftと同じ階層に新規ファイルを作成し、ファイル名をHapticController.swiftとしてください。 HapticController.swiftを開き、以下のコードを記述します。 HapticController.swift import Foundation import CoreHaptics import AVFoundation class HapticController { // メトロノームのパラメーター var bpm: Double = 120.0 // AudioSession private var audioSession: AVAudioSession // 音声データに関わるパラメータ private let audioResorceNames = "sound" private var audioURL: URL? private var audioResorceID: CHHapticAudioResourceID? // HapticEngine private var engine: CHHapticEngine! // 端末がCore Hapticsに対応しているか private var supportsHaptics: Bool = false // HapticPatternPlayer private var player: CHHapticAdvancedPatternPlayer? // HapticEventのパラメーター private let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) private let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) private var hapticDuration: TimeInterval = TimeInterval(0.08) // AudioEventのパラメーター private let audioVolume = CHHapticEventParameter(parameterID: .audioVolume, value: 1.0) private var audioDuration: TimeInterval { TimeInterval(60.0 / bpm) } init(){ // AudioSessionの設定 audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playback) try audioSession.setActive(true) } catch { print("Failed to set and activate audio session category.") } // 端末がCore Hapticsに対応しているかを調べる let hapticCapability = CHHapticEngine.capabilitiesForHardware() supportsHaptics = hapticCapability.supportsHaptics // 外部音源の取り込み if let path = Bundle.main.path(forResource: audioResorceNames, ofType: "mp3") { audioURL = URL(fileURLWithPath: path) } else { print("Error: Failed to find audioURL") } createAndStartHapticEngine() //この関数は下で定義 } // Engineの作成と開始 private func createAndStartHapticEngine() { // 端末の対応を確認 guard supportsHaptics else { print("This device does not support CoreHaptics") return } // AudioSessionを渡してEngineを作成 do { engine = try CHHapticEngine(audioSession: audioSession) } catch let error { fatalError("Engine Creation Error: \(error)") } // Engineをスタート do { try engine.start() } catch let error { print("Engin Start Error: \(error)") } } // メトロノームを再生 func play() { // 端末の対応を確認 guard supportsHaptics else { return } do { // Engineをスタート try engine.start() // HapticPatternを作成 let pattern = try createPattern() //この関数は下で定義 // Playerを作成(Advacedの方を利用していることに注意) player = try engine.makeAdvancedPlayer(with: pattern) player!.loopEnabled = true // 再生 try player!.start(atTime: CHHapticTimeImmediate) } catch let error { print("Haptic Playback Error: \(error)") } } // メトロノームを停止 func stop(){ // 端末の対応を確認 guard supportsHaptics else { return } // 停止 engine.stop() } // HapticPatternの作成 private func createPattern() throws -> CHHapticPattern { do { var eventList: [CHHapticEvent] = [] // AudioResorceIDを取得 audioResorceID = try self.engine.registerAudioResource(audioURL!) // eventListにHapticEventを加えていく eventList.append(CHHapticEvent(audioResourceID: audioResorceID!, parameters: [audioVolume], relativeTime: 0, duration: self.audioDuration)) eventList.append(CHHapticEvent(eventType: .hapticTransient, parameters: [sharpness, intensity], relativeTime: 0)) eventList.append(CHHapticEvent(eventType: .hapticContinuous, parameters: [sharpness, intensity], relativeTime: 0, duration: self.hapticDuration)) // HapticPatternを生成し返す let pattern = try CHHapticPattern(events: eventList, parameters: []) return pattern } catch let error { throw error } } } いくつかのポイントを詳しく説明します。 外部音源を利用するにはEngineにAudioSessionを渡す Core Hapticsで外部音源を利用するには、 AudioSessionのインスタンスを取得し、アクティブにする CHHapticEngine(audioSession: audioSession)のように、Engineのインスタンスを取得する際にオーディオセッションを渡す 外部音源のパスを取得し、AudioResorceIDを取得する AudioResorceIDを渡したHapticEventを生成し、それをHapticPatternに組み込む といった手順が必要になります。EngineはAudioSessionを渡さなくてもインスタンスが取得できるのですが、その場合は外部音源が再生できなかったり、音量が端末本体の音量と連動しなかったりします。 メトロノームアプリにTimerは使えない 一定の時間感覚で繰り返し処理を行う場合、一般的にはTimerが使用されます。しかしながら、Timerの時間間隔は厳密でなく、メトロノームアプリに採用してしまうとテンポの揺らぎを感じてしまいます。(少なくとも筆者は違和感を感じました) そこで、Core Hapticsに内蔵されたループ機能を用います。ループの長さはHapticPatternやHapticEventの長さに準拠するので、それらを調節することによってループの長さを調節できます。 ループ機能を使うにはmakeAdvancedPlayerメソッドでPlayer作成する HapticPatternPlayerを作るためのメソッドはmakePlayer(with:)とmakeAdvancedPlayer(with:)の2種類が用意されていますが、ループ機能を用いる時はAdvancedの方が必須となります。Advancedは、再生速度を変えたいときなどにも用いられます。 2.4 Viewの作成 ContentView.swiftファイル内のContentView構造体を以下のように書き直します。 ContentView.swift import SwiftUI struct ContentView: View { // UI表示に必要なパラメーター @State private var bpm: Int = 60 @State private var isPlaying: Bool = false // コントローラーのインスタンス private var hapticController = HapticController() var body: some View { VStack { Text("BPM") .font(.system(size: 20)) // BPMを表示 Text(String(bpm)) .font(.system(size: 68)) // "+"ボタン: タップするとBPMを1増やす Button(action: { self.bpm += 1 }, label: { Image(systemName: "plus.circle") .resizable() .scaledToFit() .frame(width: 60.0, height: 60.0) }) .disabled(isPlaying) // 再生・停止ボタン if isPlaying == false { Button(action: { hapticController.bpm = Double(self.bpm) hapticController.play() isPlaying = true }) { Text("Start") .font(.system(size: 60)) } } else { Button(action: { hapticController.stop() isPlaying = false }) { Text("Stop") .font(.system(size: 60)) } } } } } プレビューでUIの見た目を確認すると1.1 本記事で作成するメトロノームで紹介したスクリーンショットのような見た目になっていると思います。 2.5 実機で確認 Core HapticsをSimulatorでテストすることはできません。よって、想像通りのものができているか確認するためには、実機テストが必要不可欠になります。 お手元にiPhone 8以降の端末があれば、ぜひBuildして実機テストを行ってみてください。指定のBPMで音が鳴ると同時に端末が振動していれば完成です。 3. より深く知りたい方へ 筆者が作成したメトロノームアプリがApp Storeに公開されています。ソースコードも筆者のGitHubにて公開しているので、まずはApp Storeでダウンロードして使い心地を試した後、ソースコードをじっくり読んでみることをお勧めします。
- 投稿日:2021-05-16T10:54:41+09:00
Notion APIライブラリまとめ
Ruby Laravel Swift JS go python vue PHP React
- 投稿日:2021-05-16T10:23:50+09:00
iOSにてFCMを用いてPUSH通知受信サンプル実行録
謎にハマる部分がいくつかあったため、備忘録として残しておく。 1. プロジェクトのコピー こちらのリポジトリをとりあえずコピー、pod installまでしておきましょう。 2. アプリの情報をFirebaseのコンソールに登録 Firebaseのコンソール画面にいく プロジェクトの概要右の歯車→プロジェクトの設定 マイアプリよりアプリを追加を選択 1.にてコピーしたプロジェクトのbundleIDがそのままだとcom.google.firebase.quickstart.MessagingExampleだったはず。このままだとビルドできないのでcom.google.firebase.quickstart.MessagingExample.abcのように変更してビルドできることを確認する。(確か同じbundleIDのアプリはこの世に存在してはいけないからとか、そんな理由かと) (このタイミングではGoogleService-Info.plistを入れていないので、ビルド後すぐクラッシュすると思われるがOK) とりあえずビルドできることを確認したら設定したbundleIDをアプリの追加の「iOSバンドルID」のところに入力する。 アプリ登録完了 2. Firebaseの情報をアプリに登録 プロジェクトの概要右の歯車→プロジェクトの設定 マイアプリに登録したアプリが追加されているか確認 その中にGoogleService-Info.plistなるボタンがあると思うので、それをダウンロード ダウンロードしたらXCode上にぶち込む ビルドが普通に通ることを確認する。(さっきクラッシュしてたところが通るはず) 3. APNsの認証キーを作成する Apple Developer Programに登録する(12980円!) メンバー画面に飛び、Certificates, Identifiers & Profiles→Keysを選択 Keyの名前を適当につける。(pushFirebaseExampleみたいな) Apple Push Notifications service (APNs)にチェック入れてContinue,Registerする 認証キーをダウンロードする。(二度とダウンロードできないらしいので注意) このとき画面に書いてあるKeyIDをメモ ※1 4. Firebase上にAPNs情報を登録する プロジェクトの概要右の歯車→プロジェクトの設定 Cloud Messaginを選択 マイアプリから先ほど作成したアプリのAPNs認証キーというものが空白のはずなので選択 ここに先ほどの認証キーをアップロード。 キーIDには※1にてメモしておいたKeyIDを入力 チームIDにはApple DeveloperのMembershipページ→Team IDとあるところを入力 アップロードする 5. テストする FirebaseにてCloud Messagingを選択 通知を作成 アプリの選択は作成したサンプルアプリを選択 送信! できた! 実際にアプリに導入する際にはこのサンプルアプリでの動作を確認&公式ドキュメントを確認しつつやると少しはやりやすいかな、と思います。 本来であれば公式ドキュメントのみですらすら終えるつもりが・・中々時間がかかってしまった。 あまりに初心者だったため、「チームID? 何設定してもええやろ」と思って設定したので困ってました。 引き続き開発に勤しみます。
- 投稿日:2021-05-16T03:48:33+09:00
週刊 DICOMデコーダーを創る (3) シークエンス解読
SQ(シークエンス)の構造 DICOM規格日本語訳のPS3.5 p39-を読み解いていきます 図のように,SQのVRが来ると,その次の2byteは予約済み(普通は0x0000がはいっている)で,関係が無いので飛ばします. 続く4byteにデータ長が入っています. データ長は2種類あり, 種類 データ長のバイト列 概要 明示的長さ 0x00 01 13 50 (例) このように値が入っている場合はデータ長を示す 未定義長さ 0xFF FF FF FF データ長が明示されていない.この場合は,項目を区切るタグを目印によむ このような未定議長さの場合には,区切りタグを基に読む必要があります. タグ 意味 概要 FFFE, E0000 項目の開始 項目の開始に必要なタグ FFFE, E00D 項目の区切り 項目の長さが明示されている場合は省略される FFFE, E0DD SQの終わり シークエンスの長さが明示されている場合は省略される 今回のファイルで最初にSQが登場するのは,アドレス0x21C(540)からです. この部分を図示すると次の通りとなります. 実装 コーディング DicomData.swift func analyzeData(){ currentPosition = 128 + 4 // シークエンス用 var hierarchy = 0 while currentPosition < dicomData.count { let position = currentPosition let group = readUInt16() let element = readUInt16() // SQ用の特殊タグをチェックする if (group == 0xFFFE && element == 0xE000){ // 項目開始 let length = readUInt32() if length == 0xFFFFFFFF{ printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: "", length: length) hierarchy += 1 continue }else{ // TODO: 明示長さの場合は,指定の部位まで読んだら階層を下げる必要がある printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: "", length: length) currentPosition += Int(length) continue } }else if (group == 0xFFFE && element == 0xE00D){ // 項目終わり let length = readUInt32() // 0xFFFFFFFF printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: "", length: length) hierarchy -= 1 continue }else if (group == 0xFFFE && element == 0xE0DD){ // シークエンスの終わり let length = readUInt32() // 0xFFFFFFFF printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: "", length: length) hierarchy -= 1 continue } let vr = readChar(length: 2) if ["OB", "OW", "OF", "UT"].contains(vr){ // VRの続きの2byteは意味をなさないので飛ばす currentPosition += 2 let length = readUInt32() if length == 0xFFFFFFFF{ printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: vr, length: length) hierarchy += 1 }else{ printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: vr, length: length) currentPosition += Int(length) } }else if vr == "SQ"{ // SQでは,データ長さが0xFFFFFFFFの場合に注意 currentPosition += 2 let length = readUInt32() if length == 0xFFFFFFFF{ // 未定義長さ printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: vr, length: length) hierarchy += 1 }else{ // 明示長さ currentPosition += Int(length) printLog(hierarchy: hierarchy, position: position, group: group, element: element, vr: vr, length: length) } }else{ let length = readUInt16() print("\(String(repeating: " ", count: hierarchy))0x\(String(position, radix: 16).uppercased())(\(position)), tag: (\(String(format: "%04x", group)), \(String(format: "%04x", element))), VR: \(vr), Length: \(UInt32( length))") currentPosition += Int( length) } } } (省略 readUInt16, readUInt32, readCharは変更なし) func printLog(hierarchy: Int, position: Int, group: UInt16, element:UInt16, vr:String, length:UInt32){ if length == 0xFFFFFFFF{ print("\(String(repeating: " ", count: hierarchy))0x\(String(position, radix: 16).uppercased())(\(position)), tag: (\(String(format: "%04x", group)), \(String(format: "%04x", element))), VR: \(vr), Length:") }else{ print("\(String(repeating: " ", count: hierarchy))0x\(String(position, radix: 16).uppercased())(\(position)), tag: (\(String(format: "%04x", group)), \(String(format: "%04x", element))), VR: \(vr), Length: \(length)") } } 解読の流れ タグ(Group, Element)をよむ 特殊なタグの場合は個別処理 FFFE, E000 : 項目の始まりなので,階層を上げる FFFE, E00D : 項目の終わりなので,階層を下げる FFFE, E0DD : シークエンスの終わりなので,階層を下げる 上記の特殊タグの場合は続くVRは無いので,continueでループの最初に戻る VRを読む "OB", "OW", "OF", "UT"の場合は画像データが入っていたりする.いまはまだ読まないので,後日実装 SQの場合はシークエンスの始まり.長さは明示されている場合もあるし未定義の場合もある現時点では明示されている場合は,その長さ分すっ飛ばして解読を続けている 他のVRは2byteの長さを読む 実行結果 dicom file loaded 0x84(132), tag: (0002, 0000), VR: UL, Length: 4 0x90(144), tag: (0002, 0001), VR: OB, Length: 2 0x9E(158), tag: (0002, 0002), VR: UI, Length: 28 0xC2(194), tag: (0002, 0003), VR: UI, Length: 46 0xF8(248), tag: (0002, 0010), VR: UI, Length: 22 0x116(278), tag: (0002, 0012), VR: UI, Length: 8 0x126(294), tag: (0008, 0008), VR: CS, Length: 38 0x154(340), tag: (0008, 0016), VR: UI, Length: 28 0x178(376), tag: (0008, 0018), VR: UI, Length: 46 0x1AE(430), tag: (0008, 0020), VR: DA, Length: 8 0x1BE(446), tag: (0008, 0030), VR: TM, Length: 6 0x1CC(460), tag: (0008, 0050), VR: SH, Length: 0 0x1D4(468), tag: (0008, 0060), VR: CS, Length: 2 0x1DE(478), tag: (0008, 0070), VR: LO, Length: 0 0x1E6(486), tag: (0008, 0080), VR: LO, Length: 0 0x1EE(494), tag: (0008, 0081), VR: ST, Length: 0 0x1F6(502), tag: (0008, 0090), VR: PN, Length: 0 0x1FE(510), tag: (0008, 1030), VR: LO, Length: 0 0x206(518), tag: (0008, 1050), VR: PN, Length: 0 0x20E(526), tag: (0008, 2110), VR: CS, Length: 2 0x218(536), tag: (0008, 2112), VR: SQ, Length: 0x224(548), tag: (fffe, e000), VR: , Length: 0x22C(556), tag: (0008, 1150), VR: UI, Length: 28 0x250(592), tag: (0008, 1155), VR: UI, Length: 46 0x286(646), tag: (fffe, e00d), VR: , Length: 0 0x28E(654), tag: (fffe, e0dd), VR: , Length: 0 0x296(662), tag: (0009, 0010), VR: LO, Length: 14 0x2AC(684), tag: (0009, 1002), VR: OB, Length: 904 0x640(1600), tag: (0009, 1003), VR: OB, Length: 528 0x85C(2140), tag: (0009, 1005), VR: OB, Length: 24 0x880(2176), tag: (0010, 0010), VR: PN, Length: 12 0x894(2196), tag: (0010, 0020), VR: LO, Length: 8 0x8A4(2212), tag: (0010, 0030), VR: DA, Length: 8 0x8B4(2228), tag: (0010, 0040), VR: CS, Length: 2 0x8BE(2238), tag: (0018, 0060), VR: DS, Length: 0 0x8C6(2246), tag: (0018, 1063), VR: DS, Length: 2 0x8D0(2256), tag: (0018, 1152), VR: IS, Length: 0 0x8D8(2264), tag: (0018, 1155), VR: CS, Length: 2 0x8E2(2274), tag: (0018, 1500), VR: CS, Length: 0 0x8EA(2282), tag: (0018, 1510), VR: DS, Length: 4 0x8F6(2294), tag: (0018, 1511), VR: DS, Length: 2 0x900(2304), tag: (0019, 0010), VR: LO, Length: 16 0x918(2328), tag: (0019, 1030), VR: UL, Length: 4 0x924(2340), tag: (0020, 000d), VR: UI, Length: 54 0x962(2402), tag: (0020, 000e), VR: UI, Length: 54 0x9A0(2464), tag: (0020, 0010), VR: SH, Length: 0 0x9A8(2472), tag: (0020, 0011), VR: IS, Length: 2 0x9B2(2482), tag: (0020, 0013), VR: IS, Length: 0 0x9BA(2490), tag: (0020, 0020), VR: CS, Length: 0 0x9C2(2498), tag: (0021, 0010), VR: LO, Length: 16 0x9DA(2522), tag: (0021, 1013), VR: IS, Length: 2 0x9E4(2532), tag: (0028, 0002), VR: US, Length: 2 0x9EE(2542), tag: (0028, 0004), VR: CS, Length: 12 0xA02(2562), tag: (0028, 0008), VR: IS, Length: 2 0xA0C(2572), tag: (0028, 0009), VR: AT, Length: 4 0xA18(2584), tag: (0028, 0010), VR: US, Length: 2 0xA22(2594), tag: (0028, 0011), VR: US, Length: 2 0xA2C(2604), tag: (0028, 0100), VR: US, Length: 2 0xA36(2614), tag: (0028, 0101), VR: US, Length: 2 0xA40(2624), tag: (0028, 0102), VR: US, Length: 2 0xA4A(2634), tag: (0028, 0103), VR: US, Length: 2 0xA54(2644), tag: (0028, 1040), VR: CS, Length: 4 0xA60(2656), tag: (0028, 1090), VR: CS, Length: 4 0xA6C(2668), tag: (0028, 6040), VR: US, Length: 6 0xA7A(2682), tag: (0028, 6100), VR: SQ, Length: 0xA86(2694), tag: (fffe, e000), VR: , Length: 0xA8E(2702), tag: (0028, 6101), VR: CS, Length: 4 0xA9A(2714), tag: (0028, 6110), VR: US, Length: 2 0xAA4(2724), tag: (fffe, e00d), VR: , Length: 0 0xAAC(2732), tag: (fffe, e0dd), VR: , Length: 0 0xAB4(2740), tag: (0029, 0010), VR: LO, Length: 16 0xACC(2764), tag: (0029, 1000), VR: SQ, Length: 0xAD8(2776), tag: (fffe, e000), VR: , Length: 0xAE0(2784), tag: (0029, 0010), VR: LO, Length: 16 0xAF8(2808), tag: (0029, 1001), VR: US, Length: 4 0xB04(2820), tag: (0029, 1002), VR: US, Length: 50 0xB3E(2878), tag: (0029, 1003), VR: FL, Length: 4 0xB4A(2890), tag: (fffe, e00d), VR: , Length: 0 0xB52(2898), tag: (fffe, e0dd), VR: , Length: 0 0xB5A(2906), tag: (5000, 0005), VR: US, Length: 2 0xB64(2916), tag: (5000, 0010), VR: US, Length: 2 0xB6E(2926), tag: (5000, 0020), VR: CS, Length: 4 0xB7A(2938), tag: (5000, 0030), VR: SH, Length: 10 0xB8C(2956), tag: (5000, 0103), VR: US, Length: 2 0xB96(2966), tag: (5000, 0104), VR: US, Length: 0 0xB9E(2974), tag: (5000, 0105), VR: US, Length: 0 0xBA6(2982), tag: (5000, 0106), VR: US, Length: 0 0xBAE(2990), tag: (5000, 0110), VR: US, Length: 4 0xBBA(3002), tag: (5000, 0112), VR: US, Length: 2 0xBC4(3012), tag: (5000, 0114), VR: US, Length: 2 0xBCE(3022), tag: (5000, 3000), VR: OW, Length: 7680 0x29DA(10714), tag: (7fe0, 0010), VR: OB, Length: 0x29E6(10726), tag: (fffe, e000), VR: , Length: 384 0x2B6E(11118), tag: (fffe, e000), VR: , Length: 17912 0x716E(29038), tag: (fffe, e000), VR: , Length: 18680 0xBA6E(47726), tag: (fffe, e000), VR: , Length: 18644 0x1034A(66378), tag: (fffe, e000), VR: , Length: 17820 0x148EE(84206), tag: (fffe, e000), VR: , Length: 16848 0x18AC6(101062), tag: (fffe, e000), VR: , Length: 16308 0x1CA82(117378), tag: (fffe, e000), VR: , Length: 16020 0x2091E(133406), tag: (fffe, e000), VR: , Length: 16088 0x247FE(149502), tag: (fffe, e000), VR: , Length: 16280 0x2879E(165790), tag: (fffe, e000), VR: , Length: 16408 0x2C7BE(182206), tag: (fffe, e000), VR: , Length: 16568 0x3087E(198782), tag: (fffe, e000), VR: , Length: 16556 0x34932(215346), tag: (fffe, e000), VR: , Length: 16604 0x38A16(231958), tag: (fffe, e000), VR: , Length: 16536 0x3CAB6(248502), tag: (fffe, e000), VR: , Length: 16628 0x40BB2(265138), tag: (fffe, e000), VR: , Length: 16428 0x44BE6(281574), tag: (fffe, e000), VR: , Length: 16328 0x48BB6(297910), tag: (fffe, e000), VR: , Length: 16456 0x4CC06(314374), tag: (fffe, e000), VR: , Length: 16468 0x50C62(330850), tag: (fffe, e000), VR: , Length: 16528 0x54CFA(347386), tag: (fffe, e000), VR: , Length: 16576 0x58DC2(363970), tag: (fffe, e000), VR: , Length: 16648 0x5CED2(380626), tag: (fffe, e000), VR: , Length: 16684 0x61006(397318), tag: (fffe, e000), VR: , Length: 16788 0x651A2(414114), tag: (fffe, e000), VR: , Length: 16852 0x6937E(430974), tag: (fffe, e000), VR: , Length: 16960 0x6D5C6(447942), tag: (fffe, e000), VR: , Length: 17164 0x718DA(465114), tag: (fffe, e000), VR: , Length: 17376 0x75CC2(482498), tag: (fffe, e000), VR: , Length: 17428 0x7A0DE(499934), tag: (fffe, e000), VR: , Length: 17392 0x7E4D6(517334), tag: (fffe, e000), VR: , Length: 17240 0x82836(534582), tag: (fffe, e000), VR: , Length: 17216 0x86B7E(551806), tag: (fffe, e000), VR: , Length: 17272 0x8AEFE(569086), tag: (fffe, e000), VR: , Length: 17416 0x8F30E(586510), tag: (fffe, e000), VR: , Length: 17480 0x9375E(603998), tag: (fffe, e000), VR: , Length: 17568 0x97C06(621574), tag: (fffe, e000), VR: , Length: 17572 0x9C0B2(639154), tag: (fffe, e000), VR: , Length: 17676 0xA05C6(656838), tag: (fffe, e000), VR: , Length: 17612 0xA4A9A(674458), tag: (fffe, e000), VR: , Length: 17760 0xA9002(692226), tag: (fffe, e000), VR: , Length: 17716 0xAD53E(709950), tag: (fffe, e000), VR: , Length: 17800 0xB1ACE(727758), tag: (fffe, e000), VR: , Length: 17952 0xB60F6(745718), tag: (fffe, e000), VR: , Length: 17916 0xBA6FA(763642), tag: (fffe, e000), VR: , Length: 17984 0xBED42(781634), tag: (fffe, e000), VR: , Length: 18120 0xC3412(799762), tag: (fffe, e000), VR: , Length: 18112 0xC7ADA(817882), tag: (fffe, e000), VR: , Length: 18008 0xCC13A(835898), tag: (fffe, e000), VR: , Length: 17984 0xD0782(853890), tag: (fffe, e000), VR: , Length: 17908 0xD4D7E(871806), tag: (fffe, e000), VR: , Length: 17960 0xD93AE(889774), tag: (fffe, e000), VR: , Length: 18112 0xDDA76(907894), tag: (fffe, e000), VR: , Length: 18256 0xE21CE(926158), tag: (fffe, e000), VR: , Length: 18240 0xE6916(944406), tag: (fffe, e000), VR: , Length: 18168 0xEB016(962582), tag: (fffe, e000), VR: , Length: 18092 0xEF6CA(980682), tag: (fffe, e000), VR: , Length: 18052 0xF3D56(998742), tag: (fffe, e000), VR: , Length: 18008 0xF83B6(1016758), tag: (fffe, e000), VR: , Length: 18048 0xFCA3E(1034814), tag: (fffe, e000), VR: , Length: 17976 0x10107E(1052798), tag: (fffe, e000), VR: , Length: 18056 0x10570E(1070862), tag: (fffe, e000), VR: , Length: 18040 0x109D8E(1088910), tag: (fffe, e000), VR: , Length: 18068 0x10E42A(1106986), tag: (fffe, e000), VR: , Length: 17984 0x112A72(1124978), tag: (fffe, e000), VR: , Length: 18012 0x1170D6(1142998), tag: (fffe, e000), VR: , Length: 18024 0x11B746(1161030), tag: (fffe, e000), VR: , Length: 18048 0x11FDCE(1179086), tag: (fffe, e000), VR: , Length: 17940 0x1243EA(1197034), tag: (fffe, e000), VR: , Length: 17984 0x128A32(1215026), tag: (fffe, e000), VR: , Length: 17952 0x12D05A(1232986), tag: (fffe, e000), VR: , Length: 18048 0x1316E2(1251042), tag: (fffe, e000), VR: , Length: 17996 0x135D36(1269046), tag: (fffe, e000), VR: , Length: 18040 0x13A3B6(1287094), tag: (fffe, e000), VR: , Length: 18048 0x13EA3E(1305150), tag: (fffe, e000), VR: , Length: 17936 0x143056(1323094), tag: (fffe, e000), VR: , Length: 18020 0x1476C2(1341122), tag: (fffe, e000), VR: , Length: 18112 0x14BD8A(1359242), tag: (fffe, e000), VR: , Length: 18200 0x1504AA(1377450), tag: (fffe, e000), VR: , Length: 18128 0x154B82(1395586), tag: (fffe, e000), VR: , Length: 18164 0x15927E(1413758), tag: (fffe, e000), VR: , Length: 18192 0x15D996(1431958), tag: (fffe, e000), VR: , Length: 18108 0x16205A(1450074), tag: (fffe, e000), VR: , Length: 18088 0x16670A(1468170), tag: (fffe, e000), VR: , Length: 18116 0x16ADD6(1486294), tag: (fffe, e000), VR: , Length: 18032 0x16F44E(1504334), tag: (fffe, e000), VR: , Length: 17976 0x173A8E(1522318), tag: (fffe, e000), VR: , Length: 18096 0x178146(1540422), tag: (fffe, e000), VR: , Length: 18016 0x17C7AE(1558446), tag: (fffe, e000), VR: , Length: 18032 0x180E26(1576486), tag: (fffe, e000), VR: , Length: 17996 0x18547A(1594490), tag: (fffe, e000), VR: , Length: 17964 0x189AAE(1612462), tag: (fffe, e000), VR: , Length: 17992 0x18E0FE(1630462), tag: (fffe, e000), VR: , Length: 17996 0x192752(1648466), tag: (fffe, e000), VR: , Length: 17952 0x196D7A(1666426), tag: (fffe, e000), VR: , Length: 17972 0x19B3B6(1684406), tag: (fffe, e000), VR: , Length: 17976 0x19F9F6(1702390), tag: (fffe, e0dd), VR: , Length: 0 このように,DICOMファイルの終端までエラーなく読むことができました. 0x29DA(10714), tag: (7fe0, 0010)に格納されている情報が画像データとなっています. 次回 次回以降,各tagに入っているデータをチェックしていきましょう なお,今回のコードでは,SQや項目タグの長さが明示されている場合にはうまく読み取れないかもしれません. 次回以降も,初回に記載したDICOMデータファイルで解読を進めます.
- 投稿日:2021-05-16T03:26:13+09:00
【Swift】サロンでRxSwiftを教えてもらったのでまとめてみた
はじめに 随時更新予定 今回は、オンラインサロンでRxSwiftを教えてもらっているので、その中で自分の学びになったことを簡潔にまとめてみたいと思います。 学んだこと ・どれがBLなのかを考える ・Result型で値を返すことができる enum CalculateError: Error { case numberToDivideIsZero } func calculate(num1: Double, num2: Double) -> Result<Double, CalculateError> { if num2.isZero { return .failure(.numberToDivideIsZero) } return .success(num1 / num2) } let num1 = 10.0 let num2 = 0.0 switch calculate(num1: num1, num2: num2) { case .success(let result): print(result) case .failure(let error): print(error) } ・メインスレッドで実行したいものには、Driverを使う(イベントをながすものはRelay) ・Driverをbindするときは、drive(onNext:)を使う ・Relayはaccept()でイベント通知する relay.accept("text") ・Alertなどはイベントとして、enumを用意する enum Event { case showAlert(String) } ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ おわりに おわりです。
- 投稿日:2021-05-16T02:16:43+09:00
聞き取りドリルのプライバシーポリシー
プライバシーポリシー tsuji.daisuke(以下,「当社」といいます。)は,本アプリケーション上で提供するサービス(以下,「本サービス」といいます。)における,ユーザーの個人情報の取扱いについて,以下のとおりプライバシーポリシー(以下,「本ポリシー」といいます。)を定めます。 第1条(個人情報) 「個人情報」とは,個人情報保護法にいう「個人情報」を指すものとし,生存する個人に関する情報であって,当該情報に含まれる氏名,生年月日,住所,電話番号,連絡先その他の記述等により特定の個人を識別できる情報及び容貌,指紋,声紋にかかるデータ,及び健康保険証の保険者番号などの当該情報単体から特定の個人を識別できる情報(個人識別情報)を指します。 第2条(個人情報の収集方法) 当社は,ユーザーが利用登録をする際に氏名,生年月日,住所,電話番号,メールアドレス,銀行口座番号,クレジットカード番号,運転免許証番号などの個人情報をお尋ねすることがあります。また,ユーザーと提携先などとの間でなされたユーザーの個人情報を含む取引記録や決済に関する情報を,当社の提携先(情報提供元,広告主,広告配信先などを含みます。以下,「提携先」といいます。)などから収集することがあります。 第3条(個人情報を収集・利用する目的) 当社が個人情報を収集・利用する目的は,以下のとおりです。 当社サービスの提供・運営のため ユーザーからのお問い合わせに回答するため(本人確認を行うことを含む) ユーザーが利用中のサービスの新機能,更新情報,キャンペーン等及び当社が提供する他のサービスの案内のメールを送付するため メンテナンス,重要なお知らせなど必要に応じたご連絡のため 利用規約に違反したユーザーや,不正・不当な目的でサービスを利用しようとするユーザーの特定をし,ご利用をお断りするため ユーザーにご自身の登録情報の閲覧や変更,削除,ご利用状況の閲覧を行っていただくため 有料サービスにおいて,ユーザーに利用料金を請求するため 上記の利用目的に付随する目的 第4条(利用目的の変更) 当社は,利用目的が変更前と関連性を有すると合理的に認められる場合に限り,個人情報の利用目的を変更するものとします。 利用目的の変更を行った場合には,変更後の目的について,当社所定の方法により,ユーザーに通知し,または本ウェブサイト上に公表するものとします。 第5条(個人情報の第三者提供) 当社は,次に掲げる場合を除いて,あらかじめユーザーの同意を得ることなく,第三者に個人情報を提供することはありません。ただし,個人情報保護法その他の法令で認められる場合を除きます。 人の生命,身体または財産の保護のために必要がある場合であって,本人の同意を得ることが困難であるとき 公衆衛生の向上または児童の健全な育成の推進のために特に必要がある場合であって,本人の同意を得ることが困難であるとき 国の機関もしくは地方公共団体またはその委託を受けた者が法令の定める事務を遂行することに対して協力する必要がある場合であって,本人の同意を得ることにより当該事務の遂行に支障を及ぼすおそれがあるとき 予め次の事項を告知あるいは公表し,かつ当社が個人情報保護委員会に届出をしたとき 利用目的に第三者への提供を含むこと 第三者に提供されるデータの項目 第三者への提供の手段または方法 本人の求めに応じて個人情報の第三者への提供を停止すること 本人の求めを受け付ける方法 前項の定めにかかわらず,次に掲げる場合には,当該情報の提供先は第三者に該当しないものとします。 当社が利用目的の達成に必要な範囲内において個人情報の取扱いの全部または一部を委託する場合 合併その他の事由による事業の承継に伴って個人情報が提供される場合 個人情報を特定の者との間で共同して利用する場合であって,その旨並びに共同して利用される個人情報の項目,共同して利用する者の範囲,利用する者の利用目的および当該個人情報の管理について責任を有する者の氏名または名称について,あらかじめ本人に通知し,または本人が容易に知り得る状態に置いた場合 第6条(個人情報の開示) 当社は,本人から個人情報の開示を求められたときは,本人に対し,遅滞なくこれを開示します。ただし,開示することにより次のいずれかに該当する場合は,その全部または一部を開示しないこともあり,開示しない決定をした場合には,その旨を遅滞なく通知します。なお,個人情報の開示に際しては,1件あたり1,000円の手数料を申し受けます。 本人または第三者の生命,身体,財産その他の権利利益を害するおそれがある場合 当社の業務の適正な実施に著しい支障を及ぼすおそれがある場合 その他法令に違反することとなる場合 前項の定めにかかわらず,履歴情報および特性情報などの個人情報以外の情報については,原則として開示いたしません。 第7条(個人情報の訂正および削除) ユーザーは,当社の保有する自己の個人情報が誤った情報である場合には,当社が定める手続きにより,当社に対して個人情報の訂正,追加または削除(以下,「訂正等」といいます。)を請求することができます。 当社は,ユーザーから前項の請求を受けてその請求に応じる必要があると判断した場合には,遅滞なく,当該個人情報の訂正等を行うものとします。 当社は,前項の規定に基づき訂正等を行った場合,または訂正等を行わない旨の決定をしたときは遅滞なく,これをユーザーに通知します。 第8条(個人情報の利用停止等) 当社は,本人から,個人情報が,利用目的の範囲を超えて取り扱われているという理由,または不正の手段により取得されたものであるという理由により,その利用の停止または消去(以下,「利用停止等」といいます。)を求められた場合には,遅滞なく必要な調査を行います。 前項の調査結果に基づき,その請求に応じる必要があると判断した場合には,遅滞なく,当該個人情報の利用停止等を行います。 当社は,前項の規定に基づき利用停止等を行った場合,または利用停止等を行わない旨の決定をしたときは,遅滞なく,これをユーザーに通知します。 前2項にかかわらず,利用停止等に多額の費用を有する場合その他利用停止等を行うことが困難な場合であって,ユーザーの権利利益を保護するために必要なこれに代わるべき措置をとれる場合は,この代替策を講じるものとします。 第9条(プライバシーポリシーの変更) 本ポリシーの内容は,法令その他本ポリシーに別段の定めのある事項を除いて,ユーザーに通知することなく,変更することができるものとします。 当社が別途定める場合を除いて,変更後のプライバシーポリシーは,本ウェブサイトに掲載したときから効力を生じるものとします。 第10条(お問い合わせ窓口) 本ポリシーに関するお問い合わせは,下記の窓口までお願いいたします。 社名:tsuji.daisuke Eメールアドレス:tsuji.daisuke75@gmail.com
- 投稿日:2021-05-16T01:09:13+09:00
【Swift】MVPを簡単にまとめてみる
はじめに 今回は、MVPアーキテクチャについて簡単にまとめていきたいと思います。 MVPとは 特徴 ・Model, View, Presenterにより分割されるGUIアーキテクチャ ・データ同期する方法に、PassiveViewとSupervisingControllerの二つがある フロー同期とオブザーバー同期 まず、PassiveViewとSupervisingControllerを理解する前に、フロー同期とオブザーバー同期の説明をします。 フロー同期 上位モジュールのデータを下位モジュールに適宜セットして同期する同期方法 つまり、手続き的な同期方法。 メリット ・画面遷移間でデータの受け渡しなどでデータフローを追いやすい →上位モジュール(遷移先)が下位モジュール(遷移元)の参照を持つことができるため、有効 デメリット ・共通したデータを参照しているすべての箇所の参照を持っておかなければいけなくなるので、参照の管理が煩雑になりやすい。 オブザーバー同期 下位モジュール(監視元)が上位モジュール(監視先)からObserverパターンを使い送られてくるイベントを受け取ってデータを同期させる同期方法。 つまり、宣言的な同期方法である。 メリット ・共通した監視先を持つ複数の箇所でデータ同期をしやすい ・上位モジュールが下位モジュールの参照をもつ必要がなく、かつコンポーネント間の距離に関係なくデータ同期ができる。(互いに疎結合) デメリット ・データ変更のたびに同期処理が実行されるため、いつデータ同期が行われるのかが追いづらい。 Model MVC, MVVMなどもModelと同じ。 View View+ViewController Presenter ViewとModelの仲介役 プレゼンテーションロジックを担う 1Viewにつき、1Presenter作成することが多い PresenterからはViewをweak参照することが多い PassiveView Presenter→Viewでフロー同期を使う MVPのフロー同期例 myNameLabel.text = "REON" tableView.reloadData() Viewはすべてのユーザー入力イベントをPresenterに渡す。Presenterは入力に応じてプレゼンテーションロジックを処理して、Viewに対して手続き的な描画処理を出す。 ViewはPresenterの指示によってのみ描画処理を行い、Viewを基準とした描画処理は行わない。 SupervisingController フロー同期とオブザーバー同期の両方を使う Presenter→View間をフロー同期 Model→View間をオブザーバー同期する 監視元であるViewが監視先であるModelからNotificationCenterなどで送られるイベントを受け取って描画処理を行うことが例として挙げられる。 Viewは簡単なプレゼンテーションロジックを持つ。 Presenterは複雑なプレゼンテーションロジックを持つ。 ModelはPresenterによって呼び出され、Viewに対して描画に必要なデータをイベント通知によって受け渡しする。 SupervisingControllerはModel→View間にオブザーバーパターン同期を用いることで、処理を簡潔にかける。 おわりに MVPはPassiveViewかSupervisingControllerのどちらを使うのかがミソですね。