20211123のSwiftに関する記事は9件です。

決定版〜継承vsインタフェースのベストプラクティス

こんにちは。歴13年のアプリエンジニアです(iOS/Android、Best of AppStore?受賞歴あり)。 ふだんは自分が手がけているスタートアップと数社の技術顧問をしているのですが、長年さまざまなプロジェクトで目の当たりにしてきたアプリ(ソフトウエア)開発上の問題について、設計とプログラミング双方の観点からのベストプラティクスを、ルーディメンツ(Rudiments)と名づけシンプルで理解しやすい形で提唱を目指し準備中しています。 今日はこの中から最もポピュラーな問題である、「継承vsインタフェース」(Interface as inheritance alternative)を取り上げます。特に初級者の方にとってより理解が深まれば幸いです。 Interface as inheritance alternative あるクラスを新しく作り、既存のクラスと共通の型にしたい場合、クラスの継承を使うべきか、interface(Swiftではprotocol)を使うべきかは多くのエンジニアにとって常につきまとう問題である。 採用基準 筆者は継承とinterfaceの採用基準として以下の基準を推奨している。 基準①:既存のクラスと機能や役割的に大きな部分が共通しているとみなせる場合は継承、そうでなく機能を超えて部分的に共通な役割を設定したい場合にはinterface 基準②:多くの言語では多重継承ができないため、将来のアップデートでさらなる拡張が見込まれる場合(柔軟性を確保したい場合)にはinterface、クラス同士の依存関係を固定させ堅牢性を確保したい場合には継承 基準③:「何を(具体性)」が重要な場合は継承、「どんな(抽象性)」が重要な場合はinterface 基準④:クラスの分布のイメージが縦断的/階層的/中央集権的な場合は継承、横断的/網羅的/分散的な場合はinterface ※筆者はよくポケモンの例を挙げて説明している。すなわち、あるポケモンが進化して新しいポケモンになる場合は継承、属性(水属性や火属性など)はinterfaceを採用するのが望ましい。上の4つの基準に照らして妥当かどうかを考えてみてほしい。 ルーディメンツ ここでは継承の代替としてのinterface(protocol)による実装をルーディメンツとして挙げる。interface(protocol)は特にデータモデル系のクラスに対して相性が良い。 ※サンプルコードはswiftで書かれています。今後他の言語も追加予定 protocol Feedable { var feedIndex: Int { get set } var feedColor: UIColor { get } } protocol Locatable: class { var location: CLLocationCoordinate2D { get set } var mapMeters: Int { get } } class User: Feedable { // MARK: Feedable var feedIndex: Int = 0 var feedColor: UIColor { // readonly return .black } } class Shop: Feedable, Locatable { var latitude: Double = 0 var longitude: Double = 0 // MARK: Feedable var feedIndex: Int = 0 var feedColor: UIColor { // readonly return .red } // MARK: Locatable var location: CLLocationCoordinate2D { get { return CLLocationCoordinate2DMake(latitude, longitude) } set { latitude = newValue.latitude longitude = newValue.longitude } } var mapMeters: Int { return 1000 } } // Caller側ではクラスに依存せずに処理が可能になる var feedArray: [Feedable] = [User(), Shop()] feedArray.enumerated().forEach { index, item in item.feedIndex = index // 個別の処理を行いたければswitchで行える switch item { case let item as User: // do something case let item as Shop: // do something } } ? アンチパターン① 本ルーディメンツを継承で実装するとこうなる。 class BaseItem { var feedIndex: Int = 0 var feedColor: UIColor { return .clear } var location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(0.1, 0.1) var mapMeters: Int { return 0 } } class User: BaseItem { // ... } class Shop: BaseItem { // ... } この実装は設計上無理がある。先述の4つの基準に当てはめて考える。 基準①:UserとShopは機能や役割的に共通点が多いとは言えないので継承にそぐわない 基準②:今後他のクラスが出てきたときにBaseItemを拡張しなくてはならず、柔軟性を損なうため継承にはそぐわない 基準③:BaseItemはクラス名のとおり具体性がなく無理矢理感がある。よって継承にはそぐわない 基準④:UserとShopの関係は階層関係とはいえず、むしろ横断的関係のほうが適合する。よって継承にはそぐわない ? アンチパターン② 入門書などで継承の説明で以下のようなコード例をよく見かけるが、これは望ましい実装ではなく、むしろアンチパターンである。 class Animal { func bark() {} } class Dog: Animal { override func bark() { print("わんわん?") } } class Cat: Animal { override func bark() { print("ニャーニャー?") } } もし今後仕様追加でRobotというクラスを追加しなくてはならなくなった場合、基底クラスがAnimalでは意味が通じずコードの保守性が低下してしまう。またRobotは鳴かない(bark()関数が必要ない)。 そこでinterfaceを使って次のように実装すべきである。 protocol Barkable { func bark() } class Dog: Barkable { func bark() { print("わんわん?") } } class Cat: Barkable { func bark() { print("ニャーニャー?") } } これだと新しいクラスRobotを追加してもBarkableを実装しなければ済むだけである。 継承とは異なり、大多数のプログラミング言語ではインタフェースの多重実装を認めている。新しい型が欲しければ新しいインタフェースを作成して、それを追加実装すれば良い。 おわりに いかがだったでしょうか。ぜひ閲覧者の皆さんの意見や批判、感想をいただけましたら幸いです。 序文にある通り、筆者は現在これまでの経験を踏まえた新しいソフトウエア開発のトレーニングメソッド(The Programming Rudiments)の策定を目指しています(現在準備段階で17パターンを用意中)。 ソフトウエア(アプリ)開発においては、現在でも入門書と現場レベルの間には大きな乖離があり、それを埋めるトレーニングメソッドに目ぼしいものがないという状況が続いています(たとえば、有名どころのデザインパターンは抽象度が高く難解で初級者には実感が湧きづらいといった問題があります)。 ぜひ皆さんのご意見、ご感想をお待ちしています・・・!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【決定版】継承vsインタフェースのベストプラクティス

こんにちは。歴13年のアプリエンジニアです(iOS/Android、Best of AppStore?受賞歴あり)。 ふだんは自分が手がけているスタートアップと数社の技術顧問をしているのですが、長年さまざまなプロジェクトで目の当たりにしてきたアプリ(ソフトウエア)開発上の問題について、設計とプログラミング双方の観点からのベストプラティクスを、ルーディメンツ(Rudiments)と名づけシンプルで理解しやすい形で提唱を目指し準備しています。 今日はこの中から最もポピュラーな問題である、「継承vsインタフェース」(Interface as inheritance alternative)を取り上げます。特に初級者の方にとってより理解が深まれば幸いです。 Interface as inheritance alternative あるクラスを新しく作り、既存のクラスと共通の型にしたい場合、クラスの継承を使うべきか、interface(Swiftではprotocol)を使うべきかは多くのエンジニアにとって常につきまとう問題である。 採用基準 筆者は継承とinterfaceの採用基準として以下の基準を推奨している。 基準①:既存のクラスと機能や役割的に大きな部分が共通しているとみなせる場合は継承、そうでなく機能を超えて部分的に共通な役割を設定したい場合にはinterface 基準②:多くの言語では多重継承ができないため、将来のアップデートでさらなる拡張が見込まれる場合(柔軟性を確保したい場合)にはinterface、クラス同士の依存関係を固定させ堅牢性を確保したい場合には継承 基準③:「何を(具体性)」が重要な場合は継承、「どんな(抽象性)」が重要な場合はinterface 基準④:クラスの分布のイメージが縦断的/階層的/中央集権的な場合は継承、横断的/網羅的/分散的な場合はinterface ※筆者はよくポケモンの例を挙げて説明している。すなわち、あるポケモンが進化して新しいポケモンになる場合は継承、属性(水属性や火属性など)はinterfaceを採用するのが望ましい。上の4つの基準に照らして妥当かどうかを考えてみてほしい。 ルーディメンツ ここでは継承の代替としてのinterface(protocol)による実装をルーディメンツとして挙げる。interface(protocol)は特にデータモデル系のクラスに対して相性が良い。 ※サンプルコードはswiftで書かれています。今後他の言語も追加予定 protocol Feedable { var feedIndex: Int { get set } var feedColor: UIColor { get } } protocol Locatable: class { var location: CLLocationCoordinate2D { get set } var mapMeters: Int { get } } class User: Feedable { // MARK: Feedable var feedIndex: Int = 0 var feedColor: UIColor { // readonly return .black } } class Shop: Feedable, Locatable { var latitude: Double = 0 var longitude: Double = 0 // MARK: Feedable var feedIndex: Int = 0 var feedColor: UIColor { // readonly return .red } // MARK: Locatable var location: CLLocationCoordinate2D { get { return CLLocationCoordinate2DMake(latitude, longitude) } set { latitude = newValue.latitude longitude = newValue.longitude } } var mapMeters: Int { return 1000 } } // Caller側ではクラスに依存せずに処理が可能になる var feedArray: [Feedable] = [User(), Shop()] feedArray.enumerated().forEach { index, item in item.feedIndex = index // 個別の処理を行いたければswitchで行える switch item { case let item as User: // do something case let item as Shop: // do something } } ? アンチパターン① 本ルーディメンツを継承で実装するとこうなる。 class BaseItem { var feedIndex: Int = 0 var feedColor: UIColor { return .clear } var location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(0.1, 0.1) var mapMeters: Int { return 0 } } class User: BaseItem { // ... } class Shop: BaseItem { // ... } この実装は設計上無理がある。先述の4つの基準に当てはめて考える。 基準①:UserとShopは機能や役割的に共通点が多いとは言えないので継承にそぐわない 基準②:今後他のクラスが出てきたときにBaseItemを拡張しなくてはならず、柔軟性を損なうため継承にはそぐわない 基準③:BaseItemはクラス名のとおり具体性がなく無理矢理感がある。よって継承にはそぐわない 基準④:UserとShopの関係は階層関係とはいえず、むしろ横断的関係のほうが適合する。よって継承にはそぐわない ? アンチパターン② 入門書などで継承の説明で以下のようなコード例をよく見かけるが、これは望ましい実装ではなく、むしろアンチパターンである。 class Animal { func bark() {} } class Dog: Animal { override func bark() { print("わんわん?") } } class Cat: Animal { override func bark() { print("ニャーニャー?") } } もし今後仕様追加でRobotというクラスを追加しなくてはならなくなった場合、基底クラスがAnimalでは意味が通じずコードの保守性が低下してしまう。またRobotは鳴かない(bark()関数が必要ない)。 そこでinterfaceを使って次のように実装すべきである。 protocol Barkable { func bark() } class Dog: Barkable { func bark() { print("わんわん?") } } class Cat: Barkable { func bark() { print("ニャーニャー?") } } これだと新しいクラスRobotを追加してもBarkableを実装しなければ済むだけである。 継承とは異なり、大多数のプログラミング言語ではインタフェースの多重実装を認めている。新しい型が欲しければ新しいインタフェースを作成して、それを追加実装すれば良い。 おわりに いかがだったでしょうか。ぜひ閲覧者の皆さんの意見や批判、感想をいただけましたら幸いです。 序文にある通り、筆者は現在これまでの経験を踏まえた新しいソフトウエア開発のトレーニングメソッド(The Programming Rudiments)の策定を目指しています(現在準備段階で17パターンを用意中)。 ソフトウエア(アプリ)開発においては、現在でも入門書と現場レベルの間には大きな乖離があり、それを埋めるトレーニングメソッドに目ぼしいものがないという状況が続いています(たとえば、有名どころのデザインパターンは抽象度が高く難解で初級者には実感が湧きづらいといった問題があります)。 ぜひ皆さんのご意見、ご感想をお待ちしています・・・!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】処理フローが「一発」でわかる非同期の順次処理の書き方

はじめに 今回は、スパゲッティコードにならないような順次処理を考えてみたので記事にしてみました。 特定の順番で毎回実行される処理って、条件分岐や繰り返しが無いので、シンプルに書くことができます。 しかし、非同期で実行される処理がある場合などは、可読性を意識して書かないと、案外メチャクチャになりがちです。(←過去の経験より) この記事で学べること 順次処理とは? 非同期で実行される順次処理を実装する上での課題はなにか? 処理フローを追いやすい順次処理の書き方 結論だけ確認したい方は、「処理フローを追いやすい順次処理の書き方」から読んでいただければと思います。 順次処理ってなに? 指定した順番に沿って順番に実行されていくプログラムのこと 日常生活にたとえてみると、 朝起きる → 朝食を食べる → 歯を磨く → 着替える → 家を出る というような、一連の流れがこれに当てはまります。 とてもとてもシンプルな構造ですので、一見プログラムもシンプルになると思われがちです。 非同期処理を含む順次処理の課題とは? さっきの日常生活のたとえを、実際の要件にありそうな仕様に変えてみます。 今回は、起動時の処理を例にして説明しようと思います。 アプリ起動 → バージョンチェックAPI呼出 → ユーザー情報取得API呼出 → 商品情報取得API呼出 → トップ画面へ遷移 この一連の流れをSwiftでコードに落とし込むとこんな感じになります。 今回の場合、アプリを起動すると、SplashViewControllerが一番最初に呼ばれるとします。 ※サンプルコードなので中身ちゃんと見なくて大丈夫です。 ※非同期処理の[weak self]など割愛してます。 SplashViewController.swift import UIKit class SplashViewController: UIViewController { let api: APIManeger = APIManeger() override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 1. 起動したのでバージョンチェック fetchVersionInfo() } func fetchVersionInfo() { print("!!!! start version check") api.requestVersionCheck() { result in if result.isSuccess { // バージョンアップが必要ならAppStoreに飛ばす etc... // バージョンチェック正常終了 // 2. ユーザー情報取得API呼出 self.fetchUserInfo() } else { // エラーだったらアプリ終了 } } } func fetchUserInfo() { print("!!!! start userInfo request") api.requestUserInfo { result in if result.isSuccess { // ユーザー情報から必要な情報を取得したり、データを加工したりする処理 etc... // 正常終了 // 3. 商品情報取得API呼出 self.fetchProductInfo() } else { // エラーだったらアプリ終了 } } } func fetchProductInfo() { print("!!!! start productInfo request") api.requestProductInfo { result in if result.isSuccess { // 商品情報情報から必要な情報を取得したり、データを加工したりする処理 etc... // 正常終了 // 全部終わったのでトップ画面へ遷移する } else { // エラーだったらアプリ終了 } } } } いかがでしょうか。 自分はこの実装を見たときに 「順番で実行されるメソッドがどこで呼ばれているのかぱっと見てわからない」 という課題を感じます。 上記のような実装の場合、 「AがBを呼び出す」「BがCを呼び出す」 というような構造になっているため、上からちゃんと全部読まないと、どこで「C」が呼び出されているのかがわかりません。 今回は、サンプルコードでコードが短いため、比較的すぐに流れを追うことができると思いますが、 実際のプロジェクトのコードになると、 requestメソッドのコールバックでエラーハンドリングや色々な処理が行われる 今回は3つの処理しかしてないが、実際はもっと多くの処理を行う可能性がある 仕様変更でB処理とC処理の間に「B'処理」を入れたいなどが起こる可能性がある といったコードが複雑化する要因が増えるため、サンプルコードよりも処理を追うことが困難になると予測されます。 処理フローを追いやすい順次処理の書き方 とりあえずコードはこんな感じ! SplashViewController.swift import UIKit enum SplashTask { case fetchVersionInfo case fetchUserInfo case fetchProductInfo static func getTask() -> [Self] { [.fetchVersionInfo, .fetchUserInfo, .fetchProductInfo] } } class SplashViewController: UIViewController { let api: APIManeger = APIManeger() var splashTasks: [SplashTask] = [] override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // getTask()によって、起動時の処理と順番を確定させる splashTasks = SplashTask.getTask() // 初回のみここで実行処理を呼び出す executeSplashTask() } private func executeSplashTask() { if splashTasks.count == 0 { // タスクがなくなったので起動処理終了 -> トップ画面へ遷移 print("!!!! finish all") return } let task = splashTasks.removeFirst() switch task { case .fetchVersionInfo: fetchVersionInfo { self.executeSplashTask() } case .fetchUserInfo: fetchUserInfo { self.executeSplashTask() } case .fetchProductInfo: fetchProductInfo { // この処理が最後なので念の為処理(整合性が取れてれば実際は不要) splashTasks.removeAll() self.executeSplashTask() } } } func fetchVersionInfo(completion: () -> Void) { print("!!!! start version check") api.requestVersionCheck() { result in if result.isSuccess { // バージョンアップが必要ならAppStoreに飛ばす etc... // バージョンチェック正常終了 completion() } else { // エラーだったらアプリ終了 } } } func fetchUserInfo(completion: () -> Void) { print("!!!! start userInfo request") api.requestUserInfo { result in if result.isSuccess { // ユーザー情報から必要な情報を取得したり、データを加工したりする処理 etc... // 正常終了 completion() } else { // エラーだったらアプリ終了 } } } func fetchProductInfo(completion: () -> Void) { print("!!!! start productInfo request") api.requestProductInfo { result in if result.isSuccess { // 商品情報情報から必要な情報を取得したり、データを加工したりする処理 etc... // 正常終了 completion() } else { // エラーだったらアプリ終了 } } } } この順次処理の特徴は、 各リクエストメソッドが次にどのメソッドを呼び出すかを決定する責務を持っていないという点 です。 それにより、 リクエストメソッドの中身を覗かなくても、プログラマは順次処理の流れを把握することができるようになりました。 最初のコードでは 「AがBを呼び出す」「BがCを呼び出す」 という処理を各メソッドで行っていましたが、 今回は、リクエストが正常に完了したら、completion()を呼び出して終わりです。 起動処理のコントロールは、executeSplashTask()が行い、順序をコントロールしているのはSplashTaskという列挙型です。 executeSplashTask()は、 Taskリストの先頭から切り出したTaskを実行し、Taskリストが空になったら、起動処理がすべて完了したとみなす仕組みを採用しています。 そして、自分自身を再帰呼び出しすることで処理を次に進めます。 まとめ 非同期処理を含む順次処理では、順序を決定するメソッドと実際の処理を行うメソッドに責務を分散させることで、プログラマは各処理の中身を知らなくても、順次処理の流れを理解することができる。 順所の変更や処理の追加といった仕様変更が発生しても、既存のロジックにほぼ影響を与えることなく修正が可能になる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DispatchGroupの.leaveと.notifyを使用して取得したデータを表示(楽天API)

今回の内容 最近、DispatchQueue.main.asyncAfterをよく使ってましたが、データの取得が終わった時にUITableViewに表示させる為にDispatchGroupの.leaveと.notifyを使用してます。 コード Model GetDataDetailModel struct GetDataDetailModel{ let mediumImageURL:String? let itemName:String? let itemPrice:Int? let itemCaption:String? } AlamofireProcess import Alamofire import SwiftyJSON class AlamofireProcess{ public var getDataResultArray = [GetDataDetailModel]() private var keyword:String? init(getKey:String?){ keyword = getKey } } extension AlamofireProcess{ public func getItemDetailData(completion: @escaping() -> Void){ guard let key = keyword else { return } let apiKey = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json&keyword=\(key.urlEncoded)&applicationId=作成したアプリID" AF.request(apiKey, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {[self] response in switch response.result{ case .success: let jsonDetail = JSON(response.data as Any) getDataResultArray = [] for dataCount in 0..<jsonDetail["Items"].count{ if let getMediumImageURL = jsonDetail["Items"][dataCount]["Item"]["mediumImageUrls"][0]["imageUrl"].string, let getItemName = jsonDetail["Items"][dataCount]["Item"]["itemName"].string, let getItemPrice = jsonDetail["Items"][dataCount]["Item"]["itemPrice"].int, let getItemCaption = jsonDetail["Items"][dataCount]["Item"]["itemCaption"].string{ getDataResultArray.append(GetDataDetailModel(mediumImageURL: getMediumImageURL, itemName: getItemName, itemPrice: getItemPrice, itemCaption: getItemCaption)) } } completion() case .failure: completion() } } } } extension String{ var urlEncoded:String{ let charset = CharacterSet.alphanumerics.union(.init(charactersIn: "/?-._~")) let remove = removingPercentEncoding ?? self return remove.addingPercentEncoding(withAllowedCharacters: charset) ?? remove } } View Main.storyboard SearchResultCell SearchResultCell import UIKit class SearchResultCell: UITableViewCell { @IBOutlet weak var mediumImageView: UIImageView! @IBOutlet weak var itemNameLabel: UILabel! @IBOutlet weak var itemPriceLabel: UILabel! @IBOutlet weak var itemCaptionView: UITextView! override func awakeFromNib() { super.awakeFromNib() } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } required init?(coder: NSCoder) { super.init(coder: coder) } override func prepareForReuse() { super.prepareForReuse() mediumImageView.image = UIImage(named: "") itemNameLabel.text = "" itemPriceLabel.text = "" itemCaptionView.text = "" } } Controller import UIKit import SDWebImage class ViewController: UIViewController { @IBOutlet weak var searchTextField: UITextField! @IBOutlet weak var searchButton: UIButton! @IBOutlet weak var resultTableView: UITableView! private let alamofireProcess = AlamofireProcess(getKey: nil) private let dispatchGroup = DispatchGroup() private let dispachQueue = DispatchQueue(label: "alamofire",attributes: .concurrent) private var cellContentsArray = [GetDataDetailModel]() override func viewDidLoad() { super.viewDidLoad() resultTableView.register(UINib(nibName: "SearchResultCell", bundle: nil), forCellReuseIdentifier: "ItemDetailCell") resultTableView.delegate = self resultTableView.dataSource = self } @IBAction func search(_ sender: UIButton) { let afProcess = AlamofireProcess(getKey: searchTextField.text) dispatchGroup.enter() dispachQueue.async {[self] in afProcess.getItemDetailData { self.dispatchGroup.leave() } } dispatchGroup.notify(queue: .main){ [self] in cellContentsArray = afProcess.getDataResultArray print(cellContentsArray) resultTableView.reloadData() } } } extension ViewController:UITableViewDelegate{ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return tableView.frame.height / 2.4 } } extension ViewController:UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cellContentsArray.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ItemDetailCell", for: indexPath) as! SearchResultCell cell.mediumImageView.sd_setImage(with: URL(string: cellContentsArray[indexPath.row].mediumImageURL!), completed: nil) cell.itemNameLabel.text = cellContentsArray[indexPath.row].itemName cell.itemPriceLabel.text = String(cellContentsArray[indexPath.row].itemPrice!) + "円" cell.itemCaptionView.text = cellContentsArray[indexPath.row].itemCaption return cell } } 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

progreesViewを縦にして、太さも変更する

UIProgressViewを回転させると、太さを変更したのにそれが適用されないみたいな事が起こりました。 ちなみに太さは YourProgressView.transform = CGAffineTransform(scaleX:任意の値,y:任意の値) で変更できるのですが、そこでもし、「よし、回転させたい」ってなった時に、 YourProgressView.transform = CGAffineTransform(rotationAngle:CGFloat(90*CGFloat.pi / -180)) を記述して回転させると、まあちゃんと回転はするのですが、逆に「変更したはずの太さが変更されてなかった」という事が起こってしまいした。 解決策 そこで、どうすれば太さを維持したまま回転させる事が出来るのか模索した結果、CGAffineTransformが持つconcatenating(文字列結合)というメソッドを用いれば良い事がわかりました。 記述するとこんな感じです。 import UIKit class ViewController: UIViewController { @IBOutlet weak var YourProgressView: UIProgressView! override func viewDidLoad() { super.viewDidLoad() let changeScale = CGAffineTransform(scaleX: 1.0, y: 3.0)//太さ変更 let changeRotate = CGAffineTransform(rotationAngle: CGFloat(90*CGFloat.pi / -180))//回転     let concatenatingTransform = changeScale.concatenating(changeRotate)//二つのTransformを結合 YourProgressView.transform = concatenatedTransform } } これでちゃんと出来るようになります。 では!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift初心者のためのAIアプリ(画像認識編)

◆はじめに 今回作るもの Swiftで「CoreML」という人工知能のライブラリを用いて、iPhoneのカメラで撮影したものが何であるのか判断するアプリを作成します。 どんな人に Swift初心者 機械学習、理論じゃなくてまずは触ってみたい 画像認識おもしろそう とりあえずmacbookもってる プログラミングなんかおもしろそう ーCoreMLとはー まずはこちらでAppleの機械学習のサイトに飛びましょう。 次に学習済みモデルのダウンロードを行います。 学習済みモデルとは、予め用意されている 今回はAppleが提供する、データを集め既に学習済みのモデルを使います。 ここで機械学習とは?? 機械にデータを学習・分析させて分類や推定を行うことです。 今回は、予め正解データを与えて新しいデータを処理する教師あり学習を使います。詳しい理論についてはここでは解説しません。(あくまでアプリ作成が目的なため) ◆解説編 流れ STEP0:今回使用する学習済みモデルのダウンロード STEP1:新規プロジェクトの作成 STEP2:見た目の部分作成 STEP3:step0でダウンロードしたものをアプリ内にいれます STEP4:実際にコードを書く 完成 ❏ STEP0 モデルのダウンロード 下の方の「Models」→「Browse models」のリンクをクリックしてください。 今回はまず「Resnet50」というモデルを使いたいと思います。 この画像の一番上のやつをダウンロードしてください。 ❏ STEP1 プロジェクトの追加 新しいプロジェクトを作成しましょう。 今回はiOSのAppで作成してください。 参考画像 名前は適当に決めて大丈夫です。 ❏ STEP2 UI UIを作りましょう。 Mainのストーリーボードを開き、以下のパーツを追加します。 Image View Text View Button 下の画像のように「Assitant」を開きましょう。 参考画像 先程配置したパーツを「control」キーを推しながら「viewController」にドラッグ&ドロップします。 場所は下の画像を参考にしてください。(初心者の方は全く同じようにしてください。) 参考画像 今回は下のように名前をつけています。(この後コードをコピペする方は合わせてください。) Image View → imageView Text View → infoView Button → takeImage また、Text View を「写真を撮ってください。
判定します!」に変更しておくと、アプリを使用する際にきれいにみえます。 ❏ STEP3 モデルをアプリへ 最初にダウンロードしたResnet50のモデルをドラッグ&ドロップでアプリに入れてください。 下の画像のようになっていれば大丈夫です。 参考画像 ❏ STEP4 コーディング 次にコードを記入していきます。 写真とコードを貼っておくので自分のコードと見比べながら追記していってください。 コード全体像写真 全コード import UIKit import CoreML import Vision import AVFoundation class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate { @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var infoView: UITextView! var imagePicker: UIImagePickerController! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.sourceType = .camera } @IBAction func takeImage(_ sender: Any) { present(imagePicker, animated: true, completion: nil) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { imageView.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage imagePicker.dismiss(animated: true, completion: nil) imageInference(image: (info[UIImagePickerController.InfoKey.originalImage] as? UIImage)!) } func imageInference(image: UIImage) { //モデルの定義(モデルを指定することで色々と変更できる:今回はResnet50) guard let model = try? VNCoreMLModel(for: Resnet50().model) else { fatalError("cannnot read the model") } let request = VNCoreMLRequest(model: model) { [weak self] request, error in guard let results = request.results as? [VNClassificationObservation], let firstResult = results.first else { fatalError("cannot judge it") } DispatchQueue.main.async { self?.infoView.text = "Accuracy : \(Int(firstResult.confidence * 100))%, \n Lavel : \((firstResult.identifier))" let utterWords = AVSpeechUtterance(string: (self?.infoView.text)!) utterWords.voice = AVSpeechSynthesisVoice(language: "en-US") let synthesizer = AVSpeechSynthesizer() synthesizer.speak(utterWords) } } guard let ciImage = CIImage(image: image) else { fatalError("cannnot convert image") } let imageHandler = VNImageRequestHandler(ciImage: ciImage) DispatchQueue.global(qos: .userInteractive).async { do { try imageHandler.perform([request]) } catch { print("error \(error)") } } } } ❏ STEP5 実行してみよう このようになったら成功です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftPMプロジェクトのExampleとしてPlaygroundを利用する

Xcode13.2から、拡張子.swiftpmを持つSwift Packageファイルが使えるようになりました。 実態は、Package.swiftをラップしたパッケージファイルなのですが、これを開くとPackage.swiftに指定されたアプリケーションを実行できるXcodeの特別なワークスペースを開くことができます。 このSwift Packageファイルは次のようにSwiftPMプロジェクトのExampleとして扱うことができます。 Example.swiftpmから、同階層のSwiftPMプロジェクトを呼び出すには、Example.swiftpm/Package.swiftに、相対パス指定でdependenciesを登録する必要があります。 Example.swiftpm/Package.swift // swift-tools-version: 5.5 import PackageDescription import AppleProductTypes let package = Package( name: "Example", platforms: [ .iOS("15.2") ], products: [ .iOSApplication( name: "Example", targets: ["AppModule"], bundleIdentifier: "dev.noppe.DebugMenu.Example", teamIdentifier: "XXXXXXXXX", displayVersion: "1.0", bundleVersion: "1", iconAssetName: "AppIcon", accentColorAssetName: "AccentColor", supportedDeviceFamilies: [.phone], supportedInterfaceOrientations: [.portrait] ) ], dependencies: [ .package(name: "DebugMenu", path: "../") //ここ ], targets: [ .executableTarget( name: "AppModule", dependencies: [ .productItem(name: "DebugMenu", package: "DebugMenu", condition: nil) ] ) ] ) これで、Xcodeからはlocalとラベルのついたswiftpmとしてプロジェクトに取り込むことができました。 localパッケージはXcodeから編集することができるため、このような構成にしてExampleを元に開発することもできます。 謎のxmlなどが消えて、ピュアswiftな非常にシンプルなファイル構成になるのでぜひ試してみてください。 この構成はDebugMenuでも採用しています。 https://github.com/noppefoxwolf/DebugMenu 多分この構成にするとiPadのPlaygroundでも開けるようになるんじゃないかなぁ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RealityKitで顔と重なったオブジェクトを表示する(FaceMeshを非表示にする)

RealityKitでFaceAnchorを使うと、顔より後ろのARオブジェクトが透過されます。 この深度オクルージョンをオフにするには、 arView.renderOptions.insert(.disableFaceOcclusions) // iOS15からは.disableFaceMesh ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftのプロトコルは何?

Swiftのプロトコルって何でしょうか。これはどういう役割のために存在するものでしょうか?この問いに対する、ぼくの答えはこうです。 Swiftのプロトコルはジェネリックの型パラメータに制約を与えるためのもの。 以下、具体例を交えて説明します。 ジェネリック 犬という型があったとしましょう。その一部を抜粋したものが以下です。 /// 犬。 class Dog { /// お腹が空いているかどうか。 var isHungry: Bool { ... } /// 餌を食べます。 func feed() { ... } /// 尻尾を追いかけます。 func chaseTail() { ... } ... } ここで、犬の面倒を見てくれる人の型を作ります。 /// 犬の面倒を見る人。 struct Dogsitter { /// ペットのお世話をします。 /// - Parameter pet: 対象のペット func care(for pet: Dog) { // お腹を空かせていたら餌をやる if pet.isHungry { pet.feed() } } } 次のようにして、このドッグシッターにペットのポチの面倒を見てもらうことができます(こんなお世話でいいのかどうかはおいといて)。 let pochi = Dog() let dogshitter = Dogsitter() dogshitter.care(for: pochi) ところで、猫の型もあったとしましょう。 class Cat { /// お腹が空いているかどうか。 var isHungry: Bool { ... } /// 餌を食べます。 func feed() { ... } /// じっと見つめます。 func stare() { ... } ... } 猫の面倒を見てくれる人の型を作りたいのですが、やることは犬の時と同じでした。 struct Catsitter { func care(for pet: Cat) { if pet.isHungry { pet.feed() } } } こんなとき、ジェネリックを使えばいちいち同じ処理を書くのを避けることができます。次のような型を定義すれば良いのです。 struct Petsitter<Pet> { func care(for pet: Pet) { if pet.isHungry { pet.feed() } } } ところが、残念ながら上のコードはコンパイルエラーになります。なぜなら、ジェネリックの型パラメーターである Pet は型のプレースホルダーです。実際に使われるときには Pet は具体的な型に置き換わるわけですが、このままではどんな型でも取り得るため、置き換えられた具体的な型が isHungry や feed() を持っているかどうかがわからないからです。 struct Petsitter<Pet> { func care(for pet: Pet) { if pet.isHungry { // Error: Value of type 'Pet' has no member 'isHungry' pet.feed() // Error: Value of type 'Pet' has no member 'feed' } } } プロトコルで型に制約を設ける ここでプロトコルの登場です。プロトコルで isHungry や feed() の存在を要求することができます。 /// このプロトコルに適合するものは、 /// お腹を空かせてるか確認できたり、餌を食べたりできます。 protocol Animal { var isHungry: Bool { get } func feed() } Petsitter の型パラメーターである Pet に、このプロトコルで制約を与えます。これで、Pet には Animal に適合した型しか入れられなくなりました。 isHungry や feed() を持っていることが保証できるので、コンパイルが通ります。 struct Petsitter<Pet: Animal> { // ←ここ func care(for pet: Pet) { if pet.isHungry { pet.feed() } } } また、犬や猫の型を下記のようにこのプロトコルに準拠させておきます。 class Dog: Animal { // ←ここ ... } class Cat: Animal { // ←ここ ... } こうして、ジェネリックを使って Petsitter ひとつ定義するだけで Dogsitter と Catsitter を個別に定義するのと同等のことができました。猫のタマの面倒を見てもらうには、次のようにします。 let tama = Cat() let catshitter = Petsitter<Cat>() catshitter.care(for: tama) この使い方が Swift におけるプロトコルのメインの存在意義だと、ぼくは考えています。 型消去 ところで、この Petsitter は、型パラメーターに与えた具体的な型によって、それぞれ異なる型として振る舞います。当然ですが、猫のシッターに犬の面倒を見てもらうことはできません。 let pochi = Dog() let catshitter = Petsitter<Cat>() catshitter.care(for: pochi) // Error: Cannot convert value of type 'Dog' to expected argument type 'Cat' そもそも Petsitter は Dogsitter や Catsitter をまとめて定義しただけです。もともとの Catsitter も Dog を与えたらエラーになってくれないと困ります。だから、これは正しい振る舞いです。 では、犬でも猫でも、 Animal プロトコルに準拠するものならなんでもお世話できる人を作りたかった場合はどうしたらいいでしょうか。 その場合は、ジェネリックでシッターを作るのではなく、「 Animal プロトコルに準拠するものならなんでも」の部分をジェネリックを使って解決することになります。例えば、 Animal プロトコルに準拠したものをラップする、次のような型を作ります。ここでは init(_:) にジェネリックを使っています。なお、このような型は、 Dog や Cat という具体的な型を消すものなので、型消去(type erasure)と呼ばれたりもします。 class AnyAnimal { private let _isHungry: () -> Bool private let _feed: () -> () init<Wrapped: Animal>(_ wrapped: Wrapped) { _isHungry = { wrapped.isHungry } _feed = wrapped.feed } var isHungry: Bool { _isHungry() } func feed() { _feed() } } シッターの方は、この AnyAnimal のお世話をするように作ります。 struct Animalshitter { func care(for pet: AnyAnimal) { if pet.isHungry { pet.feed() } } } そうすれば、次のように、犬のポチも猫のタマも AnyAnimal に型消去することで、同じ Animalshitter のインスタンスに面倒を見てもらうことができるようになります。 let pochi = Dog() let tama = Cat() let animalshitter = Animalshitter() animalshitter.care(for: AnyAnimal(pochi)) animalshitter.care(for: AnyAnimal(tama)) プロトコルを型として利用する 先程の AnyAnimal の例のように、プロトコルに準拠するものを型によらずまとめて扱いたいことがあります。実は Swift のプロトコルには、特別大サービスとして AnyAnimal のようなものを自動的にやってくれる仕組みがあります。それが、プロトコルを型として利用する方法です。 AnyAnimal という型消去を用意しなくても、次のように、プロトコルを直接使って型のように利用できます。このようにして利用する型を存在型(existential type)と呼んだりもします。 struct Animalshitter { func care(for pet: Animal) { if pet.isHungry { pet.feed() } } } 使う側でも、型消去の必要はありません。 let pochi = Dog() let tama = Cat() let animalshitter = Animalshitter() animalshitter.care(for: pochi) animalshitter.care(for: tama) ただし、これは特別大サービスなので、単純なプロトコルでしか利用できません。例えば、プロトコルが associatedtype を持っていたり Self を使っていたりすると、この使い方はできません。そういうときは、自分で型消去を用意する必要があります。 あくまでも、プロトコルはジェネリックの型パラメーターに制約を設けるためのもので、存在型の利用は特別大サービスだと考えるのがいいと思います。 余談:プロトコルに対する誤解(ハマりポイント) 特別大サービスである、プロトコルを存在型として使う方法は、その使い方が Java や C# のインタフェースによく似ています。実際、今回の例のようにプロトコルに適合するものがシンプルな参照型( class )だけなら、Java や C# のインタフェースと同じように使えます。しかし、値型( struct )を適合させた場合は、AnyAnimal を使ったときのような感じで、コンパイラが生成した具体的な型にラップされ、実行時のコストも発生します。 Java や C# の経験のある人が Swift を始めると、「プロトコルは Java や C# のインタフェースに相当するものだ」と捉えがちです。ぼく自身、最初はそのように考えていましたし、ぼくの周りでもそういう人を見かけました。そのような理解で、実際にコーディングを始めてしまうと、 associatedtype や Self を使ったプロトコルを使いたくなったところでたいてい一旦ハマります。そして、Swift のプロトコルは難しいという苦手意識が残ります。 そもそもプロトコルはそういうものじゃなく、ジェネリックの型パラメータに制約を与えるためのものであって、型としての利用の方がおまけなのだ、と考えた方がシンプルに理解しやすいと思います。 実際のところ、Swift のコミュニティでも、プロトコルを型として利用する機能があまりにも単純に利用できてしまい、裏に存在型がいることを隠してしまっていることが問題視されています。Swift 6では存在型を使いたい場合は明示的に記述させるようにしよう、という提案も始まっているようです。もし、この提案がそのまま通れば、最後の Animalsitter の例は、次のように書くことになりそうです。 struct Animalshitter { // ↓「Animal」は型ではなくてあくまでもプロトコルを指し、 // 「any Animal」が存在型を示す(型として利用する)構文になる。 func care(for pet: any Animal) { if pet.isHungry { pet.feed() } } } もっと知りたい この記事では「プロトコルはジェネリックの型パラメータに制約を与えるためのもの(存在型はおまけ)」という説明を行いました。そう捉えた上で、その一歩先として読んでほしい、他の方の記事を紹介します。 Heart of Swift - 第 2 章 Protocol-oriented Programming 従来の Object-oriented Programming(オブジェクト指向プログラミング)で扱うのがプロトコルの存在型。一方、Protocol-oriented Programming(プロトコル指向プログラミング)では、ジェネリックを積極的に使い、プロトコルでそこに制約を与えるという考え方が解説されています。この第2章は3ページに渡って書かれています。一度に理解するのは難しいかもしれませんが、丁寧に詳しく書かれているので何度も参照したいです。 Swiftのprotocolの存在型がそれ自身のprotocolに準拠しない理由 記事そのものの目的とは違うかもしれませんが、存在型を使う際の振る舞いや注意点が書かれているのでぜひ見てほしいです。 Swift の Type Erasure の実装パターンの紹介 この記事では触れなかった、associatedtype や Self を持つプロトコルの型消去を行う方法が書かれています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む