20210916のSwiftに関する記事は10件です。

【Swift5】Firebase Authでメールの重複チェックを行う

背景 Firebaseでメールの重複チェックを行う時に、Firestoreのuserデータから検索を行う手段が多かったので Firebase Authのみで重複チェックを行う方法を記載しようと思いました。 実装 import Firebase func checkEmailExist(email: String, completion: @escaping ((Result<Bool, Error>) -> Void) { // SignInの方法を取得するメソッドを使用 Auth.auth().fetchSignInMethods(forEmail: email, completion: { (method, error) in if let err = error { completion(.failure(err)) return } // すでにメールアドレスの認証などされている場合は // methodの中に"EmailLink"や"Email"などの認証方法が入る guard let method = method, !method.isEmpty else { // 何もない場合は存在しない completion(.success(false)) return } // methodに認証方法が入っている場合は存在する判定 completion(.success(true)) }) } 上記のメソッドをメールアドレスを入力しているViewControllerなどから呼び出してあげれば良いです! Result型がわからない場合に備えて、ViewControllerでの実装例も載せておきます。 実装例 import UIKit class SampleViewController: UIViewController { @IBOutlet var textField: UITextField! override func viewDidLoad() { super.viewDidLoad() } @IBAction func tapButton(_ sender: Any) { guard let email = textField.text else { return } checkEmailExist(email: email, completion: { result in switch result { case let .success(isExist): if (isExist) { // メールアドレスが重複した時の処理 } else { // メールアドレスが重複していないので、認証処理を行う } case let .failure(error): // エラー時のハンドリング(ダイアログ出したり) } }) } } 他の実装方法について FirestoreのDB構造 Users - ユーザーA - hogehoge1@sss.com - ユーザーB - hogehoge2@sss.com 上記のようにFirestoreのUsersコレクションを作成してメールアドレスで検索する方法もありますが、 セキュリティ的にメールアドレスを全てのユーザーが見れる状態はあまり良い状態ではない(そういうアプリであれば良いですが) と思うので、こちらの方法はあまり推奨できないと思います。 良いFirebaseライフを!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

guard let x = y else { return }使ってみよう(足し算するだけの簡単App)

今回の内容 コードと簡単解説 guard let x = y else { return }を使用して関数を作る。 この関数では、Int型の引数upperTextとunderTextに入ってきた値を、nilで無いなら足し算をして、配列に入れTableViewに表示できるようにします。 引数upperTextとunderTextの両方または片方でもnilだった場合、アラートで知らせる様にします。 func addition(upperText:Int?,underText:Int?){ guard let resultUpperText = upperText else { self.showAlert(); return } //nilならアラートを表示して後続の処理をさせない guard let resultUnderText = underText else { self.showAlert(); return } //nilならアラートを表示して後続の処理をさせない cellLeftLabelContentsArray.append(String(resultUpperText + resultUnderText)) upperTextField.text = "" underTextField.text = "" tableView.reloadData() } 全体コード TableViewなどはMain.storyboardで下の画像の様に作っています。 import UIKit class ViewController: UIViewController { @IBOutlet weak var upperSystemIndigoView: UIView! @IBOutlet weak var underSystemIndigoView: UIView! @IBOutlet weak var upperTextField: UITextField! @IBOutlet weak var underTextField: UITextField! @IBOutlet weak var addButton: UIButton! @IBOutlet weak var tableView: UITableView! var cellLeftLabelContentsArray = [String]() override func viewDidLoad() { super.viewDidLoad() upperSystemIndigoView.layer.cornerRadius = 120.0 //角を丸くする upperSystemIndigoView.layer.maskedCorners = [.layerMinXMaxYCorner] //丸くする角を指定する upperSystemIndigoView.layer.shadowOffset = CGSize(width: 3, height: 3) //影をつける upperSystemIndigoView.layer.shadowRadius = 10.0 //影のぼかし具合を設定 upperSystemIndigoView.layer.shadowOpacity = 0.8 //影の透明度を設定 underSystemIndigoView.layer.cornerRadius = 120.0 underSystemIndigoView.layer.maskedCorners = [.layerMaxXMinYCorner] underSystemIndigoView.layer.shadowOffset = CGSize(width: 3, height: 3) underSystemIndigoView.layer.shadowRadius = 10.0 underSystemIndigoView.layer.shadowOpacity = 0.8 upperTextField.layer.borderColor = UIColor.systemIndigo.cgColor upperTextField.layer.borderWidth = 1.0 upperTextField.borderStyle = .roundedRect underTextField.layer.borderColor = UIColor.systemIndigo.cgColor underTextField.layer.borderWidth = 1.0 addButton.layer.cornerRadius = 10.0 addButton.layer.shadowOffset = CGSize(width: 1, height: 1) addButton.layer.shadowRadius = 10.0 addButton.layer.shadowOpacity = 0.4 tableView.layer.borderColor = UIColor.systemIndigo.cgColor tableView.layer.borderWidth = 1.0 tableView.dataSource = self } @IBAction func add(_ sender: UIButton) { addition(upperText: Int(upperTextField.text!), underText: Int(underTextField.text!)) } func addition(upperText:Int?,underText:Int?){ guard let resultUpperText = upperText else { self.showAlert(); return } guard let resultUnderText = underText else { self.showAlert(); return } cellLeftLabelContentsArray.append(String(resultUpperText + resultUnderText)) upperTextField.text = "" underTextField.text = "" tableView.reloadData() } func showAlert(){ let usualAlert = {() -> UIAlertController in let alert = UIAlertController(title: "nilを見つけたよ", message: "入力されているか確認してね", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "No", style: .destructive, handler: { _ in print("No") })) alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in print("Yes") })) return alert }() self.present(usualAlert, animated: true, completion: nil) } } extension ViewController:UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cellLeftLabelContentsArray.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let cellLeftLabel = cell.contentView.viewWithTag(1) as! UILabel cellLeftLabel.text = cellLeftLabelContentsArray[indexPath.row] return cell } } 終わり 自分がそうだったのですが、勉強を始めて最初の頃は調べても結局あまり理解出来ないことがありました。 理由としては、結局いつ使うの?、どんな使い方をするの?って感じでしたね。 なので、アプリとは呼べない様な簡単なコードを何度も調べながら書いて、やっと理解出来たかな?って感じでした。(分からないこともあるけど、それも楽しい) ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] typealiasって?

はじめに typealiasという文字をを見たときなんか、ふわっとエイリアンの顔が頭に浮かんだのは僕だけでしょうか。多分仲間がいると思っています。 typealiasは今までにも見たことはあったのですが、どういう場面で使えるのか分かっていなかったので調べてみました。この記事では使い方の一部をご紹介します。 ざっくり理解するtypealias typealiasはざっくり、IntやStringなどの型に自分用の名前をつけて使えるよー。というものです。 例えばInt型の数値を扱うときにIntっていう型の名前はちょっとダサいな。と思ったとしましょう。 そこでtypealiasの出番です。これを使うとIntの性能を持ったあなた好みの名前の型を作れます。 以下に例を示します。 typealias Explosion = Int // これでIntの性能を持ったExplosionという型が作れました! // ExplosioinはIntと同じ特徴なので当然整数を代入することができます。 let num1: Explosion = 3 let num2: Explosion = 5 print(num1 + num2) // 実行結果: 8 // 計算も問題なく行えます let num3: Explosion = 3 let num4: Int = 5 print(num3 + num4) // 実行結果: 8 // ExplosionはIntと同じ特徴を持っているのでIntとの足し算も問題なくできます 他にもたくさん使い方がありますが、この記事ではこれくらいにしておきます。 もっと知りたい方は以下の記事に詳しく書かれています。 https://qiita.com/shimesaba/items/4d19a7e4c67caca73603 使用例 型の名前を変えられるということは分かったと思いますが、これをどのように使うと便利なのかの例を以下に示したいと思います。 あなたは整数のみの掛け算ができるシステムを以下のように作ったとします。 ※Intは今回わかりやすくするためにわざとつけています。 let num1: Int = 4 let num2: Int = 6 print(num1 * num2) 実行結果: 24 その後、あなたは今まで整数のみの掛け算だったところを、小数も扱えるようにしたいと考えました。 そして、以下のようにコードをなおしました。 let num1: Double = 4.2 let num2: Double = 6.1 print(num1 * num2) 実行結果: 25.62 今回はIntをDoubleに変えるところが2カ所だけでしたが、もし修正しなきゃいけない箇所がもっとたくさんあったら、修正作業はかなり面倒ですよね。ここでtypealiasが登場します。以下のコードをご覧ください。 typealias NumType = Int let num1: NumType = 4 let num2: NumType = 6 print(num1 * num2) 実行結果: 24 このようにtypealiasを使うことでNumTypeはIntの特徴を持っているのでnum1、num2はInt型のように使用できます。 そして、この後に小数も扱えるようにしたい!と思ったときには以下のようにします。 typealias NumType = Double // ←ここだけ変えればいい! let num1: NumType = 4.2 let num2: NumType = 6.1 print(num1 * num2) 実行結果: 25.62 typealiasを使うことで修正箇所は1カ所だけでよくなります! このように、後から型の変更をするかもしれないものを初めからtypealiasを用いて宣言しておくことで、後の修正で楽になることがあります。 おわりに 少しでも参考になれば嬉しく思います。ではでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【プライバシーポリシー】シンプル・TODOリスト

開発者(ryoga mori)は、本アプリ上で提供するサービスにおけるプライバシー情報の取り扱いについて、以下の通りプライバシーポリシーを定めます。 プライバシー情報 本アプリではプライバシー情報の収集並びに第三者に公開することは一切ありません。 なお、アプリの利便性向上のため、匿名でのアクセス解析を利用しています。(アプリのクラッシュ情報などの収集し、バグの早期対応に役立てるため。) とくに入力情報などの取得・保存は一切行なっておりませんので、安心してご利用いただけます。 アップデート情報 最新アップデートは、バージョン0.1.0です。 お問い合わせ 本アプリに関するご不明点、ご要望等については、コメントから受け付けております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WIP] SOLID原則を腹に落とし込む

みなさんこんにちは。都内のIT企業でiOSアプリを開発している @zrn-ns です。 突然ですが皆さん、保守性の高いコードを書けていますか? 「SOLID原則」という原則はよく目にしてはいたのですが、なんとなく雰囲気で理解したつもりになっていたので、ちゃんと文章化して理解を深めたいと思い記事を書きました。 正直まだふわふわしてる感じがしていて、誤った理解をしている箇所があるかもしれません。もし間違いに気づいたら、そっと教えて下さい。 SOLID原則の目的 オブジェクト指向プログラミングの分野において、SOLID(ソリッド)とは、ソフトウェア設計の5つの原則を記憶するための頭字語である。これらの原則は、ソフトウェアをより理解しやすく、より柔軟に、よりメンテナナンス性の高いものにするために考案されたものである。 https://ja.wikipedia.org/wiki/SOLID SOLID原則はRobert C. Martin発案の、よりメンテナンス性の高いコードを書くための5つの原則(下記)の頭文字をとったものです。 S: 単一責任の原則(Single responsibility principle) O: 開放閉鎖の原則(Open–closed principle) L: リスコフの置換原則(Liskov substitution principle) I: インタフェース分離の原則(Interface segregation principle) D: 依存性逆転の原則(Dependency inversion principle) このあとのセクションではそれぞれの原則について、具体例を上げつつ掘り下げていきます。 1. (S)単一責任の原則 単一責任の原則とは、「クラスを変更する理由は1つ以上存在してはならない」、言い換えると「クラスに変更が起こる理由は1つであるべき」という原則です。 https://thinkit.co.jp/article/13274 参考になった記事 https://qiita.com/MinoDriven/items/76307b1b066467cbfd6a なぜ? クラスが複数の責任を持つということは、それだけクラスを変更される理由が増えるということです。A, Bという2つの責任をもつクラスがあったとき、Aの理由によって変更された場合、Bの責任についても満たせていることを確認する必要がでてきます。 多くの変更が入るクラスはそれだけ不安定であり、不安定なクラスに依存したクラスの安定度を下げてしまうことに繋がります。 各クラスを変更する理由を一つにする(責任を一つにする)ことで、クラスの安定度を上げ、コードの修正時の修正範囲を最小限に留める事ができるようになります。 具体例 例を示します。 店舗の従業員を管理するためのアプリに、下記のような Staff クラス(正確には構造体ですが、便宜上クラスと表記します)が存在するとします。 struct Staff: Codable { let id: Int var name: String var introduction: String static func find(byId id: Int) -> Staff? { // ユーザをDBから検索する処理(省略) return nil } func save() { // ユーザをDBに保存する処理(省略) } } このクラスは既に、複数の役割を持っています。 ユーザの属性を保持する役割 ユーザをDBに保存する役割(find, saveメソッド) ユーザの情報をDBに保存するためにエンコード/デコードする役割(Codable) あるとき、このアプリはサーバと連携する機能を実装することになり、 Staff クラスのCodable実装をそのまま使って、APIから取得したJSON形式の従業員情報のパース処理を行うことになったとします。 let response = APIClient.sendRequest(GetUserRequest()) let parsedStoreStaff = JSONDecoder().decode([User].self, from: jsonData) これで、 Staff クラスの役割は4つになりました。 ユーザの属性を保持する役割 ユーザをDBに保存する役割(find, saveメソッド) ユーザの情報をDBに保存するためにエンコード/デコードする役割 APIから取得したユーザデータをモデルにマッピングする役割 ← new!! このクラスは現在かなり不安定な状態です。 各プロパティ(id, name, introduction)はただデータを保持するだけの役割に加えて、DBからデータを取り出すときのマッパーとしての役割、さらにAPIから渡されたデータをパースするためのマッパーとしての役割も担っています。 4つの役割のうちのいずれかがきっかけでこのクラスを変更した場合、他の役割に影響を及ぼす可能性が高いですし、このクラスは多くの機能に依存されることになるため、他のクラスに連鎖的に影響を与える可能性もあります。 この過剰に役割を担った状態を解消するためには、 Staff クラスの役割を別にクラスに逃がす必要があります。 /// 1. ユーザの属性を保持する役割 struct Staff { let id: Int var name: String var introduction: String } /// 2. ユーザをDBに保存する役割(find, saveメソッド) struct StaffStore { static func find(byId id: Int) -> StaffStorageObject? { // ユーザをDBから検索する処理(省略) return nil } static func save(staff: StaffStorageObject) { // ユーザをDBに保存する処理(省略) } } /// 3. ユーザの情報をDBに保存するためにエンコード/デコードする役割 struct StaffResponse: Decodable { let id: Int var name: String var introduction: String } /// 4. APIから取得したユーザデータをモデルにマッピングする役割 struct StaffStorageObject: Codable { let id: Int var name: String var introduction: String } かなりコード量は増えてしまい冗長な感じがするかもしれませんが、それぞれが別の責任を担っています。 例えば、APIから返される従業員情報のプロパティが変化したり、使っているDBの種類が変更になった場合でも、責任ごとにクラスを分離することで、保守する際に変更するコード量を減らし、意図せずバグを埋め込むリスクを減らすことができます。 2. (O)開放/閉鎖の原則 クラス(およびその他のプログラム単位)は - 拡張に対して開いて (open) いなければならず、 - 修正に対して閉じて (closed) いなければならない という設計上の原則である。 開放/閉鎖原則に従ったソフトウェアは、既存のソースコードを変更することなく、振る舞いを変更することができる。 どういう意味? 「修正に対して閉じている」、「拡張に対して開いている」というのはどういうことなのでしょうか。 「修正に対して閉じている」とは、要件の変化や不具合によりモジュール(特定のプログラム単位)を修正する際に、そのモジュールだけ修正すればよく、他のモジュールにに手を入れる必要がないということです。 「拡張に対して開いている」とは、プログラムに新たな機能を追加したい(拡張したい)場合に、既存のプログラムに手を入れずに、新たなモジュールを追加するだけで機能を追加できるということです。 なぜ? 修正に対して閉じていることで、他のモジュールに影響を与えずに修正を行う事ができます。 また、拡張に対して開いていることで、拡張が必要になった際にも、既存のコードやテストを修正することなく、新たなコードを追加する事ができます。 開放閉鎖の原則に則っていないプログラムは、修正や拡張の際に既存のモジュールの修正が必要になるため、モジュールの安定性が低下します。これにより開発やテストの工数が増えたり、バグを生むきっかけになることもあります。 具体例 下記のような、動物に餌やりをするFeederクラスがあったとします。 struct Feeder { static let shared: Feeder = .init() /// 餌やりをする func feed(to animal: Animal) { if animal is Cat { print("Fed ?!") } else if animal is Snake { print("Fed ?!") } else { fatalError("No appropriate feeds?") } } private init() {} } protocol Animal {} struct Cat: Animal {} struct Snake: Animal {} Feeder.shared.feed(to: Cat())  // Fed ?! Feeder.shared.feed(to: Snake()) // Fed ?! feed(to:)メソッドに動物(Animal)を渡すと、餌を与える事ができます。 現状FeederはOCPに則っていません。なぜなら、新たにAnimal protocolを実装したDogクラスを追加する場合、Feeder.feed(to:) メソッドを修正する必要がある(拡張に対して開いていない)為です。 OCPに則り拡張に開いた状態にするには、餌の情報をFeedメソッドから切り出してやる必要があります。 struct Feeder { static let shared: Feeder = .init() func feed(to animal: Animal) { print("Fed \(animal.feed)!") } private init() {} } protocol Animal { var feed: String { get } } struct Cat: Animal { let feed: String = "?" } struct Snake: Animal { let feed: String = "?" } // 新たにDogを追加しても、Feederを変更する必要はない struct Dog: Animal { let feed: String = "?" } Feeder.shared.feed(to: Cat()) // Fed ?! Feeder.shared.feed(to: Snake())// Fed ?! Feeder.shared.feed(to: Dog()) // Fed ?! これでFeederはOCPに則った状態になりました。 3. (L)リスコフの置換原則 S が T の派生型であれば、プログラム内で T 型のオブジェクトが使われている箇所は全て S 型のオブジェクトで置換可能であれ、ということで、この原則が損なわれなければ、プログラムの型システムに照らした妥当性は損なわれない、ということである。 リスコフの置換原則 - Wikipedia なぜ? 継承したクラスで継承元のクラスを置換可能なんて当たり前のことでしょ。と思うかもしれませんが、割とこの原則に違反したコードを書いてしまうことがあります。 リスコフの置換原則に違反した場合、親クラスを使う際に子クラスについて意識しないといけなくなります。 またそのようなコードは開放閉鎖の原則にも違反することになります。 具体例 下記のような、動物の餌やりをするプログラムがあったとします。 // LSPに違反したコード protocol Animal { var name: String { get } func rubbed() } class Cat: Animal { let name = "ニャンちゅう" func rubbed() { print("ニャー!") } } class Dog: Animal { let name = "ワンダー" func rubbed() { print("ワン!") } } class Ant: Animal { let name = "アリス" func rubbed() { fatalError("撫でないで!") } } class PetOwner { static let shared = PetOwner() // 動物を紹介する func introduction(_ animal: Animal) { print("He is \(animal.name). So cute!") } /// 撫でる func rub(_ animal: Animal) { // 具象クラスの内容を知った上で実装する必要がある。 // Ant以外にも撫でられない動物が追加されたとき、忘れずに分岐を追加できるか? if !(animal is Ant) { animal.rubbed() } } } [Animal](arrayLiteral: Cat(), Dog(), Ant()).forEach { PetOwner.shared.introduction($0) PetOwner.shared.rub($0) } 動物を表すAnimalインタフェースと、それを実装した(派生型である)Dog?, Cat?, Ant?型があります。 Animalインタフェースには撫でられたときの振る舞い(rubbed関数)が定義されていますが、Ant型においてはrubbedメソッドが想定どおり実装されておらず(fatalErrorで落としている)、Animal型の代わりにAnt型を使えなくなっているので、LSPに違反しています。 LSPに違反していることで、rubbedメソッドを使う側(PetOwnerのrub関数)ではrubbed関数を呼ぶ前に具体的なクラスの判定が必要になっており、Animal継承型の詳細を知らないと実装ができない状態になっています。 これをLSPに準拠した実装に変えてみます。 /// LSPが守られたコード protocol Animal { var name: String { get } } protocol RubbableAnimal: Animal { func rubbed() } class Cat: RubbableAnimal { let name = "ニャンちゅう" func rubbed() { print("ニャー!") } } class Dog: RubbableAnimal { let name = "ワンダー" func rubbed() { print("ワン!") } } class Ant: Animal { let name = "アリス" func rubbed() { fatalError("撫でないで!") } } class PetOwner { static let shared = PetOwner() // 動物を紹介する func introduction(_ animal: Animal) { print("He is \(animal.name). So cute!") } /// 撫でる /// 型をRubbableAnimalで縛る func rub(_ animal: RubbableAnimal) { // 具体的な型での判定が不要になる animal.rubbed() } } [Animal](arrayLiteral: Cat(), Dog(), Ant()).forEach { PetOwner.shared.introduction($0) if let rubbableAnimal = $0 as? RubbableAnimal { PetOwner.shared.rub(rubbableAnimal) } } LSP違反が発生していたそもそもの原因は、Animalにrubbed関数が定義されていたためです。今回の実装において、必ずしもAnimalを撫でることができないのであれば、Animalにrubbedを定義するべきではありませんでした。 そこで、Animalから撫でる事ができるという性質だけを専用のインタフェース(RubbableAnimal)として切り出し、撫でる事ができる動物はRubbableAnimalに準拠するようにしました。 これにより、PetOwnerのrub関数ではRubbableAnimalインタフェースでアクセスすることができるようになり、具体的な型での判定が不要になりました。 参考 iOS開発の事例に寄せたSOLID原則の解説 リスコフの置換原則とその違反を実例を踏まえて解説 - Qiita 4. (I)インタフェース分離の原則 「インターフェース分離の原則」は、「クライアントに対し、利用しないインターフェースへの依存を強制しないべき」という原則です。 【Clean Architecture】SOLID原則 – インターフェース分離の原則 | 全国個人事業主支援協会 より なぜ? 「インターフェース分離の原則」という名前を聞くたび、「インタフェースを実装と分離すべき(≒実装に対してプログラミングするのではなくインタフェースに対してプログラミングしろ)」という原則なのかな、と思うのですが、これは間違いで、実際には「インタフェースではクライアントに対して必要がない機能は提供すべきでない」という意味の原則です。 ここでいうクライアントとは、インタフェースを使うインタフェースの実装者、およびインタフェースの利用者の両方を指すと考えています。 インタフェース分離の原則(ISP)原則に違反すると、インタフェースの実装者および呼び出す側において下記のような問題が発生します。 インタフェースの実装者: 本来不要なメソッドを実装しなければならなくなる インタフェースの利用者: 本来不要な引数を渡す事になったり、使わない機能に依存することになる 具体例 具体例を示します。 下記のような、乗り物を運転するプログラムがあります。 protocol Vehicle { /// エンジン始動 func startEngine() /// 走る func run() } class Car: Vehicle { func startEngine() { print("PURR!!") } func run() { print("BRRR!!") } } class Driver { func drive(vehicle: Vehicle) { vehicle.startEngine() vehicle.run() } } 乗り物を示すVehicleインタフェース(protocol)と、それを実装した自動車(Car)クラス、それを利用する運転手(Driver)クラスが定義されています。 このプログラムを拡張して飛行機の運転もできるようにしたくなったとします。 // ISPに違反したコード protocol Vehicle { /// エンジン始動 func startEngine() /// 走る func run() /// 追加した 飛ぶ 関数 func fly() } class Car: Vehicle { func startEngine() { print("PURR!!") } func run() { print("BRRR!!") } func fly() { preconditionFailure("車は飛びません!") } } class Airplane: Vehicle { func startEngine() { print("PURR!!") } func run() { print("WHIR!!") } func fly() { print("ZOOM!!") } } class Driver { func drive(vehicle: Vehicle) { vehicle.startEngine() vehicle.run() // 車によって飛べるものと飛べないものがある if let plane = vehicle as? Airplane { plane.fly() } } } Vehicleインタフェースに新たにfly関数を追加し、Vehicleインタフェースを実装したAirplaneクラスを実装しました。 しかし、既存の自動車(Car)クラスは飛ぶことができないので、Carクラスではこのメソッドを使えないようにしました。 この実装はISPに違反しています。インタフェースを実装する側(Car, Airplane)では不要なメソッドの実装を強要されており、インタフェースを利用する側(Driver)としては具象クラスを意識した実装をせざるを得ない状態になっています。 ISPに準拠させるために、インタフェースを適切に分離してみます。 // ISPが守られたコード protocol Vehicle { /// エンジン始動 func startEngine() /// 走る func run() } /// 航空機プロトコルを新たに作成し、fly()はこちらに定義 protocol Aircraft: Vehicle { /// 飛ぶ func fly() } class Car: Vehicle { func startEngine() { print("PURR!!") } func run() { print("BRRR!!") } } class Airplane: Aircraft { func startEngine() { print("PURR!!") } func run() { print("WHIR!!") } func fly() { print("ZOOM!!") } } class Driver { func drive(vehicle: Vehicle) { vehicle.startEngine() vehicle.run() } } class Pilot { func drive(airplane: Airplane) { airplane.startEngine() airplane.run() airplane.fly() } } 車両(Vehicle)と 航空機(Aircraft)のインタフェースを分離したことでISPが守られた状態になり、インタフェースを実装する側(Car, Airplane)では不要なメソッドの実装をする必要はなくなり、インタフェースを利用する側(Driver, Pilot)では具象型の判定が不要となりました。 参考 【Clean Architecture】SOLID原則 – インターフェース分離の原則 | 全国個人事業主支援協会 5. (D)依存性逆転の原則 オブジェクト指向設計において、依存性逆転の原則、または依存関係逆転の原則[1]とはソフトウエアモジュールを疎結合に保つための特定の形式を指す用語。 この原則に従うとソフトウェアの振る舞いを定義する上位レベルのモジュールから下位レベルモジュールへの従来の依存関係は逆転し、結果として下位レベルモジュールの実装の詳細から上位レベルモジュールを独立に保つことができるようになる。 この原則で述べられていることは以下の2つである: - A. 上位レベルのモジュールは下位レベルのモジュールに依存すべきではない。両方とも抽象(abstractions)に依存すべきである。 - B. 抽象は詳細に依存してはならない。詳細が抽象に依存すべきである。 依存性逆転の原則 - Wikipedia なぜ? 上位モジュールは下位のモジュールを呼び出す立場のモジュールです。 通常、深く考えずにオブジェクト指向でプログラムを実装した場合、プログラムの依存の向きは、呼び出しに従った向きになるかと思います。 しかし保守性の観点では、上位モジュールと下位モジュール間では具象に依存することは避けるべきです。上位/下位モジュール感で具象に依存していると、下位モジュールを変更したときに、上位モジュール側も変更が必要になるためです。 具象に依存せず抽象に依存させるために、依存性の逆転を利用する事ができます。 具体例 具体例を示します。 下記のような、カメラと、写真データを保存する内蔵ストレージであるHDDクラスを考えます。 // DIPに違反したコード struct HDD { func save(image: UIImage) { // HDDに保存する処理 } } struct Camera { let storage: HDD func takePhoto() { let image: UIImage = capture() storage.save(image: image) } private func capture() -> UIImage { // 〜〜省略〜〜 return UIImage() } } let hddCamera: Camera = .init(storage: HDD()) hddCamera.takePhoto() CameraがHDDを使用しているので、Cameraが上位モジュール、HDDが下位モジュールです。 これは前項で話した、上位モジュールが下位モジュールの具象に依存した状態です。 もし後々写真データの保存先をHDDからMicroSDへ変更したくなった場合、下記のようにCameraクラスを変更する必要が出てきます。 // DIPに違反したコード struct HDD { func save(image: UIImage) { // HDDに保存する処理 } } struct MicroSD { func save(image: UIImage) { // MicroSDに保存する処理 } } struct Camera { let storage: MicroSD func takePhoto() { let image: UIImage = capture() storage.save(image: image) } private func capture() -> UIImage { // 〜〜省略〜〜 return UIImage() } } let camera: Camera = .init(storage: MicroSD()) camera.takePhoto() これは使う側が使われる側の具象(クラス)に依存しているために発生している問題のため、抽象(インタフェース)に依存させるようにすれば解消します。 具体的には、下記のようにStorageというインタフェースを定義し、CameraをHDD(具象)ではなくStorage(抽象)に依存するようにします。 // DIPに準拠したコード protocol Storage { func save(image: UIImage) } struct HDD: Storage { func save(image: UIImage) { // HDDに保存する処理 } } struct MicroSD: Storage { func save(image: UIImage) { // SDカードに保存する処理 } } struct Camera { let storage: Storage func takePhoto() { let image: UIImage = capture() storage.save(image: image) } private func capture() -> UIImage { // 〜〜省略〜〜 return UIImage() } } let hddCamera: Camera = .init(storage: HDD()) hddCamera.takePhoto() let microSdCamera: Camera = .init(storage: MicroSD()) microSdCamera.takePhoto() 抽象に依存するようになったことで、上位モジュールと下位モジュールの間が疎結合になり、下位モジュールを変更したとしても、上位レイヤー側を変更する必要がなくなり、保守性が向上しました。 参考 依存性逆転の原則 - Wikipedia 依存関係逆転の原則の重要性について. こんにちは!eurekaのAPIチームでエンジニアをやっている@rikiiです。… | by riki(Rikitake) | Eureka Engineering | Medium 依存関係逆転の原則(DIP) – 野生のプログラマZ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift で Maximum Subarray Problem(最大部分配列問題)を Kadane's Algorithm を解く

問題 LeetCode 53. Maximum Subarray: https://leetcode.com/problems/maximum-subarray/ 解法 Kadane's algorithmで解く。部分問題を下記のように定義する list の 0 から n までの間で、listのn番目の要素を必ず含んだ最大部分配列 愚直にコードで書くとこうなる。 func maxSubArray(_ nums: [Int]) -> Int { var dp = [Int](repeating: -1, count: nums.count) dp[0] = nums[0] for i in 1..<nums.count { dp[i] = max(dp[i-1] + nums[i], nums[i]) // relaxation } return dp.max()! } ポイント 部分問題の定義にて「n番目の要素を必ず含んだ」部分配列の総和という考えがキモとなる。 例えば、下記のケースを考える list = [10, -999, 20, 40] n=2 (val=20) このときの選択肢は下記のどちらかになる。 0~n-1までの配列を含めると、 -969 (= 10 + -999 + 20 ) 0~n-1までの配列を含めないと、 20 この場合は最大数の20が選択される 改善 一般的な動的計画法だと答えは大抵は dp の末尾に答えが入るケースが多いけど、このケースの場合は必ずしもそうならない。 なぜなら、dp に保存されてるのは「nを含む」0~nの中の最大配列総和だから。dpの末尾だと「必ずn番目の要素を含んだ最大部分配列」が答えとして出てくる。 なので、return するときに、dpの中からmax で探してる。 return dp.max()! 改善として、for の中で合わせてmaxも保存しておく。そうなると、O(2n)からO(n)になる。 最終的にコードは下記のようになる。 func maxSubArray(_ nums: [Int]) -> Int { var dp = [Int](repeating: -1, count: nums.count) dp[0] = nums[0] var maxSoFar = nums[0] // <= 追加 for i in 1..<nums.count { dp[i] = max(dp[i-1] + nums[i], nums[i]) maxSoFar = max(maxSoFar, dp[i]) // <= 追加 } return maxSoFar } REF https://leetcode.com/problems/maximum-subarray/discuss/20193/DP-solution-and-some-thoughts https://engineer.yeele.net/algorithm/leetcode-easy-53-maximum-subarray/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift で Kadane's Algorithm 使って Maximum Subarray(最大部分配列)

問題 LeetCode 53. Maximum Subarray: https://leetcode.com/problems/maximum-subarray/ 解法 Kadane's algorithmで解く。動的計画法なので部分問題に分割する。部分問題は下記のように定義する。 list の 0 から n までの間で、listのn番目の要素を含んだ 最大部分配列 愚直にコードで書くとこうなる。 func maxSubArray(_ nums: [Int]) -> Int { var dp = [Int](repeating: -1, count: nums.count) dp[0] = nums[0] for i in 1..<nums.count { dp[i] = max(dp[i-1] + nums[i], nums[i]) // relaxation } return dp.max()! } ポイント 部分問題の定義にて「n番目の要素を含んだ」部分配列の総和という考えがキモとなる。 例えば、下記のケースを考える list = [10, -11, 21, 40] そして、n=2のときの選択肢は下記のどちらかになる。 0~n-1までの配列を含めると、 20 (= 10 + -11 + 21 ) 0~n-1までの配列を含めないと、 21 この場合は最大数の21が選択される 改善 一般的な動的計画法だと答えは大抵は dp の末尾に答えが入るケースが多いけど、このケースの場合は必ずしもそうならない。 なぜなら、dp に保存されてるのは「nを含む」0~nの中の最大配列総和だから。dpの末尾だと「必ずn番目の要素を含んだ最大部分配列」が答えとして出てくる。 なので、return するときに、dpの中からmax で探してる。 return dp.max()! 改善として、for の中で合わせてmaxも保存しておく。そうなると、O(2n)からO(n)になる。 最終的にコードは下記のようになる。 func maxSubArray(_ nums: [Int]) -> Int { var dp = [Int](repeating: -1, count: nums.count) dp[0] = nums[0] var maxSoFar = nums[0] // <= 追加 for i in 1..<nums.count { dp[i] = max(dp[i-1] + nums[i], nums[i]) maxSoFar = max(maxSoFar, dp[i]) // <= 追加 } return maxSoFar } REF https://leetcode.com/problems/maximum-subarray/discuss/20193/DP-solution-and-some-thoughts https://engineer.yeele.net/algorithm/leetcode-easy-53-maximum-subarray/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode 53. Maximum Subarray

問題 https://leetcode.com/problems/maximum-subarray/ 解法 Kadane's algorithmで解く。部分問題を下記のように定義する list の 0 から n までの間で、listのn番目の要素を必ず含んだ最大部分配列 愚直にコードで書くとこうなる。 func maxSubArray(_ nums: [Int]) -> Int { var dp = [Int](repeating: -1, count: nums.count) dp[0] = nums[0] for i in 1..<nums.count { dp[i] = max(dp[i-1] + nums[i], nums[i]) // relaxation } return dp.max()! } ポイント 部分問題の定義にて「n番目の要素を必ず含んだ」部分配列の総和という考えがキモとなる。 例えば、下記のケースを考える list = [10, -999, 20, 40] n=2 (val=20) このときの選択肢は下記のどちらかになる。 0~n-1までの配列を含めると、 -969 (= 10 + -999 + 20 ) 0~n-1までの配列を含めないと、 20 この場合は最大数の20が選択される 改善 一般的な動的計画法だと答えは大抵は dp の末尾に答えが入るケースが多いけど、このケースの場合は必ずしもそうならない。 なぜなら、dp に保存されてるのは「nを含む」0~nの中の最大配列総和だから。dpの末尾だと「必ずn番目の要素を含んだ最大部分配列」が答えとして出てくる。 なので、return するときに、dpの中からmax で探してる。 return dp.max()! 改善として、for の中で合わせてmaxも保存しておくとO(2n)からO(n)になる。 func maxSubArray(_ nums: [Int]) -> Int { var dp = [Int](repeating: -1, count: nums.count) dp[0] = nums[0] var maxSoFar = nums[0] // <= 追加 for i in 1..<nums.count { dp[i] = max(dp[i-1] + nums[i], nums[i]) maxSoFar = max(maxSoFar, dp[i]) // <= 追加 } return maxSoFar } REF https://leetcode.com/problems/maximum-subarray/discuss/20193/DP-solution-and-some-thoughts https://engineer.yeele.net/algorithm/leetcode-easy-53-maximum-subarray/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】ストアドプロパティとコンピューテッドプロパティ

構造体とは インスタンスの型になるもの。 Taiyakiの構造体から、中身があんこのたいやきが作られる。 Carの構造体から、日産GTRが作られる。 Swiftでも、UI部品は構造体又はクラスから作られる。 struct Taiyaki{ 文 } プロパティとは 「インスタンスを特徴づける性質や特性」 たい焼きでと例えると、「中の具材」や「値段」 // プロパティの宣言 var プロパティ名 = 初期値 // 例 struct Taiyaki{ var nakami = "あんこ" // 省略 } ストアドプロパティ プロパティの値が構造体で初期化されていて、 毎回固定の値が保持されているもの。 実際のコードだと以下の通り。 var プロパティ名 = 初期値 let プロパティ名 = 初期値 // 使用例 struct Taiyaki{ // ストアプロパティ-初期値が設定されている var nakami = "クリーム" func sayNakami() { print("中身は" + nakami + "です") } } コンピューテッドプロパティ ストアプロパティは初期値が設定されていて、 その値をもとにメソッドで使用していた。 それに対して、コンピューテッドプロパティには、以下の特性がある。 初期値を持たない 呼び出されたタイミングで処理を行い、必ず結果を返す 実際にコードを見るとわかりやすい // 書き方 var プロパティ名: 型名{ 文 return 値 } // 例文 struct Vtuber{ // これは初期値があるので、ストアプロパティ var nakami = "あんこ" // ここからがコンピューテッドプロパティ var sayNakami:String { result = print("中身は" + nakami + "です") // 初期値を使用せずに、変数を使用している } } ストアドプロパティとコンピューテッドプロパティの違い 2つの構文での違いは以下の通りになる。 ストアドプロパティ コンピューテッドプロパティ 変数、定数 varまたはlet varのみ 型名の省略 可能 不可 波括弧{}の有無 なし あり 戻り値 不要 必須 変数と定数 コンピューテッドプロパティの宣言では定数を指定できない。 定数を指定すると、中身の変更ができなくなるため。 // letだと、変数nakamiが変更できなくなる var sayNakami:String { result = print("中身は" + nakami + "です") // 初期値を使用せずに、変数を使用している } 型名の省略 ストアドプロパティは変数の初期化の際、型を自動で認識してくれる。 対して、コンピューテッドプロパティは初期値がないので、 宣言時に明示的に型を指定する必要がある // ストアドプロパティ-変数を定義しているので、型を自動で認識する var nakami = "あんこ" // コンピューテッドプロパティ-初期値がないので、明示的に型を指定する必要がある var sayNakami:String { result = print("中身は" + nakami + "です") } コンピューテッドプロパティとメソッドとの違い 上記で記載したコードはメソッドでも同様のことが実現できる。 // メソッドの場合 func sayNakami() -> Int{ result = print("中身は" + nakami + "です") } ただし、コンピューテッドプロパティには大きな違いがある。 引数を受け取ることができない 戻り値が必ずある。 引数を受け取ることができない メソッドには func sayNakami("あんこ":String) のように引数を設定できたが、 コンピューテッドプロパティではできない 戻り値が必ずある。 コンピューテッドプロパティは必ず、returnで戻り値を指定しなければならない なお、中身1行だけの場合、省略できる // 省略可能なケース var nakami = "あんこ" var sayNakami:String { print("中身は" + nakami + "です") //中身が1行のみのため、returnが省略できる } // 省略不可能なケース var nakami = "あんこ" var sayNakami:String { 文 return print("中身は" + nakami + "です") //中身が2行のみのため、明示的にreturnを記述 } まとめ ストアドプロパティは初期値をもつもの(値を保持する) コンピューテッドは初期値を持たず、読み出されたタイミングで計算する(計算する)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AXUIElementを操作するために

はじめに macで他のアプリのウィンドウを移動させるアプリケーションを作りたいと思い、色々と調べたところ の記事が参考になりました。 しかし実装中、上記の記事の制限事項の回避をしていなかったため、AXUIElementを操作するAXUIElementCopyAttributeValueやAXUIElementSetAttributeValueを使うための権限がアプリに付与されておらず、うまくオブジェクトの情報を取得できずにはまってしまいました。 そこで忘れないうちに、制限を回避するためのアクセシビリティの権限をアプリに付与する方法を残しておくことにしました。 解決方法 1. SandBoxをオフにする Xcodeで自動生成されるアプリ名.entitlementsファイルのApp sandboxをNOに変更する アクセシビリティの API はApp SandBox環境では叩けないようなので、SandBoxの設定を外します。 2. アクセシビリティの権限を付与する 以下のコードを挿入する。 let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String : true] let accessEnabled = AXIsProcessTrustedWithOptions(options) if !accessEnabled { print("Access Not Enabled") } AXIsProcessTrustedWithOptionsでアプリにアクセシビリティの権限があるかどうか確認し、ない場合は権限を与えるかどうかのダイアログが表示されるので、システム環境設定で許可を与えます。 おわりに わかる人にとっては簡単なことでも、知識がないと苦労しますね。 今回初めてまともなアプリを制作してますが、ちゃんとしたエンジニアへの道はまだまだ先が長そうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む