20211224のSwiftに関する記事は7件です。

No such module ‘RealmSwift’が出た時にまずやること

対象 ビルドしてもクリーンしてもxcode再起動してもエラーが消えない。 RealmSwfitをCocoapodsから入れている。 シミュレータでビルドしている。 解決方法 すでにQiitaに上がっているのは下記の参考URLに書いてあるので、今回自分が見つけた手順を書きます。 シミュレータのバージョンをios13に変更し、ビルドする。 (Excluded Architectureには何も設定しない。ios14以降はarm64を記述しないとビルドできないことがあるが、13の場合はなくてもビルドできるため) これだけでビルドが通ります。あとは任意のosに変更しても問題なくビルドが成功するはずです。 上記で解決しなければこちら https://qiita.com/gentuki/items/a90a4a793a24612355d5 https://qiita.com/m-h5510/items/72f2b5eb8fdb4b2d81a6
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自前でDIコンテナを作ってみる試みとRxSwiftを利用した構成への適用を試してみる

1. はじめに 皆様お疲れ様です。「iOS Advent Calendar」の24日目を担当させて頂きます、fumiyasac(Fumiya Sakai)と申します。何卒よろしくお願い致します。 Swift/Kotlin愛好会Advent Calendarでは、「Androidアプリでバックグラウンド再生機能を実現するためのヒントとiOSアプリとの見比べた際の特徴を簡単にまとめてみた」というタイトルにて、iOS/Androidアプリでのバックグラウンド再生機能を実現する上で、ポイントになり得る点を双方の比較をしながら解説した記事を書きましたので、こちらもご覧頂けますと嬉しく思います。 今回はUIまわりのトピックではなく、実務や個人開発を通じて、OSSライブラリのDI(Dependency Injection)を実現するためのライブラリを参考にしながら、自前で実装する&リプレイスするが機会がありましたので、自分なりの事例に関して簡単ではありますがご紹介ができればと思います。 以前登壇した際の発表資料: また、今回の内容につきましては、potatotips #76 (iOS/Android開発Tips共有会)にて登壇の際に利用した資料を下記のリンクにて共有しておりますので、こちらも是非ご活用頂けますと幸いです。紹介しているスライドの方では、実際の業務内で導入するまでのアイデアや検証の過程、そして導入をしていくところまでの流れを簡単にまとめています。 Slide: https://www.slideshare.net/fumiyasakai37/didiy 2. 自作したDIコンテナにおいて参考にした資料やドキュメント等の紹介 以前にも「ライブラリを利用しない実現方法のアイデア」の一例として【実装MEMO】PropertyWrappersの機能を利用したDependency Injectionのコードに触れた際の備忘録をMedium内で軽くまとめた事もあり、これを応用できないかを最初は考えました。ですが、実際のプロジェクトへ導入するための検証を重ねていく中で少しそぐわなかったので、よく利用されているOSSライブラリでの実装方法等も参考にしながら下記の様な形をとってみました。 ⭐️ 2-1. 今回自作を試みたDIコンテナ例で主に参考にした資料 今回参考にした手法は、「How to: create your SUPER simple dependency injector container in Swift」で紹介されていた方法になります。この記事内で紹介されている方法でのポイントは、大きなシングルトンの中に型と名前で管理された必要な責務のインスタンスを格納するという点になるかと思います。 今回、適用を考えていたアプリにおいて一番大事な部分は、 1. コンストラクタインジェクションができること 2. Local / Remoteで同じProtocolを使う際も名前をつけ方で管理できること という2点だったので、この方針で大きな問題はなさそうと判断しました。 まずは、DIコンテナ用のクラスを準備は下記の様な形で準備をすることとなります。 DependeciesContainer.swift import Foundation final class DependeciesContainer { // MARK: - DIコンテナ自体はSingletonとして保持する static let shared = DependeciesContainer() private init() {} // MEMO: DependencyKeyクラスを利用して「型」と「名前」を利用して分類する private var dependecies: [DependencyKey: Any] = [:] // MARK: - Function func register<T>( _ type: T.Type, impl: Any, name: String? = nil ) { let dependencyKey = DependencyKey(type: type, name: name) dependecies[dependencyKey] = impl } func resolve<T>( _ type: T.Type, name: String? = nil ) -> T { let dependencyKey = DependencyKey(type: type, name: name) if let dep = dependecies[dependencyKey] as? T { return dep } else { // MEMO: 設定し忘れがあった場合にはfatalErrorで検知できるようにする let protocolTypeName = NSString(string: "\(type)").components(separatedBy: ".").last! fatalError("\(protocolTypeName)の依存性を解決できませんでした。当該実装クラス:\(protocolTypeName).") } } } final class DependencyKey: Hashable, Equatable { // MARK: - Properties private let type: Any.Type private let name: String? // MARK: - Initializer init(type: Any.Type, name: String? = nil) { self.type = type self.name = name } // MARK: - Hashable, Equatable func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(type)) hasher.combine(name) } static func == (lhs: DependencyKey, rhs: DependencyKey) -> Bool { return lhs.type == rhs.type && lhs.name == rhs.name } } そして次に、それぞれの責務におけるクラス同士の依存関係の解決を下記の様な形で図っていくためのクラスを定義し、このクラスのインスタンスをAppDelegate.swiftへ適用するような形とします。 【1. 依存関係の解決を図る部分の処理】 DependenciesDefinition.swift final class DependenciesDefinition { // MARK: - Function func inject() { // MEMO: インスタンスを保持するための場所 let dependecies = DependeciesContainer.shared // ※途中省略 // MARK: - Infrastructure // → アプリ内DataStore処理やRestAPI/GraphQLのクライアントに関する処理 dependecies.register( MovieQualityLocalStore.self, impl: MovieQualityLocalStoreImpl() ) dependecies.register( ApiClient.self, impl: ApiClientManager.shared ) // ※以降はInfrastructure層の依存関係解決処理が続きます... // MARK: - Repository // → アプリ内DataStore処理からのデータ取得&保存処理やRestAPI/GraphQLからのデータ取得処理 dependecies.register( FeaturedMovieRepository.self, impl: FeaturedMovieRepositoryImpl( apiClient: dependecies.resolve(ApiClient.self), backgroundScheduler: dependecies.resolve(ImmediateSchedulerType.self, name: background) // API通信処理はバックグラウンドスレッドで実施したい ) ) // ※以降はRepository層の依存関係解決処理が続きます... // MARK: - UseCase // → Repositoryクラス/Serviceクラスで定義した処理を組み合わせて実現するビジネスロジックに関する処理 dependecies.register( GetMainUseCase.self, impl: GetMainUseCaseImpl( mainBannerRepository: dependecies.resolve(MainBannerRepository.self), mainNewsRepository: dependecies.resolve(MainNewsRepository.self), featuredMovieRepository: dependecies.resolve(FeaturedMovieRepository.self), mainMovieRepository: dependecies.resolve(MainMovieRepository.self) ) ) // ※以降はUseCase層の依存関係解決処理が続きます... // MEMO: 上記の処理例では、Infrastructure → Repository → UseCaseという順番で依存関係の解決を図っていく形となります。 } } 【2. アプリ起動時に必要なインスタンスを初期化する】 AppDelegate.swift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // MEMO: 自作でのDependeciesContainerをインスタンス化する DependenciesDefinition().inject() // ※途中省略 } } 形としてはかなり愚直な形の記述方法にはなってしまいますが、それぞれの責務毎にある程度分離された形となっているならば整理はしやすいのではないかと思います。ただ裏を返せば、責務の分離を曖昧な感じにしてしまうと必要のない順番を気にしなければいけなくなるので、その点には少し気をつけると良いかもしれません。 ⭐️ 2-2. iOS/AndroidでのDIライブラリやその他Dependency Injectionを理解する上での参考資料 ライブラリに依存しない形ではあるけれども、できるだけ似た感覚で書ける形を模索する際においても、OSSライブラリを活用したDIの導入事例や他のプロジェクトの実装事例に関する記事等にも幅広く触れることで、現在開発中のアプリに導入する際の検討材料や知見になるかと思いますので、まずは簡単なサンプル実装を試してみたりすることで事前のイメージを掴んでおくと更に良さそうに思います。 【iOSでのライブラリ例】 Swinject DITranquillity needle 【Androidでのライブラリ例】 Dagger2 Dagger Hilt 【今回改めて復習する際に参考にした資料】 SwinjectとDIKitでDependency Injectionしてみた Swiftで外部ライブラリを使わないDIの管理方法 3. RxSwiftを利用した構成に対しての適用していく部分の解説 ここからは、前述したDIを適用した際の記載例についてもいくつかの例をピックアップしてみます。 (※僕自身はこれまでSwinjectを利用する機会が多かったのですが、もちろんSwinjectでも同様の事は可能です。) ⭐️ 3-1. 処理レイヤーによって利用するスレッドを適切に指定するために第2引数を活用する 前述したDIコンテナについては第2引数に名前を定義することを利用して、メインスレッド/バックグラウンドスレッドでの操作を必要な責務の処理に応じて適用できるようにする例になります。 // ---------- // (1) DependenciesDefinitionクラスのinject()内での記述 // ---------- // 処理を実行する際にバックグラウンドスレッドにしたい際につける名前 let background = "background" // メインスレッド動作 dependecies.register( ImmediateSchedulerType.self, impl: MainScheduler.instance ) // バックグラウンドスレッド動作 dependecies.register( ImmediateSchedulerType.self, impl: SerialDispatchQueueScheduler(qos: .default), name: background // 名前を付与する ) // ---------- // (2) Presenter側での処理はUIへの取得データ反映処理なのでメインスレッドで実行 // ---------- static func createMainPresneter() -> MainPresenter { return MainPresenterImpl( getMainUseCase: dependecies.resolve(GetMainUseCase.self), getFavoriteMainMoviesUseCase: dependecies.resolve(GetFavoriteMainMoviesUseCase.self), saveFavoriteMainMovieUseCase: dependecies.resolve(SaveFavoriteMainMovieUseCase.self), // MEMO: 名前がない場合はメインスレッドでの処理となる mainScheduler: dependecies.resolve(ImmediateSchedulerType.self) ) } // ---------- // (3) Repository側での処理はAPI / GraphQLでの処理なのでバックグラウンドスレッドで実行 // ---------- dependecies.register( CategoryRepository.self, impl: CategoryRepositoryImpl( graphQLClient: dependecies.resolve(GraphQLClient.self), readApiCacheClient: dependecies.resolve(ReadApiCacheClient.self), // MEMO: 第2引数で名前を付与した場合はバックグラウンドスレッドでの処理となる backgroundScheduler: dependecies.resolve(ImmediateSchedulerType.self, name: background) ) ) ⭐️ 3-2. Cacheを利用するか?サーバーとの通信を利用するか適切に指定するために第2引数を活用する 先程のメインスレッド/バックグラウンドスレッドでの操作と同じ様な要領で、Localでのキャッシュ機構からのデータ取得処理/GraphQLから非同期通信でのデータ取得処理についても、第2引数での名前を利用した管理を使う例になります。記載しているコードについてはRxSwiftを利用した処理での事例になりますが、責務に応じたスレッドの適用と同時にLocalでのキャッシュ機構からのデータ取得処理/GraphQLから非同期通信でのデータ取得処理についても適切な名前をつけながら整理していくイメージを持って頂くと分かりやすいかもしれません。 // ---------- // (1) DependenciesDefinitionクラスのinject()内での記述 // ---------- // Cacheからのデータ取得をする際につける名前 let local = "local" // ---------- // (2) GraphQLから非同期通信でデータを取得する // ---------- dependecies.register( CourseRepository.self, impl: CourseRepositoryImpl( graphQLClient: dependecies.resolve(GraphQLClient.self), readApiCacheClient: dependecies.resolve(ReadApiCacheClient.self), backgroundScheduler: dependecies.resolve(ImmediateSchedulerType.self, name: background) ) ) // ---------- // (3) Localでのキャッシュ機構からデータを取得する // ---------- dependecies.register( CourseRepository.self, impl: LocalCourseRepositoryImpl( readApiCacheClient: dependecies.resolve(ReadApiCacheClient.self), backgroundScheduler: dependecies.resolve(ImmediateSchedulerType.self, name: background) ), // MEMO: 第2引数で名前を付与した場合は同じ型でもLocalから取得したデータを利用する name: local ) // ---------- // (4) Localのキャッシュ機構またはGraphQLでの処理を利用したRepository層の例(RxSwiftを利用した処理記載例) // ---------- import RxSwift protocol CourseRepository { func findAll() -> Single<[Course]> } // (4)-1: Localのキャッシュ機構からデータを取得する処理例 final class LocalCourseRepositoryImpl: CourseRepository { private let readApiCacheClient: ReadApiCacheClient private let backgroundScheduler: ImmediateSchedulerType init( readApiCacheClient: ReadApiCacheClient, backgroundScheduler: ImmediateSchedulerType ) { self.readApiCacheClient = readApiCacheClient self.backgroundScheduler = backgroundScheduler } func findAll() -> Single<[Course]> { // キャッシュ機構に格納されているEntityデータを取得してDomainModelへ変換して返す return readApiCacheClient.getCourseEntities() .subscribe( on: backgroundScheduler // ← Repository処理なのでバックグラウンドスレッドで実行したい ).map { courseEntities in courseEntities.map { courseEntity in Course(courseEntity) } } } } // (4)-2: GraphQLでの処理からデータを取得する処理例 final class CourseRepositoryImpl: CourseRepository { private let graphQLClient: GraphQLClient private let readApiCacheClient: ReadApiCacheClient private let backgroundScheduler: ImmediateSchedulerType init( graphQLClient: GraphQLClient, readApiCacheClient: ReadApiCacheClient, backgroundScheduler: ImmediateSchedulerType ) { self.graphQLClient = graphQLClient self.readApiCacheClient = readApiCacheClient self.backgroundScheduler = backgroundScheduler } func findAll() -> Single<[Course]> { // GraphQLからEntityデータを取得してDomainModelへ変換して返す return graphQLClient.getCourses() .subscribe( on: backgroundScheduler // ← Repository処理なのでバックグラウンドスレッドで実行したい ).do( afterSuccess: { [weak self] entities in guard let weakSelf = self else { return } // GraphQLからEntityデータを取得が成功した後にキャッシュ機構に保存する weakSelf.readApiCacheClient.saveCourseEntities(entities) } ).map { entities in entities.map { entity in Course(entity) } } } } // ---------- // (5) Local・Remote対応のUseCaseをそれぞれ作成する // ---------- dependecies.register( GetCoursesUseCase.self, impl: GetCoursesUseCaseImpl( courseRepository: dependecies.resolve(CourseRepository.self) ) ) dependecies.register( GetCoursesUseCase.self, impl: GetCoursesUseCaseImpl( courseRepository: dependecies.resolve(CourseRepository.self, name: local) ), // MEMO: 第2引数で名前を付与した場合は同じ型でもLocalから取得したデータを利用する name: local ) // ---------- // (6) Presenter側での処理を記載する(RxSwiftを利用した処理記載例) // ※ こちらの処理については実際のPresenter処理内部における一部分を抜粋したものになります // ---------- private let getCoursesUseCase: GetCoursesUseCase private let localGetCoursesUseCase: GetCoursesUseCase private let mainScheduler: ImmediateSchedulerType private let disposeBag = DisposeBag() func viewWillAppear() { // まずはキャッシュ取得処理を実行する prefetchAndSetup { [weak self] in guard let weakSelf = self else { return } // まずはキャッシュ取得処理が終了次第、続けてAPI取得処理を実行する weakSelf.fetchAndSetup() } } private func prefetchAndSetup(completion: @escaping () -> Void) { localGetCoursesUseCase.execute() .do( onSubscribe: { [weak self] in guard let weakSelf = self else { return } // 処理開始時に実行したい処理 }, onDispose: { // キャッシュ取得処理の購読が終わったら実行したい処理をクロージャーで引き渡す // 例: キャッシュデータ反映処理後にAPI通信処理を試みる流れ completion() } ) .observe( on: mainScheduler // ← Presenter処理なのでメインスレッドで実行したい ) .subscribe( onSuccess: { [weak self] courseDtos in guard let weakSelf = self else { return } // キャッシュ取得処理が成功した場合 // 例: キャッシュデータをまずは画面に一時的に表示する }, onFailure: { _ in // キャッシュ取得処理が失敗した場合 } ) .disposed(by: disposeBag) } private func fetchAndSetup() { getCoursesUseCase.execute() .observe( on: mainScheduler // ← Presenter処理なのでメインスレッドで実行したい ) .subscribe( onSuccess: { [weak self] courseDtos in guard let weakSelf = self else { return } // API取得処理が成功した場合 // 例: API取得データを正式に画面に表示する }, onFailure: { [weak self] _ in guard let weakSelf = self else { return } // API取得処理が失敗した場合 // 例: 通信エラーを通知するダイアログを表示する } ) .disposed(by: disposeBag) } ⭐️ 3-3. PresenterとViewController部分に関する処理例 最後に画面を生成するタイミングでPresenterクラスを渡す部分に関する例になります。画面生成時にPresenterクラスを初期化したいので、この部分についてはDIコンテナに登録しないでFactoryメソッドを別途定義する形をとっています。 // ---------- // (1) Infrastructure/Repository/UseCaseまでの依存関係をDIコンテナに登録する // ---------- final class DependenciesDefinition { // MARK: - Function func inject() { // MEMO: Cacheからのデータ取得をする際につける名前 let local = "local" // MEMO: 処理を実行する際にバックグラウンドスレッドにしたい際につける名前 let background = "background" // MEMO: インスタンスを保持するための場所 let dependecies = DependeciesContainer.shared // ※以降はInfrastructure/Repository/UseCaseの依存関係の解決処理が続く... } } // ---------- // (2) Presenterに適用するためのFactoryメソッドを定義する // ---------- final class PresenterFactory { // MEMO: Cacheからのデータ取得をする際につける名前 private static let local = "local" // MEMO: 処理を実行する際にバックグラウンドスレッドにしたい際につける名前 private static let background = "background" // MEMO: インスタンスを保持するための場所 private static let dependecies = DependeciesContainer.shared // MARK: - Static Function static func createMainPresenter() -> MainPresenter { return MainPresenterImpl( getCoursesUseCase: dependecies.resolve(GetCoursesUseCase.self), localGetCoursesUseCase: dependecies.resolve(GetCoursesUseCase.self, name: local), mainScheduler: dependecies.resolve(ImmediateSchedulerType.self) ) } // ※以降は各種Presenterに適用するFactoryメソッド定義が続く... } // ---------- // (3) PresenterについてはFactoryメソッドを利用して適用する // ---------- final class MainViewController: UIViewController { // MARK: - Property private let presenter: MainPresenter // ※途中省略 // MARK: - Override override func viewDidLoad() { super.viewDidLoad() presenter = PresenterFactory.createMainPresenter() } // ※途中省略 } 4. あとがき iOSではSwinjectやDIKit・AndroidではDagger2やDaggerHiltについては多少自分の手元でも軽く試した経験はあったものの、DIコンテナ部分を自前で作成して実際のアプリ内に適用したり、既存のライブラリからこれまでの実装方針をできるだけ崩すことない形でのリプレイスを経験を通して、iOS/Android共にDI用の有名ライブラリがあり、それぞれ特徴的な部分があるので、平素の開発においては慣習的となっている部分に改めて深く触れることでその特徴やメリット・デメリットを再認識する良い機会になったと共に、既存である程度の規模感があるプロジェクトでは、チームの中で「しっくり来る形」を見出す準備の大切さを感じると同時に、自前でこの部分を実装する際にはチームの状況等を鑑みて良い特徴となり得る部分を積極的に取り入れていくと良さそうに思いました。 今年を改めて軽く振り返ってみると、AndroidやFlutterに関するインプットは不定期ではありますが、実践できてはいたものの特にiOS側のアウトプットは若干少な目になってしまった点は反省の余地があるかと思います。そして、まだまだ新しい技術を利用したUI実装については実践し切れていない部分もあったので、来年はバランスを考えながらアウトプットできればと考えておりますので、引き続きよろしくお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift Concurrency で top-level await できずに困った話

まとめ main.swift を投げ捨てて、 @main ディレクティブを持つ別名ファイルを用意する。 経緯 async / await で非同期処理を手続的に記述的に書けるようになったし、ちょっとしたスクリプトも Swift で実装してみるか。。 swift package init --type executable でパッケージを用意して、 platforms だけちょっと修正。 Package.swift platforms: [ .macOS(.v12), ], あとは、 main.swift に処理を書けば完成っと。 main.swift import Foundation let url = URL(string: "https://httpbin.org/delay/3")! // API を呼び出してデータ取得 let (data, _) = try await URLSession.shared.data(from: url) // API 呼び出しが完了したら、結果を表示 if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), let root = jsonObject as? [String: Any], let headers = root["headers"] as? [String: Any] { print("Your User-Agent is \(headers["User-Agent"] ?? "")") } あーはいはい、 async メソッド呼び出しなので、 Task で括ってあげる必要があるのね。 main.swift import Foundation Task { let url = URL(string: "https://httpbin.org/delay/3")! // API を呼び出してデータ取得 let (data, _) = try await URLSession.shared.data(from: url) // API 呼び出しが完了したら、結果を表示 if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), let root = jsonObject as? [String: Any], let headers = root["headers"] as? [String: Any] { print("Your User-Agent is \(headers["User-Agent"] ?? "")") } } あれ、結果が表示されない? Task が非同期実行されるから、Task 完了前に main.swift の処理が終了しちゃうのか。 Task の実行を待ち合わせるとなると、 main.swift import Foundation let semaphore = DispatchSemaphore(value: 0) Task { defer { semaphore.signal() } let url = URL(string: "https://httpbin.org/delay/3")! // API を呼び出してデータ取得 let (data, _) = try await URLSession.shared.data(from: url) // API 呼び出しが完了したら、結果を表示 if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), let root = jsonObject as? [String: Any], let headers = root["headers"] as? [String: Any] { print("Your User-Agent is \(headers["User-Agent"] ?? "")") } } semaphore.wait() いや、これはおかしい。。 対策 Swift のエントリポイントとしては、 main.swift @main ディレクティブを付与した型の main() メソッド https://github.com/apple/swift-evolution/blob/master/proposals/0281-main-attribute.md のどちらかを利用できるため、後者を利用します。 @main ディレクティブを利用する場合、main.swift が存在してしまうと NG なので、 main.swift を適当なファイル名にリネームしつつ、以下のように書き換えます。 Entry.swift import Foundation @main struct Entry { static func main() async throws { // ここで定義する main() は async にできる let url = URL(string: "https://httpbin.org/delay/3")! // API を呼び出してデータ取得 let (data, _) = try await URLSession.shared.data(from: url) // API 呼び出しが完了したら、結果を表示 if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []), let root = jsonObject as? [String: Any], let headers = root["headers"] as? [String: Any] { print("Your User-Agent is \(headers["User-Agent"] ?? "")") } } } 備考 調べてみると、きちんと async / await の Proposal でも言及されていました。 Because only async code can call other async code, this proposal provides no way to initiate asynchronous code. This is intentional: all asynchronous code runs within the context of a "task", a notion which is defined in the Structured Concurrency proposal. That proposal provides the ability to define asynchronous entry points to the program via @main, e.g., @main struct MyProgram { static func main() async { ... } } Additionally, top-level code is not considered an asynchronous context in this proposal, so the following program is ill-formed: func f() async -> String { "hello, asynchronously" } print(await f()) // error: cannot call asynchronous function in top-level code This, too, will be addressed in a subsequent proposal that properly accounts for top-level variables. とのことなので、 将来的には main.swift での top-level await もサポートされそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIでFormatStyleを使って色々なデータをTextで表示

概要 SwiftUIで文字を表示するTextでは文字列を表示できますが、色々なデータも変換を通して表示できます。 以前からFormatterを使った表示がサポートされていましたが、iOS 15から、FormatStyleというものが加わり、Formatterを作らずとも色々なデータをそのままTextで表示できるようになりました。 表示方法 日付 (Date) inputにDate、formatにDate.FormatStyleを指定します。 struct SwiftUIView: View { let date: Date = Date() var body: some View { Text(date, format: Date.FormatStyle.dateTime) .font(.largeTitle) } } 対応するFormat Date.ComponentsFormatStyle Date.FormatStyle Date.ISO8601FormatStyle Date.IntervalFormatStyle Date.RelativeFormatStyle Date.VerbatimFormatStyle バイトカウント inputにInt64、formatにByteCountFormatStyleを指定します。 struct SwiftUIView: View { let value: Int64 = 100000000 var body: some View { Text(value, format: ByteCountFormatStyle.byteCount(style: .binary)) .font(.largeTitle) } } 対応するFormat ByteCountFormatStyle ByteCountFormatStyle.Attributed 数値(Decimal) inputにDecimal、formatにDecimal.FormatStyleを指定します。 struct SwiftUIView: View { let value: Decimal = 100000000 var body: some View { Text(value, format: Decimal.FormatStyle.Currency.currency(code: "JPY")) .font(.largeTitle) } } 対応するFormat Decimal.FormatStyle Decimal.FormatStyle.Attributed Decimal.FormatStyle.Currency Decimal.FormatStyle.Percent FloatingPointFormatStyle inputにFloatなどのBinaryFloatingPoint、formatにFloatingPointFormatStyleを指定します。 struct SwiftUIView: View { let value: Float = 0.01 var body: some View { Text(value, format: FloatingPointFormatStyle.Currency.currency(code: "USD")) .font(.largeTitle) } } 対応するformat FloatingPointFormatStyle FloatingPointFormatStyle.Attributed FloatingPointFormatStyle.Currency FloatingPointFormatStyle.Percent Measurement inputにMeasurement<UnitTemperature>、formatにMeasurement.FormatStyleを指定します。 現状は温度にしか対応していません。 コードは摂氏を華氏で表示しています。 struct SwiftUIView: View { let value: Measurement<UnitTemperature> = .init(value: 36.5, unit: .celsius) var body: some View { Text(value, format: Measurement.FormatStyle.measurement(width: .abbreviated)) .font(.largeTitle) } } 対応するformat Measurement.FormatStyle IntegerFormatStyle inputにInt、formatにIntegerFormatStyleを指定します。 struct SwiftUIView: View { let value: Int = 10000 var body: some View { Text(value, format: IntegerFormatStyle.Currency.currency(code: "JPY")) .font(.largeTitle) } } 対応するformat IntegerFormatStyle IntegerFormatStyle.Attributed IntegerFormatStyle.Currency IntegerFormatStyle.Percent ListFormatStyle inputにStringの配列、formatにListFormatStyleを指定します。 struct SwiftUIView: View { let value = ["a","b","c"] var body: some View { Text(value, format: ListFormatStyle.list(type: .and)) .font(.largeTitle) } } 対応するformat ListFormatStyle PersonNameComponents.FormatStyle inputにPersonNameComponentsの配列、formatにPersonNameComponents.FormatStyleを指定します。 struct SwiftUIView: View { let value: PersonNameComponents = PersonNameComponents(namePrefix: "Esq.", givenName: "Thomas", middleName: "Louis", familyName: "Clark", nameSuffix: "Esq.", nickname: "Tom") var body: some View { Text(value, format: PersonNameComponents.FormatStyle.name(style: .abbreviated)) .font(.largeTitle) } } 対応するformat PersonNameComponents.AttributedStyle PersonNameComponents.FormatStyle
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftでクロージャーを学ぶ

こんにちわ 今回はswiftでクロージャーについて学んだので、備忘録としてこの記事に載せていこうと思います! クロージャーとは クロージャーとは、文の中に直接埋め込む命令の塊を作ることができる機能です。 基本の形は以下の通りです。 exapmle.swift //基本的な形 {(引数名: 型) -> 戻り値の型 in 文 } //実際例1 var Triangle = {(length: Int) -> Int in return length * length / 2 } Triangle(4) //8 //実際例2 var Square = {(length: Int) -> Int in var width = length return width * 5 } Square(4)//20 このように使うことができます。 また、一文の場合はreturnを省略することもできます。 example.swift //returnの省略 var Square = {(length: Int) -> Int in length * length } Square(4)//16 クロージャーのメリットとは 私はクロージャーを学び始めたてなのでそんなに詳しくはないのですが。 コードが見やすくなることが1番のメリットだと考えました。 まず関数を使った処理を見ていきましょう。 example.swift //関数を使った場合 func Calculation(NumOne:Int, NumTwo: Int, SumNumber:(Int,Int) -> Int) -> Int{ return SumNumber(NumOne,NumTwo) } func add(NumOne:Int,NumTwo:Int) -> Int { return NumOne + NumTwo } print(Calculation(NumOne:3, NumTwo:4, SumNumber:add)) これにクロージャーを使うと以下のようになります。 exapmle.swift func Calculation(NumOne:Int, NumTwo: Int, SumNumber:(Int,Int) -> Int) -> Int{ return SumNumber(NumOne,NumTwo) } print(Calculation(NumOne:3, NumTwo:4, SumNumber:{(a:Int,b:Int) -> Int in a+b})) また、型を省略することができます。 example.swift func Calculation(NumOne:Int, NumTwo: Int, SumNumber:(Int,Int) -> Int) -> Int{ return SumNumber(NumOne,NumTwo) } print(Calculation(NumOne:3, NumTwo:4, SumNumber:{(a,b) in a+b})) こっちの方がスッキリしてるかもですね。 トレイリングクロージャーとは トレイリングクロージャーは、関数の最後の引数がクロージャーの場合、カッコの外にクロージャーを記述することができる方法です。 以下のコードを見てください。 example.swift func NamePrint(name: String, closure: (String)->Void) { closure("私は\(name)です") } //トレイリングクロージャーなし NamePrint(name:"naoki",closure:{ string in print(string) }) //トレイリングクロージャーあり NamePrint(name:"naoki") { string in print(string)} メリットとしては、コードの可読性が上がり、見やすくなる事です。 まとめ クロージャーはコードの可読性を上げるために使う。。。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめてのエンジニア就職?PR出し?Swift初学者が自分のコード晒す前にせめてここだけはチェックしておいたほうがよいよ集

こんにちは。 1年半前くらいに異業種からiOSエンジニアに転職した@pocoochanです。 当時はApp Storeでリリースしたアプリを携えて就活していたのですが、いま思えばお粗末すぎるコードでした。 これからiOSエンジニアとして就職したり、インターンなどではじめてPRを出したりするみなさんへ、 自分の開発したコードをはじめて他のエンジニアの方に見てもらう前に、ここだけは自分でチェックしておくとよいよという箇所をまとめてみました。偉そうに書いていますが、かなり自戒も込めています。 まじで最低限のことを抜粋したので、「同じ書き方ならこう書き換えたほうがよい」みたいなものとかは載せていないです。 コードの提出まで時間がないよという方もこれだけはササっと最後確認してみて。 基本のキみたいな話ばかりですが、「基礎だけは揺らがない」ってドクターストーンズで千空ちゃんが言っていたので間違いないです。 別ファイルから変更されたくない変数や別ファイルから呼ばない関数はprivateをつける privateはアクセス修飾子というものです。 変数や関数にprivateをつけることで、宣言ファイル内でしか使わないよ〜んという意味になります。 privateがついていないと、複数のファイルから関数が呼ばれているのかな〜と思いながら読めてしまうし、変数の値を別ファイルで書き換えたりとかもできます。 たとえばパラメータの初期値を1行変えようと思っても、privateがないと他から書き換えられるので、プロジェクト内で5000ファイルあればその中のどこかで使われているかもしれない・・・となり、5000ファイルチェックしないといけないことになります。 アクセス修飾子はほかにもあるので、この記事とか参考にしてみてください 強制的アンラップをしていないか確認 nilを格納できない型に、オプショナルな型の値を代入しようとするとエラーがでます。 そんなときに!を使ったりしてる箇所、ありませんか? 強制的アンラップやオプショナルについての詳しい説明はここでは割愛しますが、 !を使うと、アンラップしたい変数がnilだった時にアプリがクラッシュしてしまいます。 なので!はまっさきに駆逐し、guard letやif letを使って中身がnilかどうかによって処理を分岐させる方法に書き換えておきましょう! これで値がnilだったとしてもクラッシュさせないことができます。 オプショナルバインディングや、??演算子で調べるとこのへんに関する記事がたくさん出てくるので、コードの書き方などはそちらを参考にしてみてください。 Swiftの?と!について復習したい方は下記記事をどうぞ。 変数名や関数名やファイル名に数値を使っていないか確認 FirstViewController SecondViewController 信じがたいですが、いちばんはじめに作ったアプリのファイル名をこんなふうにしていました。 ほかにも、たとえばlabelやImageViewの名前に数字を入れて順番を管理したりしていないか?を確認してみましょう let dummyLabel1 = "hogehoge" let dummyLabel2 = "fugafuga" addSubViews(dummyLabel1) addSubViews(dummyLabel2) viewの表示順番が変わったりすると、改修がはいったときに名前の変更も必要になり、 改修に手間がかかっててしまうので数字をいれてしまっているところは意味のある名前に変えておこう 変数や関数につけた名前と使われ方、処理内容が整合しているか確認 関数につけている名前以外の処理はさせないようにしよう! たとえば下記の例のように、requestってつけているのにViewの更新とかまでしちゃってない? わたしは今でも気を抜くと、つい名付け以上のことを処理させちゃっていることあります。確認してみよう。 private func requestHogeHoge() { APICliant.requestHogeHoge~~~ ... collectionView.reloadData() } 処理内容と整合させようとしたらめちゃくちゃ長い関数名になっちゃうよ!という場合は、1つの関数でいろんなお世話をさせすぎな場合もあります。 もっと適切に分けることはできないか考えなおしてみるのも手です。 返り値のない命令的なfuncは動詞で書く このように定義してしまうと、自然に返り値があるものだと読めてしまうので、呼び出す時の可読性が損なわれます。 private func normalMargin() { hogehogeConstraint.constant = 10 } 呼び出す側を見てみると・・・ //どこかに実装されている private func normalMargin() { hogehogeConstraint.constant = 10 } func hoge() { //こう取得できると勘違いしてしまう let normalMarign = self.normalMargin() } これを動詞はじまりの関数名に書き換えると、 //どこかに実装されている private func setupNormalMargin() { hogehogeConstraint.constant = 10 } func hoge() { //この中でマージンを設定しているんだなと分かる setupNormalMargin() } ちなみに、classやstruct名は名詞で書きます!それもついでに確認しておこう! ローカル変数でよいものをメンバ変数にしない 1つのfuncでしか使っていない変数をメンバでとっていたりすることがよくありました。(自戒) スコープを意識してコードを見直してみよう。 private func hogehoge() { var hogeInt = 1 hogeInt += 5 } ローカル変数とメンバ変数の違いがよくわからなくなったらこちらの記事が参考になります。 むだにselfをつけない 下記例のように、selfをつけなくても良いのにつけていると、そこに特別な意味があるのでは?と思われ可読性を損なうので、必要ないselfは削除しよう。 func viewDidLoad() { self.tableView.delegate = self self.tableView.dataSource = self } 変数名や関数名を見ればわかるコメントを書かない 下記みたいな感じで書いてしまっているところはありませんか? func viewDidLoad() { // マージンを10に設定 let margin = 10 // UIのsetupをする setupUI() } 関数名を見ただけで一目瞭然なことを繰り返しコメントで書く必要はないので、そういったコメントは削除しちゃいましょう。 大前提、第三者が適切にコードを把握できるように積極的にコメントをつけることはとても良いことです。 できるだけコメントを書くなということを言いたいわけではないので、コメントは積極的につけていきましょう★ 下記のように、注意点やなぜその実装になっているのかの説明や、コードを読むだけでは把握しづらい背景などを書くとよいです。 func viewDidLoad() { // ここのマージンとXXX.swift内のマージンを揃えること。レイアウト修正時には注意 let margin = 10 setupUI() } 変数名の最初の文字がうっかり大文字になっていないか private func setupUI() { let HogeId = xxx } これ自分まじでありました。 ちがうもんね!変数名の最初を大文字にするムーブを流行らせようかと思っただけだもんね! ・・・嘘です。うっかりミスです。ごめんなさい。 タイポを再度かくにん! もうこれはほんと自戒を込める。 見落とすこと、あるある・・・。最後にもういちど確認してみよう! PRを出すなら こちらの記事がとても参考になるので確認してみてください おわり 以上です。すこしでも参考になっていればうれしいです。 最低限、気をつけておくに越したことはない点をカバーできているコードになったかとおもいます。 こうした基本的な部分での指摘でレビュワーの貴重なリソースを割かないように気をつけていきましょおお(自分への戒め) 「こんなのもうぜんぶ完璧でした」という方はすでにつよつよえんじにあです。 すごい!!自信をもって面接やPR出しをしてください! 他にもなにか思いついたら逐一追加するかもです。 ではでは。 Special Thanks! @ ZOZOTOWN iOSチームのみんな いつも丁寧なレビューをしてくれるおかげでこの記事が書けました。 iOSチームの手厚いレビュー事情については、tech talkで話したので興味ある方は見てみてください。 ZOZO Tech Talk #2 iOS アーカイブ動画 登壇資料 @sakiyamaK 独学でコード書いていた時にコードレビューをしてもらったり色々教えてもらいとてもお世話になりました。現役エンジニアの方で、個人でiOSを教えてくれているので一度コードレビューを受けてみるとかも良いかもです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最新の超解像モデルReal ESRGANをiOSでつかう

最新の超解像機械学習モデルがiOSでつかえる 320*320 ↓ 超解像 ↓ 1280*1280 小さなサイズの画像を鮮明に、大きなサイズにしてくれる超解像モデル。 2021年発表のReal ESRGANもiOSで使える。 機械学習モデルを扱うのは難しそう? とはいえ、最新の機械学習モデルをつかうのはたくさん手順が必要なんじゃないの? 変換済みのCoreML Modelをつかえばかんたん Real ESRGANも変換済みのものがあります。これをつかえばすぐにiOSで超解像ができます。 かんたん手順 CoreML-ModelsからReal-ESRGANのCoreMLモデルをダウンロードします。 プロジェクトにドラッグします。 Visionで実行します。 guard let model = try? VNCoreMLModel(for: real_esrgan(configuration: MLModelConfiguration()).model) else {fatalError("model initialization failed")} let coreMLRequest = VNCoreMLRequest(model: model) let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]) try? handler.perform([coreMLRequest]) guard let result:CVPixelBuffer = coreMLRequest.results?.first as? VNPixelBufferObservation else { return } これで1280*1280のサイズに超解像された画像が取得できます。 超解像をプレビューするだけなら、ダウンロードしたmlmodelファイルを開いてお手持ちの画像をドロップすると、 プレビューができます。 ? フリーランスエンジニアです。 お仕事のご相談こちらまで 簡単な開発内容をお書き添えの上、お気軽にご連絡ください。 rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む