- 投稿日:2021-01-21T23:05:10+09:00
[Swift]型のプロパティについて
Swiftの型
Swiftの型は、クラス、構造体、列挙型として定義できます。
クラス、構造体、列挙型の3つには、メソッドやプロパティなどの共通の要素が用意されています。宣言方法
struct 構造体名{ 構造体の定義 } class クラス名{ クラスの定義 } enum 列挙型名{ 列挙型の定義 }インスタン化の方法
型をインスタンス化するには、以下のようにします。
その時同時にイニシャライザが呼び出されます。イニシャライザは、インスタンス化する時に呼びだれるメソッドです。型名()プロパティ
型に紐づいた値のことをプロパティと呼びます。
より詳しくいうと、型の中で定義された変数をプロパティと言います。定義方法
プロパティはvarやletで定義します。
struct 構造体名{ //再代入可能なプロパティ var プロパティ名:プロパティの型=値 //再代入不可能なプロパティ let プロパティ名:プロパティの型=値 }プロパティの整合性を保つために、インスタンス化するまでに全てのプロパティの型を決める必要があるのでプロパティに初期値を代入する必要があります。
構造体の中でプロパティを初期化するか、イニシャライザで初期化するかいづれかの方法でプロパティに値を渡します。プロパティの分類
プロパティには2種類あります。
1.インスタンスに紐づくプロパティ(インスタンスプロパティ)
2.型に紐づくプロパティ(スタティックプロパティ)
インスタンスプロパティ
struct Area{ var tate=10 var yoko=10 } var A_1=Area()//インスタンス化 var A_2=Area()//インスタンス化 A_1.tate=3 A_2.tate=7 print(A_1.tate)//3 print(A_2.tate)//7インスタンス化とは実物にするということです。
つまり、A_1とA_2という二つの物体を生成したことになります。
そのA_1とA_2の縦を変更したので、それぞれ3と7になります。
じゃあ、元々の縦を変更したい時にはどうすれば良いでしょうか。
その時は、staticキーワード
を使いますスタティックプロパティ
struct Area{ static var tate=10 var yoko=10 } var A_1=Area() var A_2=Area() Area.tate=3 A_1.tate=7//エラー print(Area.tate)//3A_1やA_2からtateにアクセスすることはできません。
そのかわり、型に直接3を代入することでtateを10から3に変更できます。
スタティックプロパティにはイニシャライザみたいな初期化するメソッドがないため宣言文のなかで初期化する必要があります。
- 投稿日:2021-01-21T17:42:20+09:00
【Swift】Combine + APIKit で複数リクエストを直列で実行するサンプル (ついでに並列も)
Combine.frameworkを体験する取っ掛かりとして
APIKitのRequestにPublisherを生やして
複数の非同期通信を直列・並列で叩くサンプルを実装してみました。まだまだCombineニュービーですのでオススメの方法や
間違い等有りましたらご指摘頂けると幸いです??♂️環境
- Xcode 12.1
- APIKit 5.1.0
- SwiftUI
サンプルコード
https://github.com/SatoshiN303/RequestsWithCombine
プロジェクト構成
APIKitにPublisherを生やす実装方法については
Developers.IO さんの以下の記事が
非常にわかりやすかったのでほぼコピさせていただきました。
[iOS 13] SwiftUI + Combine + APIKitでインクリメンタルサーチ今回のサンプルコードでは複数の通信を叩くために
GitHub REST APIを使う形に変更しています。処理の流れ
主な登場人物は上記赤枠の3つです。
処理の流れは以下になります。
ContentView
のUISearchBar
の入力を受け取るSampleModel
でリクエストを作成して直列の通信を開始する- 最初に
/search/repositories
を叩く- 上記のレスポンスを利用して
/repos/{owner}/{repositry}
を叩く- 通知された結果を表示する
直列で叩く(Publisher)
最初に
/search/repositories
を叩き、レスポンスを利用して
/repos/{owner}/{repositry}
を叩いてみます。
正直最初のリクエストで欲しいデータは取得できますが学びということで。// `/search/repositories` のリクエスト作成 let searchRepositories = SearchRepositoriesRequest(query: searchText).publisher.eraseToAnyPublisher() self.requestCancellable = searchRepositories // レスポンスはSearchRepositoriesResponse型なのでitems.firstをとりあえず取得 .compactMap { $0.items.first } // 上記レスポンスを利用して GetRepositoyRequest を作成 .flatMap { (repository) -> AnyPublisher<GetRepositoyRequest.Response, Error> in /* 処理を繋げるとコンパイラが型がネストして解釈して複雑な型になるため * eraseToAnyPublisher()で型消去した * AnyPublisher<GetRepositoyRequest.Response, Error> を返す */ GetRepositoyRequest(owner: repository.owner.login, repositry: repository.name).publisher.eraseToAnyPublisher() } .receive(on: DispatchQueue.main) .sink { (completion) in switch completion { case .finished: debugPrint("request finished") case let .failure(error): debugPrint("request failed : \(error)") } } receiveValue: { [weak self] repository in // `/repos/{owner}/{repositry}` のレスポンスをContentViewに渡す self?.items = [repository] }補足リンク eraseToAnyPublisher()での型消去について
https://developer.apple.com/documentation/combine/publisher/3241548-erasetoanypublisher
Futureを使って直列で叩く
FutureはSubscribeされなくても
インスタンスが生成されたタイミングで即時実行されるので
とりあえずクロージャーで返すメソッドを作って処理を繋げてみました。
こちらも本来であれば再利用のためにAPIKitに生やすのが望ましいでしょうか。// MARK: - Futureを使って直列で叩く self.requestCancellable = fetchRepos(query: searchText) .compactMap { $0.items.first } .flatMap { [unowned self] repository in self.fetchRepository(owner: repository.owner.login, repo: repository.name) }.sink { (completion) in switch completion { case .finished: debugPrint("request finished") case let .failure(error): debugPrint("request failed : \(error)") } } receiveValue: { [weak self] repository in self?.items = [repository] } // MARK: - 各リクエストの生成処理 // ※Futureは即時実行される => インスタンスが生成されたタイミングでSubscribeをしなくても処理が走る private func fetchRepos(query: String) -> Future<SearchRepositoriesRequest.Response, Error> { return Future<SearchRepositoriesRequest.Response, Error> { promise in Session.send(SearchRepositoriesRequest(query: query), callbackQueue: .main) { (result) in switch result { case .success(let res): // 処理成功 promise(.success(res)) case .failure(let error): // 処理失敗 promise(.failure(error)) } } } } // ※Futureは即時実行される => インスタンスが生成されたタイミングでSubscribeをしなくても処理が走る private func fetchRepository(owner: String, repo: String) -> Future<GetRepositoyRequest.Response, Error> { return Future<GetRepositoyRequest.Response, Error> { promise in Session.send(GetRepositoyRequest(owner: owner, repositry: repo), callbackQueue: .main) { (result) in switch result { case .success(let res): // 処理成功 promise(.success(res)) case .failure(let error): // 処理失敗 promise(.failure(error)) } } } }ついでに並列でも叩いてみる
Publishers.Zipに渡せば並列で叩けるので
/search/repositories
と/search/users
を同じクエリで叩いてみます。let searchRepositories = SearchRepositoriesRequest(query: searchText).publisher.eraseToAnyPublisher() let searchUsers = SearchUsersRequest(query: searchText).publisher.eraseToAnyPublisher() self.requestCancellable = Publishers.Zip(searchRepositories, searchUsers) .receive(on: DispatchQueue.main) .sink { _ in } receiveValue: { (repos, users) in print(repos) print(users) }ついでに補足比較: RxSwiftで直列で叩く場合
RxSwiftの場合はSessionにObservableを生やしてflatMapで繋ぐ感じでしょうか。
extension APIKit.Session { func rx_sendRequest<T: Request>(request: T) -> Observable<T.Response> { return Observable.create { observer in let task = self.send(request) { result in switch result { case let .success(res): observer.on(.next(res)) observer.on(.completed) case let .failure(err): observer.onError(err) } } return Disposables.create { task?.cancel() } } } } // Observableで包んで返す func searchRepositories(request: SearchRepositoriesRequest) -> Observable<SearchRepositoriesRequest.Response> { return Session.rx_sendRequest(request: request) } func getRepository(request: GetRepositoryRequest) -> Observable<GetRepositoryRequest.Response> { return Session.rx_sendRequest(request: request) } // もろもろ省略 let observable = searchRepositories(request: searchRepositoriesRequest) .flatMap { [unowned self] repos -> Observable<GetRepositoryRequest.Response> in let item = repos.items.first //とりあえず成功してアンラップできてるものとみなす let getRepositoryRequest = getRepositoryRequest(owner: item.owner.login, repo: item.name) return self.getRepository(request: getRepositoryRequest) }.もろもろ続く参考にさせて頂きました。ありがとうございます。
- [iOS 13] SwiftUI + Combine + APIKitでインクリメンタルサーチ
- [iOS]APIKit + Combine + SwiftUIでGitHubリポジトリ検索
- 【Swift】Combineで一つのPublisherの出力結果を共有するメソッドやクラスの違い(share, multicast, Future)
- Combine で非同期処理を直列で実行する(flatMap)
- Modern Networking in Swift 5 with URLSession, Combine and Codable
- Creating a search bar for SwiftUI
- 投稿日:2021-01-21T14:57:43+09:00
Swift 遅延実行
- 投稿日:2021-01-21T10:46:46+09:00
結局ScrollViewのConstraintってどうつければいいの?
- UIScrollViewの上下左右をUIViewControllerのViewに合わせる
- ContentViewの上下左右をContent Layout Guideに合わせる
- ContentViewの横幅をFrame Layout Guideに合わせる(Equal Widths)
- ContentViewにオブジェクトを追加して高さを確定させる
名前の通り、Content Layout GuideはUIScrollViewの中身、Frame Layout GuideはUIScrollViewのフレーム領域を示します。
https://qiita.com/owen/items/2fab4a4482834b95e349つまづきやすかったこと
ContentView は自分で UIView を追加して作る
UIViewで囲むのがセオリーとは思うものの、ちょっとAppleさん不親切‥
Content Layout Guide の制約に注意
ContentView に Content Layout Guide の上下左右の制約をかけるとき、ちゃんと画面いっぱいに伸ばしてCtrlで制約を引っ張ってもなぜか+315や+812とズレがつく。見た目では気づきにくいが、これを0に修正しないとスクロールしないので注意。
高さを確定させよ
中のコンテンツの一番下の要素にContentView.bottomとのconstraintを付けないと高さ確定しないので怒られる。
よくある間違い?
ContentViewのbottomをFrame Layout Guideに合わせてしまう
正しい設定では中の要素が少ない時に下側がスカスカに空いてしまう場合がある。それを埋めようとContentViewのBottomをFrameに合わせると、FrameはiPhoneの上下左右の見えてる部分らしく、本来もっと下に伸びるべきContentViewのBottomがiPhoneの底で止まってスクロールしなくなる(伝われ)。
中身が少なく下が空いてしまうときは、ContentViewとScrollViewの背景色をあわせて空いてないように見せるしかないのかしら?
- 投稿日:2021-01-21T08:42:01+09:00
[SwiftUI] SceneDelegate を消して ApplicationDelegate だけにする
これはなに
iOS 13 環境で ReplayKit & UIScene の相性が悪いため、泣く泣く UISceneDelegate を消して UIApplicationDelegate だけにしたときのメモです。
If you must use RPSystemBroadcastPickerView then you should consider not using UIScene, and going back to UIApplicationDelegate.
やったこと
SceneDelegate をバッサリ削除
Info.plist から
UIApplicationSceneManifest
をバッサリ削除します。その後、
SceneDelegate.swift
も不要になるので削除します。(SceneDelegate でやっていたことがある場合は AppDelegate に移行する必要がある)AppDelegate から UIHostingController経由で SwiftUI の View を開く
AppDelegate.swift を次のように書き換えます。
import UIKit import SwiftUI @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window!.rootViewController = UIHostingController(rootView: ContentView) window!.makeKeyAndVisible() return true } }このとき、
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
のような UISceneSession Lifecycle が残っているとうまくアプリが動かないので注意です。
これで一応アプリが起動するようになりますが、動作を保証するものではありません。あくまで自己責任で!