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

The Composable Architecture(TCA)の紹介と少し使った所感

概要 The Composable Architectureという良さげなアーキテクチャがあると聞いて、iOSの簡単なアプリをSwiftUIで書いてみました。 作ったもの https://github.com/tonionagauzzi/SwiftUITCASample よくチュートリアルで作るTODOアプリです。 実は、Qiitaにほぼ同じことをされた先人さんがいました…(書く段階で知った。笑) オリジナル性は無いですが、社内勉強会向けの資料なので、ご容赦ください! The Composable Architectureの説明 全体図 出典:The Composable Architecture — Visualize Data Flows With a Diagram 各コンポーネントの説明をしていきます。 State Action Environment Reducer Store 先ほどの図でState、Action、Reducer、Effectを囲んでいるのがStoreです。 Effect State UIに表示する内容を定義します。 ContentView.swift // ToDo1個分のState。 struct ToDoState: Equatable, Identifiable { let id: UUID var description = "" var isCompleted = false } // アプリ全体のState。ToDoの配列。 struct AppState: Equatable { var todoStates: [ToDoState] = [] } たとえば1つの画面でこれらを更新すると、他の画面にも即反映されます。 Action タップやデータ受信などのイベントを定義します。 ContentView.swift // ToDo1個に対して発生するイベント。 enum ToDoAction: Equatable { case checkTapped case textChanged(String) case removed } // アプリ全体に対して発生するイベント。TODOのは何番目に何を送るかを指定。 enum AppAction: Equatable { case todo(index: Int, action: ToDoAction) case addButtonTapped } Stateもそうですが、Equatableに準拠することでテストが容易になります(後述)。 Environment 依存関係を外部から注入します。DIです。今回はあまり活用しませんでした。 ContentView.swift struct ToDoEnvironment { } struct AppEnvironment { var uuid: () -> UUID = UUID.init } DIとはなんぞやについては別記事を参照。Androidの記事ですが。 Reducer Actionを受けてStateを更新する役割です。 ContentView.swift let todoReducer = Reducer<ToDoState, ToDoAction, ToDoEnvironment> { state, action, environment in switch action { case .checkTapped: state.isCompleted.toggle() return .none case .textChanged(let text): state.description = text return .none case .removed: return .none } } let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine( todoReducer.forEach( state: \AppState.todoStates, action: /AppAction.todo(index:action:), environment: { _ in ToDoEnvironment() } ), Reducer { state, action, environment in switch action { case .todo(index: _, action: .checkTapped): state.todoStates = state.todoStates .enumerated() .sorted { $0.element.description.lowercased() < $1.element.description.lowercased() } .sorted { !$0.element.isCompleted && $1.element.isCompleted } .map (\.element) return .none case .todo(index: let index, action: .removed): state.todoStates.remove(at: index) return .none case .todo(index: let index, action: let action): return .none case .addButtonTapped: state.todoStates.insert( ToDoState(id: environment.uuid()), at: state.todoStates.count ) return .none } } ) .debug() ユーザー入力も処理結果も含めた全てのアクションがここへやって来ます。 Store(ViewStore) State、Action、そしてReducerの1セットをStoreと呼びます。Viewを構築する役割です。 ContentView.swift let todoStore: Store<ToDoState, ToDoAction> let appStore: Store<AppState, AppAction> Storeは、Stateの変更を全て監視してViewを再レンダリングします。 また、Viewから発生したActionをReducerに渡す役割もあります。 Effect さて、今回は単純なサンプルなので、Reducerでは全部return .noneしていました。 しかし、本来Reducerは必要に応じてEffectを使います。 こちらの例を使って説明します。 struct AppEnvironment { var mainQueue: AnySchedulerOf<DispatchQueue> var numberFact: (Int) -> Effect<String, ApiError> } ↑まず、EnvironmentのnumberFactで時間のかかる処理をクロージャとして注入します。 処理はUIと非同期で実行したいので、実行スレッドの情報をmainQueueで渡します。 let appView = AppView( store: Store( initialState: AppState(), reducer: appReducer, environment: AppEnvironment( mainQueue: .main, numberFact: { number in Effect(value: "\(number) is a good number Brent") } ) ) ) ↑これが渡し元です。アプリ全体のStore初期化時に、依存関係を注入しています。 case .numberFactButtonTapped: return environment.numberFact(state.count) .receive(on: environment.mainQueue) .catchToEffect() .map(AppAction.numberFactResponse) ↑そしてReducerでは、受け取った処理を行いつつ、非同期に監視します。何かしらreceiveしたら、Reducer自身に対して新たなActionを発行します。 [NOTE] ちなみに、returnの型はEffect<AppAction, Never>型となっています。 case let .numberFactResponse(.success(fact)): state.numberFactAlert = fact return .none case .numberFactResponse(.failure): state.numberFactAlert = "Could not load a number fact :(" return .none ↑Reducerは新たなActionを受け取ったら、stateを更新します。成功/失敗の2種類あります。 このようにして、副作用と呼ばれるものを外部に出さずReducer内で完結させられるのが、Reduxなどには無いThe Composable Architectureの特徴です。 TCAの強み State management Composition Side effects Testing Ergonomics 出典:What is the Composable Architecture? 一言で言えば、画面をまたいで状態を値型で共有でき、小機能単位でStoreを分割でき、Actionの副作用をReducer内部で扱うことができ、ビジネスロジックのテストが容易で、これらすべてを短く人間工学的なコードで書ける、といったところでしょう。 テストのしやすさ ToDoリストに削除機能を追加したコミットです。 テストに注目します。 SwiftUITCASampleTests.swift func testRemoveToDo() { let (uuid1, uuid2) = (UUID.init(), UUID.init()) let store = TestStore( initialState: AppState( todoStates: [ ToDoState( id: uuid1, description: "ToDo 1", isCompleted: false ), ToDoState( id: uuid2, description: "ToDo 2", isCompleted: true ) ] ), reducer: appReducer, environment: AppEnvironment() ) store.assert( .send(.todo(index: 0, action: .removed)) { expected in expected.todoStates = [ ToDoState( id: uuid2, description: "ToDo 2", isCompleted: true ) ] } ) } 初期initialStateと、送るactionとを指定し、実行後のexpectedが期待通りのStateであることを確認するテストですが、非常に読みやすく書けました。 また、サーバーやDBを使い分けるような大きなアプリになったとき、MockEnvironmentみたいなダミーをenvironmentに注入すれば、ビジネスロジックだけをテストすることも簡単です。 補足 Swift UIとCombineに依存しているため、iOS 13、macOS 10.15、Mac Catalyst 13、tvOS 13、および watchOS 6以上でなければ使えませんが、古いOS向けにReactiveSwiftとRxSwiftのForkも一応用意されています。 所感 クリーンアーキテクチャーの記事を書いたのがちょうど2年前です。ってか、The Clean ArchitectureもTCAですね笑 今回のTCAを使ってみて、機能追加のしやすさやモジュール分割の自由度など拡張性の高さを感じました。この勢いで、もう少し複雑なアプリも作ってみたいですね。 一方、現状はApple製の新しめOS向けに特化したアーキテクチャなので、X-Platform前提の時点で難しかったり、古いOSのサポートを切れない実案件では選定しにくいという課題はあります。 実際、大規模案件では今でもMVVMやクリーンなほうのTCAを扱うことが多いです。 が、新しいアーキテクチャがあちこちで使われ出してるなと感じたら、基礎だけでも素早く学ぶよう個人的に心掛けています。 なぜかというと、人の書いたコードを読む抵抗が減るからです。 昨今、1から何かを発明する開発スタイルは淘汰され、既にあるものを繋ぎ合わせてニーズを実現するのが、いろんな分野で主流になって来てると感じます(※諸説あり)。 人の書いたコードやAPI仕様書を見たとき、いちいちアレルギーを発症していたらやっていけないので、「これはこういう思想でこうなんだな」とすぐに見抜くために、自分の中にパターン化された前提知識をいくつも持っていたいと思います。 と、良くない方向にポエム化して来たので、オチをつけましょう。最小機能単位で切り出しと疎結合ができるコンポーザブルアーキテクチャは、まさにこの切り貼りの時代にうってつけな感じですね! 参考記事 ※もっとよく知りたい方は、これらの素晴らしい記事orセッションをおすすめします! GitHub @pointfreeco/swift-composable-architecture Swiftによるアプリ開発のためのThe Composable Architectureがすごく良いので紹介したい the Composable Architecture の始め方 Composable Architecture を利用した Todo アプリの紹介(Part 1) CA.swift #14 - モジュール分割した開発での知見とテーマ切り替えやUICatalogについて
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SwiftUI】Toggleスイッチを非活性にする方法

アプリ作成を進める中で2つToggleスイッチを使用する場面がありました。 どちらか片方が選択されている場合、もう片方は非活性にしたかったのですが、 調べても参考になる記事が出てこなかったのでメモとして残しておきます。 初学者の方に参考になればと思います。 実装方法 とてもシンプルで、buttonと同じくdisabledモディファイアを使用して実現できました。 ソースコード SwiftUI import SwiftUI struct ContentView: View { // トグルスイッチフラグ // デフォルトはoffにしたいためfalse @State private var toggleFlg1 = false @State private var toggleFlg2 = false var body: some View { HStack(spacing: 15) { Text("スイッチ1") .lineLimit(1) Text(toggleFlg1 ? "ON" : "OFF") .font(.custom("Times-Roman", size: 15)) Toggle("", isOn: $toggleFlg1) .disabled(toggleFlg2) .labelsHidden() Text("スイッチ2") .lineLimit(1) .font(.body) Text(toggleFlg2 ? "ON" : "OFF") .font(.custom("Times-Roman", size: 15)) Toggle("", isOn: $toggleFlg2) .disabled(toggleFlg1) .labelsHidden() .font(.body) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スイフト勉強_ データタイプ 深化 (列挙型)

この前,スイフトのコレクション型について調べた 今日は最後に列挙形について調べたい。 列挙型(enum) 列挙型は、関連する項目図をまとめて表現することができるタイプである。 他の値とは異なって定義された項目の値以外は追加及び修正ができない特徴がある。 したがって、制限された選択肢を与えたいか、定められた値以外はもらいたくないとき、予想された値が限られているときに使いやすい。 スイフトの列挙型は、各列挙型が固有のタイプと認められるため、誤って不具合が発生するのを防ぐことができる。 1.基本列挙型 列挙型はenum というキーワードで宣言が可能である。 enum School { case primary case elementary case middle case high } enum School { case primary, elementary, middle, high } var a : School = .high var b = School.middle a = .elementary Schoolという列挙型には、primary、elementary、middle、highという項目が存在する。 各項目は固有値であり、一行にすべて表現することもできる。 2.ウォン相場 各項目がそれ自体で値を持つが、ソース値を設定できる。 enum School: String{ case primary = "a" case elementary = "b" case middle = "c" case high = "d" } var a : School = .high print(a.rawValue) // d 一部だけ元値を支払うこともできる。 その場合、文字列は項目名の通りを原始値として持つことになり、整数の場合には最初の項目を基準に0から1ずつ増えていく値を持つことになる。 enum School: String{ case primary = "a" case elementary = "b" case middle case high = "d" } var a : School = .high print(a.rawValue) // d var b : School = .middle print(b.rawValue) // middle enum num: Int { case zero case one case ten = 10 } print(num.zero.rawValue) //0 let c = School(rawValue:a) //primary let d = School(rawValue: f) //nil 列挙型の原始値を介して列挙型の変数又は定数を生成することができる。 正しくない値を使って生成しようとするとnilを返すことになる。 3.関連値 スイフトの列挙型各項目が連関値を持つことになれば、既存プログラミング言語の公用体形態を浮かべることもできる。 項目が自分に関連した値を持たせることができるからだ。 各項目の横に小括弧でまとめて表現できるため、他の項目が連関値を持つからといって全ての項目が連関値を持つ必要はない。 enum Topping { case cheese, bacon, pepperoni } enum Dough { case original, thin } enum Main { case pasta(taste:String) case pizza(dough:Dough, topping:Topping) case chiken(saurce: Bool) case rice } var dinner = Main.pasta(taste: "クリーム") dinner = Main.pizza(dough: Dough.thin, topping: Topping.bacon) dinner = Main.chiken(saurce: true) dinner = Main.rice 4.項目巡回 列挙型のすべてのケースを確認したいとき、列挙型にCaseIterable プロトコルを相続すればよい。 そうすると、その後、列挙型にallcasesという名前のタイププロパティを通じて全てのケイトのコレクションを生成してくれる。 enum School: String, CaseIterable { case priamry = "a" case elementary = "b" case middle = "c" case high = "d" } let all = Shool.allCases print(all) //[School.primary, School.elementary, School.middle, School.high] これについては、後でさらに深く調べる必要があるようだ。 5) 循環列挙型 掲げる項目の連関値が、列挙型自身の値でありたいときに使用する。 これは後でまた調べることにする。 難しい···。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】@IBInspectableと@IBDesignable

はじめに 今回はIBDesignableとIBInspectableについて解説してみたいと思います! IBInspectable これを使うとストーリーボードのInterfaceBuilderで設定できるようになります。 前回のプロジェクトを使って解説します。 このように、placeHolderに対して@IBInspectableを使います。 @IBInspectable var placeHolder: String = "" { willSet { self.placeHolderLabel.text = newValue self.placeHolderLabel.sizeToFit() } } すると、ストーリーボードのInterfaceBuilderで変更可能になります。 ただし、コードでplaceHolderを指定した場合はそちらが優先されます。 final class ViewController: UIViewController { @IBOutlet private weak var textView: PlaceTextView! override func viewDidLoad() { super.viewDidLoad() textView.placeHolder = "カキクケコ" } } IBDesignable IBInspectableを指定し、リアルタイムで見た目の変更をストーリーボードで見れるようにしてくれます。 つまり、ビルドしなくても確認できるようにするためのものです。 ストーリーボードで適当にUIViewを配置してください。 そして、ViewControllerで以下のように書いてみます。 final class DesignableView: UIView { @IBInspectable var cornerRadius: CGFloat { get { return layer.cornerRadius } set { layer.cornerRadius = newValue } } } 先ほど配置したViewにこのDesignableViewを継承させます。 先ほどの@IBInspectableのおかげでストーリーボードから変更できるようになっているので、適当に値を変えます。 ただし、この時点では@IBDesignableをまだ使っていないのでリアルタイムでは変更されません。 @IBDesignableをつけてあげます。 @IBDesignable final class DesignableView: UIView { @IBInspectable var cornerRadius: CGFloat { get { return layer.cornerRadius } set { layer.cornerRadius = newValue } } } すると、このようにリアルタイムで変更が見られるようになると思います! おわりに @IBInspectableと@IBDesignableは便利ですね!積極的に使っていきたいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift クイズゲーム Spritekit で作る。

プログラミング初心者は、往々にしてクイズゲームを作る物だと思いますw 今回は、UIKitでは無く SpriteKitでクイズゲーム 手抜きですが・・ 4つの半透明nodeを作ります。 ここをタッチして何番を押したかを判定します。 それぞれ4つの半透明ノードは名前をつけてあります。 1〜4まで、タッチした名前から正解の文字と一致したかで 正解の判定をしています。 クイズの問題文、配列question2次元配列で作成 配列question[0]が問題文、question[1~4]が選択肢 question[5]に正解のインデックスとなっています。 ``` import SpriteKit class GameScene: SKScene { var question:[[String]] = [ ["今何時?", "1時","2時","3時","4時","1"], ["今何時?", "1時","2時","3時","4時","2"], ["今何時?", "1時","2時","3時","4時","3"], ["今何時?", "1時","2時","3時","4時","4"] ] let xv = CGFloat(UIScreen.main.bounds.size.width) let yv = CGFloat(UIScreen.main.bounds.size.height) var selected = false var quiznumber = 0 let labels:SKLabelNode = SKLabelNode(fontNamed:"AvenirNext-Heavy") override func didMove(to view: SKView) { labels.fontSize = 90 labels.text = "クイズ" labels.position = CGPoint(x:xv/2,y:yv/10*8) addChild(labels) for i in 1...4 { let node = SKSpriteNode(color: .white, size: CGSize(width:xv,height:yv/15)) node.alpha = 0.2 node.zPosition = 10 node.position = CGPoint(x: xv/2, y: (yv/10*8)-(CGFloat(i)*node.size.height*1.2)) node.name = "(i)" self.addChild(node) } quiz(array:question[quiznumber]) selected = true } //================タッチ処理======================= override func touchesEnded(_ touches:Set,with event: UIEvent?) { for touch: AnyObject in touches { let location = touch.location(in: self) let touchedNode = self.atPoint(location) if quiznumber == question.count-1 { quiznumber = 0 } if touchedNode.name != nil { if selected { selected = false judge(name: touchedNode.name!) } } } } func judge(name:String){ if name == question[quiznumber][5] { labels.text = "正解" }else{ labels.text = "不正解" } let myNode = self.childNode(withName: name)! let fadeout = SKAction.fadeOut(withDuration: 0.05) let fadein = SKAction.fadeIn(withDuration: 0.05) let blinking = SKAction.sequence([fadeout,fadein]) let count = SKAction.repeat(blinking,count: 5) let wait = SKAction.wait(forDuration: 0.8) let sequence = SKAction.sequence([count,wait]) myNode.run(sequence, completion: {() -> Void in myNode.alpha = 0.2 self.quiznumber += 1 self.quiz(array:self.question[self.quiznumber]) self.selected = true self.labels.text = "クイズ" }) } //■■■■■■■■■■■■■文字の表示■■■■■■■■■■■■■■■■ var offsetX: CGFloat = 0 var offsetY: CGFloat = yv/5*2 var delayTime: CGFloat = 0.02 var labelArray: Array = [] var problemlabel: Array = [] func quiz(array: [String]){ while labelArray.count > 0 { labelArray.last?.removeFromParent() labelArray.removeLast() offsetY = yv/5*2 } for (index, element) in array.enumerated() { switch index{ case 0: print("問題文") var ct = 0 for i in element { if ct % 11 == 0 { offsetX = xv/40*4 offsetY -= xv/13*1.5 } let label:SKLabelNode = SKLabelNode(fontNamed:"AvenirNext-Heavy") label.fontSize = xv/13 label.alpha = 10.0 label.zPosition = 20 label.text = "(i)" label.position = CGPoint(x:offsetX,y:offsetY) addChild(label) labelArray.append(label) let delay = SKAction.wait(forDuration: TimeInterval(delayTime)*Double(CGFloat(ct))) let fadein = SKAction.fadeAlpha(by:1.0,duration:0.5) let seq = SKAction.sequence([delay,fadein]) label.run(seq) offsetX += xv/13 ct += 1 } case 1...4: let label:SKLabelNode = SKLabelNode(fontNamed:"AvenirNext-Heavy") label.fontSize = xv/13 label.zPosition = 20 label.text = "(element)" let myNode = self.childNode(withName: "(index)")! label.position = CGPoint(x:xv/3,y:myNode.position.y-(xv/13)/2.8) addChild(label) labelArray.append(label) default: print("default") } } } } ```
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FontAwesome.swift備忘録

導入 公式を参照のこと アイコンの探し方 上の公式サイト検索欄から検索。 カーソルをかざした際に「Pro」と右上に表示されないアイコンが無料で使える。 クリックするとアイコンの詳細画面が表示されるのでそこからstyleが確認できる。 呼び出し方 探したアイコン名とstyle、あとは色とサイズを指定するだけ。 // インポート import FontAwesome_swift import UIKit // 例 youtubeのアイコン, brandsのスタイルが無料で使える let itemIcon = UIImage.fontAwesomeIcon(name: .youtube, style: .brands, textColor: .gray, size: CGSize(30, 30) 注意点 FontAwesomeのアイコンはProプランに加入しないと扱えないアイコンも多く存在する。 FontAwesome.swiftからデフォルトで呼び出せるのは無料アイコンのみ。(Pro使えるようにする方法もあり) 表示できないアイコンは?アイコンが表示される。 同名のアイコンでも指定したstyleが無料で使えないこともあるので注意。 改善案 アプリ内で独自に使いたいアイコンはFAImageクラスとか別のクラス作成してそこから使い回すと、 毎回どのスタイルが無料で使えるか確認しなくて済みそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftで簡単に最新の同期処理/非同期処理を学ぼう

概要 iOSエンジニアの採用の基準にAPIの呼び出しができるかという基準をよく聞きます。 APIの呼び出しができれば、非同期処理なのでクロージャの呼び出しや定義、非同期処理の動きを把握てきていることになります。 意図的に別スレッドを作成して、どのような動きになるか説明します。 同期処理の例(通常の関数呼び出しなどは同期処理になる) countUp変数に関数型の値(1〜3のカウントアップ処理)を格納します。 呼び出し元が同期されているので、countUp(1, count)が終わった後に、 countUp(2, count)が呼ばれるので順番が狂いません。これが同期処理です。 let countUp = { (method: Int, count: Int) -> Int in for i in 1...3 { print("method\(method) count=\(count + i)") } return count + 3 } var count = 0 // クロージャーの呼び出し count = countUp(1, count) // count == 0 count = countUp(2, count) // count == 3 count = countUp(3, count) // count == 6 /* 処理結果: method1 1 method1 2 method1 3 // method1が終了後にmethod2 method2 4 method2 5 method2 6 // method2が終了後にmethod3 method3 7 method3 8 method3 9 */ 非同期処理の例(OperationQueue) プログラムで重たい処理を対処する時、メインスレッドを使うと、 ボタン押下などUI対処で常に監視すべき処理も止まってしまいます。 そのような場合、スレッドを複数作成し平行処理させる必要が出てきます。 APIのコールバックも同様の理由で、API呼び出し中ネットワークや、サーバーの応答を待ち続けるためにメインスレッドを使うことはできません。 応答専用のスレッド使用する必要が出てきます。 平行処理(非同期処理)の課題1 平行処理のためcountUp(1, count)完了後に、countUp(2, count)が呼ばれることが、 保証されなくなります。 let countUp = { (thread: Int, count: Int) -> Int in for i in 1...3 { print("thread\(thread) \(count + i)") } return count + 3 } let queue = OperationQueue() // クロージャーをOperationQueueに渡す // 3つのスレッドで平行に処理を行う var count = 0 queue.addOperation({ count = countUp(1, count) // count == 0 }) queue.addOperation({ count = countUp(2, count) // count == 0 }) queue.addOperation({ count = countUp(3, count) // count == 0 }) /* 処理結果: thread1 1 // thread1が途中なのにthread2、thread3が呼ばれる。 thread3 1 thread2 1 thread1 2 thread1 3 // 求める演算に到達できない。 thread3 2 thread3 3 thread2 2 thread2 3 */ 平行処理(非同期処理)の同期と新たな課題 処理ブロック完了後に処理を行う必要が出てきます。 let countUp = { (thread: Int, count: Int) -> Int in for i in 0...2 { print("thread\(thread) \(count + i)") } return count + 3 } let queue = OperationQueue() var count = 1 queue.addOperation({ count = countUp(1, count) // count == 0 // 非同期完了時に次の非同期処理を行う。 queue.addOperation({ count = countUp(2, count) // count == 3 // 非同期完了時に次の非同期処理を行う。 queue.addOperation({ count = countUp(3, count) // count == 6 }) }) }) /* 処理結果: thread1 1 thread1 2 thread1 3 // thread1が終了後にthread2 thread2 4 thread2 5 thread2 6 // thread2が終了後にthread3 thread3 7 thread3 8 thread3 9 */ 平行処理(非同期処理)の課題2 別スレッドのため重たい処理でも対処できるようになりましたが、 今度は可読性が下がってしまいます。スレッドの繋がり全体の終端処理など、 入れるとさらに可読性が下がります。この問題を対処するために、 Swift5.5からJavascriptと同じAsync/awaitが導入されます。 Swift5.5からの平行処理(非同期処理)と同期 Async/awaitで記事を書きました。 言語レベルの標準機能でもあるため、 おそらく古いiOSにも適用できるのではと思っています。 そうなれば、すぐに使える一般的な技術になるかと思います。 私も業務で使っている書き方を全て変更したほうが絶対いいと思いました。 ここまで読んでくれてありがとうございます。よかったら、いいね、お願いします。 let countUp = { (thread: Int, count: Int) async -> Int in for i in 0...2 { print("thread\(thread) \(count + i)") } return count + 3 } let queue = OperationQueue() var count = 1 queue.addOperation { count = await countUp(1, count) // count == 0 count = await countUp(2, count) // count == 3 count = await countUp(3, count) // count == 6 } /* 処理結果: thread1 1 thread1 2 thread1 3 // thread1が終了後にthread2 thread2 4 thread2 5 thread2 6 // thread2が終了後にthread3 thread3 7 thread3 8 thread3 9 */
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift5.5から使用可能になるAsync/awaitにいて要点を抜粋してみた

今までの非同期処理 Swiftで簡単に最新の同期処理/非同期処理を学ぼうでまとめました。 (いいね、がなくてモチベーションがやばいです。笑) Async/awaitとは何か WWDC2021の0296-async-await.mdをまとめてみました。 Async/awaitは、非同期を関数毎に実行して、関数の呼び出し元は同期を維持する仕組みです。 APIの呼び出しなど非同期が完了後に、別の非同期の呼び出しを行う場合、 非同期ブロックをネストさせることになりますよね。 冒頭では、いかにソースが冗長になるかの説明から始まりますが、書いた人頭良すぎるので割愛します。 非同期関数(Asynchronous functions) 新しい予約語にasyncとawaitが追加されます。 非同期関数(async形式の関数)を、処理待ち(await)で呼び出します。 非同期関数の定義と呼び出し // ① 非同期関数の定義は引数とthrowsの間にasyncを付けることで定義する。 // tips1 throwは必須ではない。 func loadWebResource(_ path: String) async throws -> Resource func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image func dewarpAndCleanupImage(_ i : Image) async throws -> Image // ③ 非同期関数の呼び出しは、そのスレッドを止める必要があるので、 // 止まっても良いスレッドか、呼び出し元の関数も非同期メソッドである、 // 必要があると推測する。 func processImageData() async throws -> Image { // ② 非同期関数の呼び出しはtryと関数名の間にawaitを付ける。 // tips2 関数が非同期で呼び出され、呼び出しが終わるまで、 // 呼び出し元の関数はその行で処理を待つ。アウェイト式という。 // tips3 関数が終了すると呼び出し行で、関数を処理したはスレッド放棄される。 // 放棄されるポイントをサスペンションポイント(Suspension points)という。 let dataResource = try await loadWebResource("dataprofile.txt") let imageResource = try await loadWebResource("imagedata.dat") let imageTmp = try await decodeImage(dataResource, imageResource) let imageResult = try await dewarpAndCleanupImage(imageTmp) return imageResult } 非同期関数型(Asynchronous function types) 関数型は既存のthrowsと同様に、asyncを内部的に持ちます。 内部的な型変換が起こるのでthrowsと同様に注意が必要です。 非同期関数型の暗黙的な型変換 struct FunctionTypes { // ① 同期型かつ例外を投げない var syncNonThrowing: () -> Void // ② 同期型かつ例外を投げる var syncThrowing: () throws -> Void // ③ 非同期型かつ例外を投げない var asyncNonThrowing: () async -> Void // ④ 非同期型かつ例外を投げる var asyncThrowing: () async throws -> Void mutating func demonstrateConversions() { // tips1 暗黙変換でasync、throwsを付与できる。 // ③ << ① 暗黙変換でasyncを付与 asyncNonThrowing = syncNonThrowing // ④ << ② 暗黙変換でasyncを付与 asyncThrowing = syncThrowing // ② << ① 暗黙変換でthrowsを付与 syncThrowing = syncNonThrowing // ④ << ③ 暗黙変換でthrowsを付与 asyncThrowing = asyncNonThrowing // tips2 暗黙変換でasync、throwsは削除できない。 syncNonThrowing = asyncNonThrowing // error syncThrowing = asyncThrowing // error syncNonThrowing = syncThrowing // error asyncNonThrowing = syncThrowing // error } } アウェイト式(Await expressions) 非同期処理の関数結果を次の非同期に渡すことができます。 アウェイト式のメリットと注意点 func redirectURL(for url: URL) async -> URL { ... } func dataTask(with: URL) async throws -> (Data, URLResponse) { ... } // tips1 非同期が完了後に次の行へ let newURL = await server.redirectURL(for: url) let (data, response) = try await session.dataTask(with: newURL) // tips2 サスペンションポイントが上のように、 // 複数あると呼び出し元スレッドが止まることが多くなる。 // 1行で書いて潜在的なサスペンションポイントを減らすことができる。 let (data, response) = try await session.dataTask(with: server.redirectURL(for: url)) オートクロージャ(Autoclosures) 既存のオートクロージャについての呼び出し注意点です。 オートクロージャの呼び出し func computeArgumentLater<T>(_ fn: @escaping @autoclosure () async -> T) { } // tips サスペンションポイントがオートクロージャの呼び出し時なので、 // この呼び出しはできない。 computeArgumentLater(await getIntSlowly()) // オートクロージャの呼び出しも非同期にする必要がある。 await computeArgumentLater(getIntSlowly()) プロトコルの適合性(Protocol conformance) プロトコルではasync型の関数に通常の関数も含みます。 プロトコルの使用例 protocol Asynchronous { func f() async } protocol Synchronous { func g() } struct S1: Asynchronous { func f() async { } // 型が一致する場合OK } struct S2: Asynchronous { func f() { } // tips1 非同期関数の定義に通常の関数を含むのでOK } struct S3: Synchronous { func g() { } } struct S4: Synchronous { func g() async { } // tips2 同期関数の定義に非同期関数を含まないのでError }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像付いてたり付いてなかったりするUITableViewCellは UIStackViewでサクッと作れる

画像掲示板で、レスに画像がついてるときもあれば無いときもある、というようなパターンでのセルです 従来はImageCell, TextCellみたいに分けちゃったほうが早かったですが、StackView使えるならCellの定義は一個ですみますね 先に構成見せたほうが早いと思うので、こうです DetailCellの中にstackViewを置きます func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 内で、サムネ.isHiddenを出し分けるだけですね 注意点として画像なしセルの表示のあとに画像付きセルが表示されると、AutoLayoutのエラー解消ログがデバッグログに出ますね これはおそらくTableViewの高さ計算とサムネのありなしの高さ計算が競合しちゃってますね なのでサムネの高さ計算の優先度をちょっと下げると解消します 自分の備忘録的な記事なのであっさりですみません 質問あればコメントください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SwiftUI】Chartsを使って線グラフを表示する

はじめに 今回はSWiftUIを使って線グラフを表示していきたいと思います。(厳密にはSWiftUIでUIViewを使います) 使用するライブラリはChartsです。 1. 作った物 本記事のコードを用いることでこのようなグラフを表示することができます。 2.ライブラリをインポート CocoaPodからライブラリを導入します。 まずはプロジェクトディレクトリでpod init 続いてPodfileにpod 'Charts'を追記してpod install あとはプロジェクト内でimport Chartsとすれば使えるようになります。 3.SwiftUIでUIViewを使う Chartsライブラリですが、SwiftUIには対応しておらず、従来のUIKitのライブラリとなります。 そのため使うためにはSwiftUIでラップする必要があります。 SwiftUIしか触ったことがない私はこの時点で???でしたが、 調べてみて何となくわかってきました。 UIViewRepresentableプロトコル UIKitのクラスをラップするにはUIViewRepresentableプロトコルを使用します。 ラッパークラス(構造体?)のLineChartを作成します。(ここの名前は自由です) そしてLineChartViewというのが今回ラップ対象のクラスです。 struct LineChart : UIViewRepresentable { func makeUIView(context: Context) -> LineChartView { // ここでViewが作られるイメージ } func updateUIView(_ uiView: LineChartView, context: Context) { // 値が更新された時に呼び出される処理 } } また、UIViewRepresentableプロトコルはmakeUIView関数とupdateUIView関数の実装が必須になります。 makeUIViewで表示するViewが作られるイメージ updateUIViewでは値が更新された時に呼び出される処理を書きます。例えば数値を追加した時にそれを動的にグラフに反映する時に使用します。今回はそこまで実装しません。 実装 こちらが全文となります。makeUIViewの箇所がメインとなります。 dataSetをdataにいれてdataをチャートに反映する。 というだけですのでそこまで複雑な内容ではありません。 import SwiftUI import Charts struct LineChart : UIViewRepresentable { func updateUIView(_ uiView: LineChartView, context: Context) { // update } func makeUIView(context: Context) -> LineChartView { // グラフに表示する要素 let lineChartEntry : [ChartDataEntry] = [ ChartDataEntry(x: 0, y: 0), ChartDataEntry(x: 1, y: 1), ChartDataEntry(x: 2, y: 2), ChartDataEntry(x: 3, y: 3), ChartDataEntry(x: 4, y: 4), ChartDataEntry(x: 5, y: 5), ] let chart = LineChartView() let data = LineChartData() let dataSet = LineChartDataSet(entries: lineChartEntry) // データセットを作ってチャートに反映 data.addDataSet(dataSet) chart.data = data return chart } } struct ContentView: View { var body: some View { LineChart() } } おわりに 無事SwiftUIでChartsを使うことができました! 次回は値を動的にグラフに反映する方法やグラフの見た目をカスタマイズする方法を投稿したいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む