- 投稿日:2020-09-21T21:56:51+09:00
iOSDC 2020 印象に残ったトークまとめ?
はじめに
iOSDC 2020に参加しました!
https://iosdc.jp/2020/
初のオンライン開催でしたが、会社の仲間と一緒にわいわい見るのは楽しかったです。
SwiftUI関連のトークが多くて、昨今のトレンドを反映してるなぁという印象を受けました。どれも勉強になるトークでしたが、印象に残ったトークを備忘録としてまとめておきたいと思います。
スライド画像多めでまとめているので、ざーっと流し見するも良し、気になるものがあればURLから飛んで詳細を見るも良しです(YouTubeのURLもアップされ次第追記予定です)Day1
iOSには無いmacOS独自機能をCatalystで実装する
概要
- スライド
- YouTube動画
- プロポーザル
内容
macOSはもともとAppKitだけだったが、一部にUIKitが利用できるようになった
macOSとiOSの違いも吸収されている
Xcodeでチェックを入れるだけでMacでも使えるようになる
macOS独自機能
* メニュー
* タッチバー
* ツールバー
タッチバーの一部でSwiftUIと相性が悪いところがあった
SwiftUIを利用したい場合はmacOSネイティブのアプリを開発したほうが良いかも?ひとこと所感
まだ全然macのこととか考えられていませんでしたが、このトークを聞いて、やるべきことのイメージが少し持てるようになりました!
そろそろCombine
概要
- スライド
- YouTube動画
- なし(見つけ次第追記します)
- プロポーザル
内容
Combineとは?
連続した多種類の非同期処理などを単一の方法で扱う宣言的なAPI
iOSの非同期処理は難しい
* タイミング次第で結果が変わってしまう
* どこで処理を実装しているのか探すのが一苦労
Combineでは、あらゆる処理をPublisherにすることで、非同期をスマートに扱うことができる
他のフレームワークとの連携も容易になる
宣言的とは?
指示的:「冷蔵庫からペットボトルの水を取り出して、コップに注いで、飲みます」
宣言的:「受け取った水を飲みます」ひとこと所感
Combineってなんぞや?という状態でしたが、RxSwiftと概念的・用語的に似ている部分があるとわかって少し安心しました。勉強せねば…
新規機能開発からモジュール分割を始めてみる
概要
- スライド
- YouTube動画
- なし(見つけ次第追記します)
- プロポーザル
内容
機能が増えるとコードベースも巨大化して開発効率の低下につながるので、モジュール分割を行った
既存アプリに取り入れたいけど、依存が深くてどこから切り出せばいいのか分からない
大規模リファクタが必要だけど、新規機能開発も止めるわけには…(わかりみ)
そこで、新規機能からモジュール分割する前提で設計することにした
独立した新規画面であったり、既存ドメイン仕様への依存が少ない場合はモジュール分割しやすい
メリットもあるけど、デメリットもある
コンフリクト回避のために、XcodeGenを先に導入すべきだった
抽象度が上がるので、実装がどこにあるのかわかりずらくなり、新しく入ったメンバーが困る場面もあったひとこと所感
新規機能からモジュール分割するの良さそう。いざやろうとしたら「どこから切り出していいか分からない」と絶対なると思うので、その際に再度見返したいトークでした!
効率よくUIKitからSwiftUIへ移行する
概要
- スライド
- YouTube動画
- プロポーザル
内容
SwiftUIを使ったほうがいい理由
* SwiftUIではより多くのことが、より少ないコードでできる
* 色んな機能がUIKitよりも楽に実装できる(アクセシビリティ、ダークモード/ライトモード、標準レイアウトなど)
* すぐにUIKitがなくなるわけではないが、いずれSwiftUIでやるしかない場合が出てくるかもしれないSwiftUIへの移行を阻むもの(待ったほうがいい理由)
* UIKitほど歴史がないのでバグがあったり、OSによって違いがあったりする
* UIKitと混ぜて使うのは苦労する
SwiftUIへの移行をどこから始めたらいいか?
* SwiftUIからObjective-Cが直接使えない(逆もしかり)ので、早期にSwift化しておく
* 移行が重なると面倒なので、Swiftの最新バージョン、機能を使っておく
事前にUIKitをモダンな使い方にしておくのも大事。
* AutoLayoutを使う
* SefeAreaを使う
* 巨大なStoryboardにせず、再利用しやすくするためコンポーネントに分ける(部分的にSwiftUIに移行しやすくなる)
* 宣言的なUIKit APIを使う(StackView、Compositional Layoutsなど)
* 本番のアプリにいきなりSwiftUIを導入するのではなく、同じ分野のアプリを小さい規模でプロトタイプとして作ったほうが、メリットデメリットがわかりやすい
* その中で、SwiftUIに向いてない画面が特定できるので、必要に応じてデザインの変更を検討する
* 既存のアプリ内で、iOS13限定の機能をSwiftUIで作るのもおすすめ
SwiftUIに向いているアーキテクチャはこの3つ
* Redux
* TCA
* MVVM
Tips
* 1つの画面の中でSwiftUIとUIKitを混ぜすぎないこと
* TableViewなどシンプルな画面から移行すること
* 急ぎすぎないこと
* SwiftUIは深いところまでCombineに依存しているので、SwiftUIを正しく使うにはCombineの知識も必要になるひとこと所感
すぐに使える知見がたくさん詰まったトークでした!いずれSwiftUIを本番に導入する日が来るかも?なので、計画的に準備していきたいと思いました。
Day2
Xcode Preview でUIKitベースのアプリ開発を効率化する
概要
- スライド
- YouTube動画
- なし(見つけ次第追記します)
- プロポーザル
内容
Xcode PreviewsはXcode11で導入されたプレビュー機能
SwiftUIでの実装ではXcode Previewsが使えるので、リアルタイムにレイアウトを確認することができる
UIKitベースの実装ではコード修正のたびにSumilatorを起動する必要があって不便…
現状多くのアプリがまだUIKitベースなので、そこにXcode Previewsを組み込んだ話
外部から状態を注入できるような、「PreviewableなView」にしておけると良い
メルペイでの活用例
短いテキストの場合、長いテキストの場合のレイアウト崩れがないか、一度にチェックできる
iOS13未満もサポートしている場合はどうしたらいいのかというと、2つのやり方がある
* マクロを使ってPreviewコードをビルドから除外する
* Preview専用のターゲットを追加する
Xcode Previewsを導入して既存アプリをSwiftUIの開発スタイルに寄せておくと、SwiftUIへの移行がよりスムーズになるかも?ひとこと所感
いろんな状態のViewを一度に表示・確認できるのはめちゃくちゃ便利だなと思いました!毎回Simulator起動するのめんどうなので、ぜひ導入したい…
XCUITestのつらさを乗り越えて、iOSアプリにUITestを導入する
概要
- スライド
- YouTube動画
- なし(見つけ次第追記します)
- プロポーザル
内容
もともと手動テストのみだったが、QAの工数を減らす手段の一つとしてUITestを導入
新機能と既存機能、どっちを対象にするか?という話があるが、メルペイでは既存機能に対象を絞った
UITestは繰り返し実施されるテストと、安定稼働している機能に対して行うと効率が良い
実際のコードはこんな感じ
テスト対象アプリを定義 → UI要素検索 → UI要素取得 → UI操作 → UI検索 → 表示チェック
UITestを書いていると、重複したコードがあることに気づく
まとめたい
Page Object Patternというデザインパターンが有効
PageObjectable プロトタイプを定義して、重複するコードをまとめる
メルペイでは夜間実行したり、GitHub ActionsのLabelトリガーを使ったりしているひとこと所感
苦難に立ち向かった知見が詰まっていて、これからUITestを始める人にとってぴったりのトーク内容でした。具体的なコードがあったのでとてもわかりすかったです。
Webとネイティブアプリの付き合い方を改めて考える
概要
- スライド
- YouTube動画
- なし(見つけ次第追記します)
- プロポーザル
内容
Webとネイティブアプリの比較
* アプリ単体での起動・動作
* オフライン動作
* プッシュ通知
などはネイティブアプリにしかない機能
PWAとは?
ネイティブアプリっぽく動くwebアプリケーション
PWAも比較
インストールが可能なので、アプリ単体で立ち上げることができる
(ここから先はあくまで私見によるもの と伝えつつ)
ネイティブアプリと同じ体験をWebでも提供することは可能か?
プッシュ通知や最新のiOS技術のことを考えると難しいのでは
どういうときにWebを活用したら良いか?
* まずは広く機能を提供したいとき
* プッシュ通知がいらないとき
* 最新のOS機能がいらないとき
専門知識が必要だったり、iOS/Androidセットで作らないとユーザーが減ってしまうことなどがネイティブアプリのデメリット
Googleからの検索流入が見込める、両OSに対応できるのはWebのメリットひとこと所感
アプリエンジニアなのでネイティブを推したい気持ちはありつつ、Webのメリットも知った上で正しく技術選択をしていく必要がありますよね…。深く考えさせられるトークでした!
テストコードが増えるとバグは減るのだろうか? - 「0% → 60.3%」で見えた世界の話
概要
- スライド
- YouTube動画
- なし(見つけ次第追記します)
- プロポーザル
内容
仕様は決まった!と言っても、実は完全には決まっていないことも…(あるある)
仕様書のチーム内レビューは静的テストに分類され、動的・静的を併用することで大きな成果が得られる
UI,ロジック、APIなどそれぞれテストが書けていれば、不具合の原因特定も容易になる
モックが書けていれば、意図しないレスポンスが返ってきたケースなども再現できる
しかし、適切なテストを書くには、経験や慣れ、様々な知識が必要
長い目で見たら役に立つが、開発時点では間違いなく工数がかかる
それを許容できる開発環境が必要
現状の課題をよく見つめ、様々なテスト手法がある中で最適なテスト方法を選択すること
費用対効果があるかを考えるのがポイント
テストコードにバグがないなど複数の前提条件を満たすとき、バグは減らせるひとこと所感
△というのがリアルでいいですね。経験が浅いとテストコードのテストコードが必要になってきそうで、テスト難しいなって改めて思いました。
おわりに
(トークの内容が濃すぎて)気づいたら大作になってしまいました!
見たかったけど見れなかったトークのスライドをここに置いておきます。
YouTube動画が配信されたら見るぞ〜
- 投稿日:2020-09-21T19:00:40+09:00
iOSDC2020初参加して
iOSDC参加しました
今回自分はiOSDC初参加しまして、参加した記事を書くまでがiOSDCだということで
あまりまとまった内容はかけないですが、とりあえず感想だけでもと思い書いています。
iOSDCとは
iOSDCは、iOS周辺技術に関わるエンジニアが、技術的なノウハウを共有するためのイベントです。
2016年から開催されていて今年で4年目のようです。
今年は初のオンライン開催でした。
ノベルティの量
下世話な話ですがノベルティの量すごい!
スマホスタンドからマスクケースからお水まで。
色々とあってすごいなーと思いました。
参加しての感想
本題の参加しての感想ですが、
いや〜楽しかった!
自分はエンジニアとしてまだまだで、正直聴きながらだと分からずに
タイムシフトで見返してやっと分かる、みたいなものもたくさんありました。
ただ、これは自社の別の現場で使えそうだな〜とか
自分自身試してみたい技術やそれを使って作りたいものも発見というか気づきを得れてとても有意義なものでした!
あとは、スピーカーの皆さんのスライド作るの凝っていたり、真面目過ぎずにクスッとするような作りのものもあって
発表資料としての参考にもなりました。
本当にお疲れ様でした!
もちろん、このカンファレンスを開催してくださった方々もお疲れ様でした!
さいごに
まだまだ、みれていない視聴させていただいたのに、フィードバックできていないトークもありますので
そちらにフィードバックを返したり、発表資料をTwitter等で上げていただけているので資料まとめみたいな記事を
自分用に、備忘録的にまた上げたいなと思います。(トークで気になった記事なども)
勢いで書いているので拙い文章がさらに拙く拙く拙くネストしていっていますが
自分のiOSDC Japan 2020 はいったんこれで区切りとします。
- 投稿日:2020-09-21T18:46:42+09:00
辞書について
1.はじめに
前回配列について説明したので、これまた重要な要素である辞書について説明していこうと思います。前回の配列について解説した記事のURLは以下になります。
(URL:https://qiita.com/0901_yasyun/items/6cc4c0175082e9b63d31)2.辞書とは
辞書とは、配列と同様に複数のインスタンスを格納できるコレクションの1種です。Swiftの辞書はDictionary型ですが、配列と同様、構造体として実現されており、変数への代入などの動作によって、必要に応じて新しいインスタンスが作成されます。
1つの辞書には、キーと値の組を複数個格納でき、キーから値を検索して参照、格納を行うので、1つの辞書に含まれるキーはすべて異なります。
キーに対応する値は1つですが、異なるキーに同じ値が設定されていても問題はありません。
ただし、キーと値はそれぞれ同一の型で統一されている必要があります。var y = ["Swift":2014, "Objective-C":1983]この例ではString型をキーとし、Int型を値とするエントリを2個もった辞書インスタンスを生成しています。
型のみを宣言する場合は、次のように記述します。
var y : [String : Int]次のように記述すると、String型をキーとし、Int型を値とする辞書のインスタンスが代入されますが、格納されている要素は0個です。
var y = [String : Int]() // イニシャライザの呼び出し空の辞書を表すには[:]と記述します。変数を型宣言し、空の辞書を代入するように記述しても、先ほどと同様な初期化が行われます。
var y : [String : Int] = [:] // 型を指定した変数に空の辞書を代入なお、辞書型はパラメータ付き型指定で次のように記述することもできます。
var y : Dictionary<String, Int> // [String:Int]と同じ意味 var y : Dictionary<String, Int>() // イニシャライザの呼び出し3.辞書へのアクセス
すでに要素をもつ辞書型の定数や変数に対し、キーを指定して値を取り出すことができます。そのために、配列にアクセスするときと同じように[]を使い、[]の中にキーを指定します。ただし返り値はオプショナル型で、存在しないキーを指定するとnilが返ってきます。
if-let文を使用した次に例を見てください。var y = ["Swift":2014, "Objective-C":1983] if let v = d["Swift"] { print(v) } // 存在するキーなので"2014"と出力 if let v = d["Ruby"] { print(v) } // 存在しないキーなので出力なしこの例では辞書は[String:Int]型なので、返される値はInt?型です。
辞書に新しい値を追加するには、[]の中にキーを指定して値を代入します。
また、既に存在しているキーの要素を削除するには、そのキーを指定してnilを代入します。以下の例を見てください。var e = ["Ruby":1995] print(e) // ["Ruby": 1995]と出力 e["Java"] = 1995 e["Python"] = 1991 print(e) // ["Java": 1995, "Ruby": 1995, "Python": 1991]と出力 e["Java"] = nil print(e) // ["Ruby": 1995, "Python": 1991]と出力ここまでの例ではキーが文字列で、値が整数でしたが、他の型でももちろん大丈夫です。
しかし、Swiftの基本的なデータ型の中で辞書のキーとして利用できるものは、各種整数型、実数型、Bool型、文字列、文字などです。実数もキーにできますが、実数値には誤差がつきものなので注意が必要です。4.辞書の比較
辞書同士を比較する際には、演算子「==」と「!=」が使えます。互いに同じキーの集合を持ち、同じキーに等しい値が割り当てられている辞書を等しいと判断します。以下の例を確認してください。
var a = ["one":"I", "two":"II", "three":"III"] let b = ["two":"II", "one":"I"] a == b // false a["three"] = nil a == b // true5.辞書から要素を取り出す
辞書は付属型としてのキーを表すKey型、値の型を表すValue型を持ちます。
またキーだけからなるコレクション、値だけからなるコレクションを提供するプロパティがあり、それぞれの型はKeys型、Values型となっています。
辞書自体もコレクションなので、要素を表す型であるElement型、添字の型であるIndex型を持っています。Element型は次のタプルの別名となっています。(key: Key, value: Value)for-in文を使って、辞書から要素を1つずつ取り出すことができます。その際、取り出されるのが上記のタプルです。以下の例を確認してください。
var dic = [String: Int]() var n = 1 for ch in "ありがとう" { dic[String(ch)] = n; n += 1 } print(dic) for t in dic { // タプルで取り出す print("\(t.key)=\(t.value)", terminator:" ") } print() for (k, v) in dic { // キーと値を取り出す print("\(k)=\(v)", terminator:" ") } print()これを実行するとこのような結果が得られます。
["が": 3, "と": 4, "り": 2, "う": 5, "あ": 1] が=3 と=4 り=2 う=5 あ=1 が=3 と=4 り=2 う=5 あ=1この結果より、辞書の要素が取り出される順番は、実行するたびに異なるのが分かります。
この仕組みについて解説すると少し長くなってしまうので、ひとまず取り出される順番はランダムだということを覚えておいてください。
気になる方は、ハッシュ値などについて調べれば仕組みが分かると思います。6.おわりに
今回は辞書の基本的な仕組みと主な使い方について解説しました。
今回の記事ではプロパティやメソッドについて、詳しく説明することができなかったので、いつかまた説明する記事が書ければと思います。
読んでくれた方、ありがとうございました。
- 投稿日:2020-09-21T18:39:46+09:00
【Swift】機械学習(=ML)とAIとの違いを学んだのち、Core ML を実装してみる。
機械学習(= ML) とは?
機械学習は英語で、「
Machine Learning
」
簡単に言うと、以下を指します。
- 『AIが自律的に物事を学ぶための技術』
- 『機械に大量のデータ・パターン・ルールを学習させることにより、判別や予測をする技術』
ML は、意外と歴史のある AI 分野のひとつ
機械学習はAIという概念の中の、1つの分野です。
1959年、機械学習の「父」とされている Arthur Samuel は、
機械学習を以下のように定義しています。「明示的にプログラムしなくても学習する能力」を、コンピュータに与える研究分野。
“Field of study that gives computers the ability to learn without being explicitly programmed”
-- Arthur Samuel --「AI=機械学習」ではなく、
AI > 機械学習 > ディープラーニング
のイメージです。なぜ近年、「機械学習」が大きな話題となっているのか
必要性
これまで人間はデータを分析し、そのデータに基づいてシステムや手順を変更してきました。
しかし、世界のデータ量が増大し、管理できなくなってきており、
データから学習し、それに応じて適応できる自動システムが必要とされています。技術の進歩
- AI技術の進歩
- 大量データの出現
- コンピューター処理能力の向上
使用例
- アマゾンエコーは、機械を使用してトレーニングされた音声テキストと音声認識を使用します
- 疾患の早期発見のために、医学界でも使用されています
- 自動運転車は、機械学習に依存して自分自身を運転します
AI と ML と DL の違い
人工知能【Artificial Intelligence】
人間のような知能をもつアルゴリズム。
※ アルゴリズム ...
「何を」「どのような順番で」「何に対して行うのか」を記述したもの。機械学習【Machine Learning】
AIが自律的に物事を学ぶための技術。
ディープラーニング(= 深層学習)【Deep Learning】
多層のニューラルネットワークを活用し、物事の特徴を抽出する技術。
因みに...
機械学習が「人間が判断・調整する」のに対し、
ディープラーニングは「機械が自動的に行う」ことが特徴。ディープラーニングで、人間が見つけられない パターンやルールの発見、特徴量の設定が可能になり、
人の認識・判断では限界があった
画像認識・翻訳・自動運転
といった技術が飛躍的に上がった。機械学習は、3つに分けられる
機械学習の主な手法には、
「教師あり学習」
「教師なし学習」
「強化学習」
がある。教師あり学習 (= Supervised Learning)
正解データを元に、入力データの特徴やルールを学習します。
✅「過去のデータから、将来起こりそうな事象を予測すること」に使われます。
- 回帰: 連続する数値を予測する
- 分類: あるデータがどのクラスに属するかを予測する
例.
【回帰】 "天候"と"お弁当の販売個数" の関係を学習し、お弁当の販売個数を予測する、
不動産価値、商品価格、株価、会社業績 etc【分類】 果物をサイズ別に分ける、画像や音声を種類別に分ける、
電子メールがスパム(迷惑メール)かどうかを判定する etc教師なし学習 (= Unsupervised Learning)
正解データなしでデータの特徴やルールを学習します。
✅「データに潜む傾向を、見つけ出すため」に使われます。
- クラスタリング: データのグループ分け
- アソシエーション分析: データ間の関連を発見する
- 異常検知: 人による指導なく、正常なものと不正常なもの(異常)を検知する
例. 【クラスタリング】 FacebookやInstagramの「あなたの友達かも..?」機能
【アソシエーション分析】 紙おむつを購入する人はビールも購入するetc強化学習 (= Reinforcement Learning)
失敗や成功を繰り返させ、どの行動が最適か学習します。
✅ 成功に対して「報酬」を与えることで学習効率を上げる方法です。
ロボットの歩行制御
ロボットに「歩けた距離」を報酬として与えます。するとロボットは、
歩行距離を最大化するために、自らさまざまな歩き方を試行錯誤します。
そうすることで、歩行可能距離の長いアルゴリズムが構築されます。囲碁AIの「Alpha Go」
囲碁は手のパターンが膨大過ぎて、既存の最新のコンピュータでも、手を読み切ることは不可能です。
よって、強化学習により、勝ちまでの手を読み切る代わりに、「どの手を打てば勝ちに近づくか」を学習させます。
試合にて失敗や成功を繰り返すと、最適な行動のみを選択するようになります。こうして「Alpha Go」は強くなっていったのです。参考サイト
- 投稿日:2020-09-21T17:29:41+09:00
VSCodeでswiftの環境設定をする方法
はじめに
VSCodeでSwiftを使うための環境設定の方法を丁寧に紹介します。
競技プログラミングでSwiftを使いたい、見慣れたVSCodeで使いたい、Xcodeで標準入力の仕方がよくわからないといった方におすすめです。開発環境
OS: macOS Catalina Version 10.15.6
Visual Studio Code (VSCode): Version 1.49.1手順
- Xcodeのインストール
- VSCodeのインストール
- NodeとNPMのインストール
- SourceKit-LSP Extension for Visual Studio Codeのインストールとビルド
- SourceKit-LSPの設定
1. Xcodeのインストール
Xcodeがすでにインストールされている場合は飛ばして構いません。
Xcodeのインストール方法: https://techacademy.jp/magazine/14092. VSCodeのインストール
VSCodeがすでにインストールされている場合は飛ばして構いません。
VSCodeのインストール方法: https://qiita.com/watamura/items/51c70fbb848e5f956fd63. NodeとNPMのインストール
Homebrewを使ってnodeをインストールします。同時にnpmもインストールされます。
$ brew install nodeインストールされたことを確認するために以下のコマンドを実行してみてください。
$ npm --version 6.14.84. SourceKit-LSP Extension for Visual Studio Codeのインストールとビルド
コマンドラインからSourceKit-LSPをクローンします。
$ git clone https://github.com/apple/sourcekit-lsp.git次にインストールされたフォルダへ移動します。
$ cd sourcekit-lsp/Editors/vscode/拡張パックをビルドします。
$ npm run createDevPackageビルドしたものをインストールします。
$ code --install-extension out/sourcekit-lsp-vscode-dev.vsix5. SourceKit-LSPの設定
VSCodeでSwiftのファイルを実行しようとした際に以下のエラーが出た場合には、VSCodeの設定を変更します。
Couldn't start client SourceKit Language ServerCmd+Shift+Pでコマンドパレットを開いたあとに
基本設定: 設定(JSON)を開く (Preferences: Open Settings (JSON)"
を選択します。
そしたら、すでにあるJSONに以下の文章を追加しましょう。(書き換えではなく追加です。)"sourcekit-lsp.serverPath": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp"6. 確認
hello_world.swiftというファイルを作成しましょう。
作成したファイルに以下のコードを記入してみます。hello_world.swiftprint("Hello, world!")ターミナルで以下のように書くと実行することができます。
swift hello_world.swiftまたは、Control+Option+nでも実行することができます。
ちなみに実行結果はHello, world!です。
7. 型推論
今のままでは型推論がされない(と思う)ので、以下のように実行してください。ファイルをまず開きます。左下に
(HDの名前) > ユーザ > (あなたのユーザ名) > ...と表示されていると思います。その中から、
(あなたのユーザ名)
をクリックします。以下順番にファイルを開いていきます。
sourcekit-lsp
を開きます。
Editors
を開きます。
vscode
を開きます。
out
を開きます。
sourcekit-lsp-vscode-dev.vsix
というファイルが有ることを確認してください。
確認できたらこのページを開きっぱなしにしておきます。
VSCodeの拡張機能タブから右上の・・・
を選択し、VSIXからのインストール
をクリックします。
すると、インストールするファイルを選ぶ画面が表示されるので先程開いたsourcekit-lsp-vscode-dev.vsix
をドラッグ&ドロップします。
最後に、開いたファイルをインストールしましょう。
これで型推論ができるようになりました!8.追記(C言語やC++のコンパイルにsourcekit-lspを用いたくない人へ)
lsp-sourcekitのページには以下のように、Swiftだけでなく、C、C++、Objective-Cにも対応していると書かれています。
lsp-sourcekit is a client for SourceKit-lsp, a Swift/C/C++/Objective-C language server created by Apple.
そのため、今までC言語やC++で使っていたコンパイラでは動くのにlsp-sourcekitでは動かないということが発生する可能性があります。そんなときは、Swiftを使うファイルをC言語、C++を使うファイルとは分けた上で、Swiftを使うワークスペースだけでlsp-sourcekitの拡張機能を有効にしましょう。
① SourceKit-LSPを無効にする② 再読み込みを行う
③ 拡張機能→SourceKit-LSP→有効にする→有効にする(ワークスペース)
9. 参考文献
https://nshipster.com/vscode/
https://medium.com/swlh/ios-development-on-vscode-27be37293fe1
https://scior.hatenablog.com/entry/2019/10/27/21582710. 終わりに
私は、まだまだ初心者なのですが参考文献に上げた3つのページを参考にVSCodeに導入してみたらうまく行ったので、共有させていただきました。
間違っているところがあったらご指摘ください。また、詳しい人がいたらぜひ記事を書いてください。私は、日本語でまとまっている記事を見つけ出すことができず、苦労しました...
- 投稿日:2020-09-21T13:10:52+09:00
【入門】iOS アプリ開発 #9【ゲームの状態遷移とシーケンス動作】
はじめに
今回はゲームとしてプレイできるように、ゲームの開始からゲームオーバーなどの状態遷移や各シーケンスの動作を作成する。以下が完成イメージ。ソースコードは GitHub に公開しているので参照してほしい。
状態遷移に関する仕様書
スタートモードとして、ゲームを開始する時のシーケンスが詳細に定義されている。
プレイモードはゲームプレイ中の状態で、プレイヤーがミスするとそのままのエサの状態からスタートし、残りのパックマンがいなくなるとゲーム・オーバーとなる。
またプレイフィールドのエサを全て食べるとラウンド・クリアとなる。
状態遷移とシーケンス動作の設計
ゲームプレイ中での必要なシーケンスを考慮して、下記の状態遷移図を作成した。
スタートモードは Start, Ready, Go の3つの状態に分けてシーケンスを作成する。
プレイモードは、主に Updating, ReturnToUpdating の2つの状態からなり、
プレイヤーがミスした場合は、PlayerMiss→PlayerDisappeared→PlayerRestartの状態/シーケンスを実行し、残りのパックマンがいれば Ready状態へ、なくなれば GameOver状態となる。またプレイフィールドのエサを全て食べると、RoundClear→PrepareFlashMaze→FlashMazeの状態/シーケンスを実行しラウンドクリアとなり、Ready状態へ移って次のラウンドが開始される。
状態遷移のソースコード
CgSceneMaze の handleSequenceメソッドに、状態遷移とシーケンス処理を実装していく。
/// Maze scene class for play mode /// This class has some methods to draw a maze and starting messages. class CgSceneMaze: CgSceneFrame, ActorDeligate { var player: CgPlayer! var blinky: CgGhostBlinky! var pinky: CgGhostPinky! var inky: CgGhostInky! var clyde: CgGhostClyde! var ptsManager: CgScorePtsManager! var specialTarget: CgSpecialTarget! var ghosts = CgGhostManager() var counter_judgeGhostsWavyChase: Int = 0 convenience init(object: CgSceneFrame) { self.init(binding: object, context: object.context, sprite: object.sprite, background: object.background, sound: object.sound) player = CgPlayer(binding: self, deligateActor: self) blinky = CgGhostBlinky(binding: self, deligateActor: self) pinky = CgGhostPinky(binding: self, deligateActor: self) inky = CgGhostInky(binding: self, deligateActor: self) clyde = CgGhostClyde(binding: self, deligateActor: self) ptsManager = CgScorePtsManager(binding: self, deligateActor: self) specialTarget = CgSpecialTarget(binding: self, deligateActor: self) ghosts.append(blinky) ghosts.append(pinky) ghosts.append(inky) ghosts.append(clyde) } /// States of game model enum EnGameModelState: Int { case Init = 0 case Start, Ready, Go, Updating, ReturnToUpdating, RoundClear, PrepareFlashMaze, FlashMaze, PlayerMiss, PlayerDisappeared, PlayerRestart, GameOver } /// Handle sequence /// To override in a derived class. /// - Parameter sequence: Sequence number /// - Returns: If true, continue the sequence, if not, end the sequence. override func handleSequence(sequence: Int) -> Bool { guard let state: EnGameModelState = EnGameModelState(rawValue: sequence) else { return false } switch state { case .Init: sequenceInit() case .Start: sequenceStart() case .Ready: sequenceReady() case .Go: sequenceGo() case .Updating: sequenceUpdating() case .ReturnToUpdating: sequenceReturnToUpdating() case .RoundClear: sequenceRoundClear() case .PrepareFlashMaze: sequencePrepareFlashMaze() case .FlashMaze: sequenceFlashMaze() case .PlayerMiss: sequencePlayerMiss() case .PlayerDisappeared: seauencePlayerDisappeared() case .PlayerRestart: sequencePlayerRestart() default: // Stop and exit running sequence. return false } // Continue running sequence. return true } // ============================================================ // Execute sequence in each state. // ============================================================ func sequenceInit() { drawBackground() goToNextSequence() } func sequenceStart() { context.resetGame() context.resetRound() context.numberOfFeeds = drawMazeWithSettingValuesAndAttributes() printBlinking1Up() printPlayers(appearance: false) printStateMessage(.PlayerOneReady) sound.enableOutput(true) sound.playSE(.Beginning) goToNextSequence(.Ready, after: 2240) } func sequenceReady() { printStateMessage(.ClearPlayerOne) printPlayers(appearance: true) player.reset() ghosts.reset() specialTarget.reset() ptsManager.reset() goToNextSequence(.Go, after: 1880) } func sequenceGo() { printStateMessage(.ClearReady) drawPowerFeed(state: .Blinking) player.start() ghosts.start() // Reset counter for wavy attack of ghosts counter_judgeGhostsWavyChase = 0 goToNextSequence() } // 〜 以下、省略 〜 }Start, Ready, Go それぞれの状態に対応するシーケンスを sequenceStart(), sequenceReady(), sequenceGo()メソッドで実装する。
仕様書とメソッドを対応させると以下の通り。
- ”1UP”表示が点滅 → printBlinking1Up()
- 巣の上に”PLAYER ONE”表示、巣の下に”READY”表示 → printStateMessage(.PlayerOneReady)
- 設定パックマンの数だけ”●”がプレイフィールド外の左下に表示される → printPlayers(appearance: false)
- スタートミュージック → sound.playSE(.Beginning)
- ”PLAYER ONE”表示が赤モンスターに代わり → printStateMessage(.ClearPlayerOne), ghosts.reset()
- 設定パックマン(プレイフィールド外の左下)が1つ減る → printPlayers(appearance: true)
- パックマンがスタート位置に表示される → player.reset()
- ”READY!”表示が消えて、プレイモードに移る → printStateMessage(.ClearReady)
プレイモードの仕様書
プレイモードにおいては、モンスターの出現タイミングや波状攻撃、アカモンスターのスパートといった細かい仕様が定義されている。これらによってモンスターは単調な追いかけ動作にならず、またプレイヤーのゲーム進行に合わせて難易度が調整される仕組みになっている。
ラウンドに対して更にモンスター出現タイミングのレベル、波状攻撃のスピードレベル、スパートする残りエサ数が定義されているが、今回は固定のレベル「A」、スパートは「①イ」のみ実装していく。
プレイモードのソースコード
プレイモードの仕様は sequenceUpdating() に動作を実装していく。
func sequenceUpdating() { // Player checks to collide ghost. let collisionResult = ghosts.detectCollision(playerPosition: player.position) switch collisionResult { case .None: // When it's no eat time, ghost goes out one by one. if player.timer_playerNotToEat.isEventFired() { player.timer_playerNotToEat.restart() ghosts.setStateToGoOut(numberOfGhosts: 4, forcedOneGhost: true) } // Appearance Timing of Ghosts ghosts.setStateToGoOut(numberOfGhosts: context.getNumberOfGhostsForAppearace(), forcedOneGhost: false) // Wavy Attack of ghosts // - Do not count timer when Pac-Man has power. if !player.timer_playerWithPower.isCounting() { counter_judgeGhostsWavyChase += SYSTEM_FRAME_TIME } // Select either Scatter or Chase mode. let chaseMode = context.judgeGhostsWavyChase(time: counter_judgeGhostsWavyChase) if chaseMode { pinky.chase(playerPosition: player.position, playerDirection: player.direction.get()) inky.chase(playerPosition: player.position, blinkyPosition: blinky.position) clyde.chase(playerPosition: player.position) } else { pinky.setStateToScatter() inky.setStateToScatter() clyde.setStateToScatter() } // If Blinky becomes spurt or not. let blinkySpurt: Bool = context.judgeBlinkySpurt() && !ghosts.isGhostInNest() blinky.state.setSpurt(blinkySpurt) // Blinky doesn't become scatter mode when he spurts. if blinkySpurt || chaseMode { blinky.chase(playerPosition: player.position) } else { blinky.setStateToScatter() } // For debug ghosts.drawTargetPosition(show: true) case .PlayerEatsGhost: let pts = context.ghostPts ptsManager.start(kind: pts, position: ghosts.collisionPosition, interval: 1000) //ms context.ghostPts = pts.get2times() addScore(pts: pts.getScore()) player.stop() player.clear() specialTarget.enabled = false ghosts.stopWithoutEscaping() sound.playSE(.EatGhost) sound.stopBGM() // REMARKS: To change playBGM(.BgmEscaping) immediately. goToNextSequence(.ReturnToUpdating, after: 1000) case .PlayerMiss: goToNextSequence(.PlayerMiss) } playBGM() }はじめにプレイヤーとモンスターの衝突判定を ghosts.detectCollision(playerPosition: player.position) で行う。
衝突がなければ(.None)、ノーイートタイムによって、ゴーストが巣から出ている処理を行う。4匹の中から必ず1匹は外に出して、ノーイートタイムをリスタートさせる。
次は、食べたエサの数でゴーストを巣から出していく。すでに指定の数が出ていたら何もしない。ミスして新たにスタートするときは、ミスバイパスシーケンスを通す。
CgContextクラスの getNumberOfGhostsForAppearace()メソッドは以下の通り。
func getNumberOfGhostsForAppearace() -> Int { let numberOfGhosts: Int // Miss Bypass Sequence if playerMiss { // Level A if numberOfFeedsEatedByMiss < 7 { numberOfGhosts = 1 } else if numberOfFeedsEatedByMiss < 17 { numberOfGhosts = 2 } else if numberOfFeedsEatedByMiss < 32 { numberOfGhosts = 3 } else { playerMiss = false numberOfGhosts = getNumberOfGhostsForAppearace() } } else { // Level A if numberOfFeedsEated < 30 { numberOfGhosts = 2 } else if numberOfFeedsEated < 90 { numberOfGhosts = 3 } else { numberOfGhosts = 4 } } return numberOfGhosts }sequenceUpdating() メソッドの処理フローに戻る。
次の波状攻撃は、パターンスタートからカウントしている時間 counter_judgeGhostsWavyChase によって Scatter と Chase を切り替える。
ただしプレイヤーが逆転している時は、カウントをストップする。CgContextクラスの judgeGhostsWavyChase()メソッドは以下の通り。
func judgeGhostsWavyChase(time: Int) -> Bool { let mode: Bool // Level A if time < 7000 || (time >= 27000 && time < 34000) || (time >= 54000 && time < 59000) || (time >= 79000 && time < 84000) { mode = false } else { mode = true } return mode }sequenceUpdating() メソッドの最後の処理フローでは、アカモンスター(Blinky)のスパート処理を行う。残りエサ数に達した時かつ全モンスターが巣から出ている時(!ghosts.isGhostInNest())にスパートする。
残りは衝突判定で、パックマンがモンスターを噛み付いた時(.PlayerEatsGhost)
、逆に捕まった時(.PlayerMiss)の処理をそれぞれ実装する。まとめ
ようやくゲームとしてプレイができるようになってきた。現在のソースコードは約5000行。
次はラウンドによって変化する難易度レベルやスピードレベルの詳細を作り込んでいく。
- 投稿日:2020-09-21T12:59:17+09:00
iOS14 CoreGraphics 描画互換問題
- 投稿日:2020-09-21T05:30:38+09:00
CATechChallengeで動画を簡単にGIFにできる追加機能をつくってきた
はじめに
サイバーエージェントの3daysインターンに参加してきました。
せっかくなのでこちらで技術的な振り返りをしていきたいと思います!技術的なこと以外の振り返りはnoteにまとめるのでそちらをご覧ください。
noteの記事はあとで書いて頑張って足します。
CATechChallengeとは
今回参加したインターンはCATechChallenge(3daysiOS/Android向け開発型インターン)というもので、AbemaTVに架空の追加機能を実装するハッカソン形式のインターンです。
作成した機能
視聴中の動画を長押しすることでその部分のGIFが作成されツイートされる機能を実装しました(機能名募集中ですw)
使用した技術
言語: Swift
アーキテクチャ: MVC
ライブラリ: Regift, Swifter
を使用しました。アーキテクチャに関しては元々MVCのコードが用意されていたのでそのまま実装しました。
ライブラリはGIF生成のためにRegift, ツイートのためにSwifterを使用しました。苦戦した箇所
ライブラリがm3u8に対応してなかった
元々用意されていた動画の形式がm3u8だったのですがRegiftがmp4にしか対応していなかったため、危うくアイデア自体が終わるところでしたw
メンターさんがmp4の動画も用意してくださっていたので入れ替えることでこちらは無事解決しました。長押しした時の秒数の取得
これは完全に僕の勉強不足なのですが、AVFoundationを使ったことがなかったので長押し中の秒数の取得に苦戦しました。
AVFoundationではCMTimeという独自の時間表現をするため、currentTimeがCMTimeで表示されていました。
GIFを作成する際には通常の秒数を取得する必要があったので、親切な方記事をガン見しながらそれっぽく変換しました()GIFの容量がデカすぎてツイートできない
Twitter的に15MB以上のメディアはアップロードできないらしくGIFがツイートできなくて詰みました。
意図せぬ変更ではあるんですがGIFの画質を落とすことによってこの制限をクリアできたので画質を一番低いものにしました。Swifterからツイートするとユーザーが文章を足せない
これは使うまで気がつかなかったのですが、アプリからSwifterを使ってツイートすると一瞬safariに飛んでアプリにコールバックするため、ツイート時にユーザーが文章を足すことができません。
個人的に不便だなーと思ったので間に自作のAlertを挟むことでユーザーが文章を足せるようにしました。工夫したポイント
今回一番のアピールポイントは動画視聴という体験をほぼ中断せずに機能が完結する点です。
ツイートする際にアプリとTwitterを連携させる部分以外は動画を視聴しながら使用できる機能にしました。実際にツイートすると
結果
そして気になる結果は...
BestEngineer賞をいただくことができました!!!めちゃ嬉しかったですGIFのプレビュー機能や番組へのリンクなどまだまだやりたい機能もあったのでホントはあと1ヶ月ぐらい開発したかったですが、とても楽しめたので参加してよかったです!ありがとうございました。
- 投稿日:2020-09-21T05:30:38+09:00
CATechChallengeで動画を簡単にGIFにできる追加機能をつくった
はじめに
サイバーエージェントの3daysインターンに参加してきました。
せっかくなのでこちらで技術的な振り返りをしていきたいと思います!技術的なこと以外の振り返りはnoteにまとめるのでそちらをご覧ください。
https://note.com/lsk4f5/n/nd28c28cfc088
CATechChallengeとは
今回参加したインターンはCATechChallenge(3daysiOS/Android向け開発型インターン)というもので、AbemaTVに架空の追加機能を実装するハッカソン形式のインターンです。
作成した機能
視聴中の動画を長押しすることでその部分のGIFが作成されツイートされる機能を実装しました(機能名募集中ですw)
使用した技術
言語: Swift
アーキテクチャ: MVC
ライブラリ: Regift, Swifter
を使用しました。アーキテクチャに関しては元々MVCのコードが用意されていたのでそのまま実装しました。
ライブラリはGIF生成のためにRegift, ツイートのためにSwifterを使用しました。苦戦した箇所
ライブラリがm3u8に対応してなかった
元々用意されていた動画の形式がm3u8だったのですがRegiftがmp4にしか対応していなかったため、危うくアイデア自体が終わるところでしたw
メンターさんがmp4の動画も用意してくださっていたので入れ替えることでこちらは無事解決しました。長押しした時の秒数の取得
これは完全に僕の勉強不足なのですが、AVFoundationを使ったことがなかったので長押し中の秒数の取得に苦戦しました。
AVFoundationではCMTimeという独自の時間表現をするため、currentTimeがCMTimeで表示されていました。
GIFを作成する際には通常の秒数を取得する必要があったので、親切な方記事をガン見しながらそれっぽく変換しました()GIFの容量がデカすぎてツイートできない
Twitter的に15MB以上のメディアはアップロードできないらしくGIFがツイートできなくて詰みました。
意図せぬ変更ではあるんですがGIFの画質を落とすことによってこの制限をクリアできたので画質を一番低いものにしました。Swifterからツイートするとユーザーが文章を足せない
これは使うまで気がつかなかったのですが、アプリからSwifterを使ってツイートすると一瞬safariに飛んでアプリにコールバックするため、ツイート時にユーザーが文章を足すことができません。
個人的に不便だなーと思ったので間に自作のAlertを挟むことでユーザーが文章を足せるようにしました。工夫したポイント
今回一番のアピールポイントは動画視聴という体験をほぼ中断せずに機能が完結する点です。
ツイートする際にアプリとTwitterを連携させる部分以外は動画を視聴しながら使用できる機能にしました。実際にツイートすると
結果
そして気になる結果は...
BestEngineer賞をいただくことができました!!!めちゃ嬉しかったですGIFのプレビュー機能や番組へのリンクなどまだまだやりたい機能もあったのでホントはあと1ヶ月ぐらい開発したかったですが、とても楽しめたので参加してよかったです!ありがとうございました。