- 投稿日: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-21T21:09:57+09:00
Xcode Previews入門 〜コードを書かないで画面を作る〜
はじめに
SwiftUIを使うことのメリットとして真っ先に上がるプレビュー機能ですが、実際にプレビュー機能を使いこなしている人は少ないように感じています。(自分含めて)
そこで、WWDC2020のセッションから学んだプレビュー機能をを使いこなせるようにし爆速にアプリの構築ができるようPreviewの基本やショートカットを記事にします。
今回は、Previewの基本的な使い方をご紹介させていただきます。
環境: Xcode12, macOS Catalina ver 10.15.6
⌘:コマンド(Command)キー
⌥:オプション(Option)キー
Ctrl:コントロール(Control)キーPreviewsの基礎
SwiftUIのアプリを起動すると以下のような画面になっています。
プレビューの実行・再実行
⌘ + ⌥ + P
右上にある
Resume
をクリックする、もしくはショートカットキー⌘ + ⌥ + P
でプレビューを実行することができます。
これは頻繁に使い開発効率が上がるので是非覚えておくと良いと思います。
Viewが反映されなくなったらとりあえずエラーが出ていない限り⌘ + ⌥ + P
で再描画されます。ビルドが走りますので、しばらく待つと以下のようにPreviewが表示されます。
このままでも良いのですが、実際のViewにのみ注目したい場合は
previewLayout(_:)
を使うと良いです。PreviewProviderのContentViewに.previewLayout(.sizeThatFits)
としてあげることでViewのみのpreviewにすることができます。Previewの表示・非表示
Previewは表示・非表示を切り替えることができます。
以下の図の①の箇所をクリックした後、②のCanvas
をクリックすることで切り替えることができます。
もしくは⌘ + Shift + Enter
でも可能です。Library
ライブラリーにはSwiftUIのViewとModifierがあります。
ライブラリーを起動するにはXcodeの右上にある+
ボタンをクリックする。
もしくは⌘ + Shift + L
で表示させることができます。表示される
Library
はいくつか種類があり、SwiftUIのViewを使いたいならViews
を選択しましょう。
種類は左から、Views
,Modifiers
,Snippets
,Media
,Color
となっております。
状況に応じて使い分けましょう!これらのViewをPreviewの挿入したい箇所にドラッグ&ドロップすることでPreviewを最新の状態に保ちながら修正することができます。
このときドロップする箇所によって、自動的に
VStack
かHStack
にしてくれます。— ツヅキ (@tsuzuki817) September 21, 2020Previewをクリック
previewに表示されたTextの文字列をクリックすると、TextのViewを選択することができます。
Previewをダブルクリックするとエディタにフォーカスが移動し、テキストか画像かに関係なくViewの要素を簡単に変更できます。
修正された内容も即時反映されます。
Viewの複製
⌘ + D
Preview画面でViewをクリックしている状態で
⌘ + D
とすると選択したViewを複製することができます。画像の追加
PreviewとLibraryの機能を使うことで画像の追加がとても簡単に行えるようになります。
Assets.xcassets
ファイルにお好きな画像をFinder
などからドラッグ&ドロップで置いておきます。
⌘ + Shift + L
でライブラリを起動し、右から二番目のMedia
をクリックすると、既に追加されている画像(drink_tapioka_tea_man)が表示されています。この画像をドラッグ&ドロップで
Hello World!
とLGTM
の間におきます。そうすることで、以下のように表示されます。
SwiftUIでは実際のサイズの画像を表示するので、デカデカと表示されてしまいました。インスペクタの利用
⌘ + ⌥ + 0
以下の図の箇所をクリックする、もしくは
⌘ + ⌥ + 0
でインスペクタを開きます。Previewの画像をダブルクリックして選択したのち、一番右の
Attributes inspector
を選択します。
Add Modifier
をクリックすると現在の要素にあった推奨されるModifier
が表示されます。
Resizable
を選択します。随分と縦長になってしまったので、
asaspectRatiopect
を設定しましょう!
contentMode
をfit
にしておきます。サイズを調整していきます。すでに見えている
Frame
のSize
に適当なサイズを入れます。そろそろLGTMも文字が二つあるのは邪魔なので、Viewを選択した状態で
⌘ + Delete
で削除します。そしてTextの代わりに今までの要領で好きな画像を以下のように表示させてみましょう。
アクション
⌘ + クリック
選択したViewを様々なコンテナ(VStack, HStack, etc )に埋め込むことができるアクションを利用します。
対象のViewを⌘
を押したままクリックでアクションを呼び出すことができます。
HStack
に格納してみました!これを繰り返し表示させてみます。再び、
⌘
を押しながらクリックしてRepeat
をクリックします。コーヒーが5個に増殖させることができました!便利ですね!
テキストを目立たせる
タイトルとして
Hello, world!
を目立たせていきたいと思います。インスペクタを表示し、
Font
の項目をいじります。
Font
をtitle
,Weight
をbold
,Color
をpurple
にしてみました。
お好きな形にしてみましょう!選択した色が即時反映されます。Modifierを無効にする
Modifierをクリアするには、コントロールの隣にある円形のインジケーターをクリックします。
色を元に戻すことができました。
Previewキャンバス上でインスペクタを呼び出す
Ctrl + ⌥ + クリック
Ctrl + ⌥
押しながら対象のViewをクリックすることで、右側にインスペクタの領域を取られる事なくModifierの値を変更することが可能です。プレビューの複製
ワンクリックでプレビューの複製が行えます。
とても便利です。
Previewをダークモードにする
Inspect Preview
をクリックし、Color Scheme
をDark
にします。ダークモードになっていることが確認できます。
おわりに
XCodeのPreview機能の触りを実際に動かしながら解説しました。
次回は、自分で作ったカスタムViewやカスタムModifierを作る記事を書こうと思います。参考
Visually edit SwiftUI views - WWDC 2020 - Videos - Apple Developer
- 投稿日:2020-09-21T20:20:08+09:00
【iOS】プロビジョニングプロファイル更新手順
前書き
プロビジョニングプロファイルの有効期限(1年)が過ぎた場合は更新が必要になるので、更新手順のメモ書き?
証明書のダウンロード(すでに証明書をキーチェーンに登録している場合は不要な手順)
下記の記事の「④ 証明書(Certificate)の作成」手順通りに証明書を作成して登録しておく
https://qiita.com/Labi/items/3b71b8f5ef065904c1deプロビジョニングプロファイルの更新
Apple Developer Program へアクセス
https://developer.apple.com/jp/programs/ダウンロードしたプロビジョニングプロファイルをダブルクリックして、Xcodeに登録
以上
- 投稿日: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:31:01+09:00
iOSDC2020に参加しました
どうも、ねこきち(@nekokichi_yos2)です
日本最大のiOSカンファレンス、iOSDC、に初参加しました。
iOSDC2020では、ニコ生上で開催され、地方在住の僕にはとてもありがたかったです。
また、アーリーバード枠の参加特典も
- iOSDCのパンフレット
- 有名IT企業のステッカー
- 有名IT企業のパンフレット
と充実してて、ありがたかったです。
初めてiOSDCに参加して、色々と感じたことを書き連ねていきます。
新たな知見を得られた
iOSのカンファレンスなので
・Storyboardの活用法
・SwiftUIとUIKitについて
・iPadのマルチウィンドウ
iOSアプリ開発で利用できる技術を知ることができます。新しい技術を知ろうと思ったら、自分でググる必要がありますが、カンファレンスや勉強会ではスピーカーが発表してくれるので、ググる手間が省けます。
しかも、現役のiOSエンジニアの方々が最新の知見を共有してくれるので、書籍やネット記事よりも勉強になります。
また、スピーカーが丁寧に解説してくれるますから、気軽に新/未知の技術を学ぶことが可能です。
他にも、
・アーキテクチャ
・数学を用いたプログラミング
・CI/CDの知見、検証結果
・ビルド、テストの方法
などの様々なテーマが用意されており、Swift以外の言語を扱う方でも学べるセッションが用意されています。当日の発表内容は、スライドやYouTube動画で公開されるので、見逃しても、何度でも見返すことができます。
高度なスキルがなくても学べる
iOSDCのセッションを理解できるか不安でしたが、全く心配ありませんでした。
もちろん中には、
・普段は滅多に使用しない処理
・ソースコードが実行される背景
などの難しいセッションもありました。ですが、 iOSDCの開催中にセッション内容を手元で試さなくても、発表者らが丁寧に解説してくれるので、聞き流すだけでも大体は理解できます。
初心者やベテランの方まで幅広い開発者らが学べるセッションが多く、参加するだけでも得したと言えます。
エンジニアが何を意識して開発してるかがわかった
スピーカーは当然、発表内容を事前に検証して、どうなったかを知っています。
そして、スピーカーが検証した過程を解説するとき、どのようにソースコードを設計したのかを話してくれました。
例えば、
・ViewとModelを分ける
・Delegateでイベントを投げる
・関数をオーバーライドする
など。つまり、セッション内容だけでなく、セッション内容を実装/検証する際にソースコードで意識したことを学べたのです。
ViewControllerでViewの表示とデータのロジックを一緒に書く僕でしたが、どのスピーカーもViewとModelでViewの表示とデータのロジックを分けることを意識してるんだって知れました。
iOSDCに限らず、エンジニアの話を聴くと、エンジニアの考えを知ることができるので、積極的にカンファレンスに参加しようと思えました。
スピーカーになれる自信を得られた
独自で実装/発見した技術を紹介、の他に、既存技術をわかりやすく解説、するセッションも多数ありました。
スピーカーとして情報を発信することは、高いスキルがなければできないと思いがちです。
しかし、レベルの高い内容でなくても
・仕様、実装方法をわかりやすく解説
・10個のアプリを開発して得たこと
・iOSアプリで〜〜を実装してみた
の内容で全然構いません。なぜなら、
・とある技術に関するまとまった情報が欲しい
・新しいフレームワークの使い方が知りたい
・iOSアプリで〜〜は機能できるのか
など、様々なニーズがあるからです。事実、
・まだまだ情報が少ないSwiftUI
・よく使うStoryBoardの活用方法
のセッションは、とても興味深い内容でした。わかりやすくまとめられたセッションを視聴して、
(俺でもスピーカーとして登壇できるんじゃね?)
と思いました。人の時間は限られており、尚且つエンジニアは忙しいので、ググる余裕すらありません。
ですから、簡単な技術だろうと、スピーカーとして発信することは意義のあることなのです。
まとめ
Firebase同好会、関西のIT勉強会などに参加してきましたが、iOSDCは大規模なカンファレンスだけでなく、数あるセッションから興味あるセッションを選び、自分でスキルアップを目指せる良いイベントでした。
ネット記事や書籍と違い、リアルタイムで現役のエンジニアと繋がれて、情報を得るためのきっかけとして断然アリです。
まだセッション内容を完璧に理解できませんでしたが、iOSアプリ開発の世界を垣間見れて、もっとスキルアップしたいと思えるようになりました。
来年もまた参加します!
- 投稿日:2020-09-21T16:33:46+09:00
ジョブカンのGPS打刻を拒否する方法(iPhone)
先月、勤務先の会社に 『ジョブカン』 が導入されました。調べてみたところ、ジョブカンには GPS打刻機能 があることがわかりました。こういう個人情報が特定される機能はキライなので、拒否していこうと思います。また、この記事は削除される可能性があるので早めに読んで、共有してください。
結論
さっそくGPSを拒否っていきましょう。
お手元にあるiPhoneを急いで開いてください。ジョブカンはまだGPSの中にあります。
私のiPhoneは iOS14 なので、今回はiOS14の設定を書きます。この設定を維持したままジョブカンから勤怠打刻をすることで、iPhoneから位置情報を抜き取ることはできなくなりました。
結果、どこから打刻したのか、ジョブカンには特定できません。参照
https://support.apple.com/ja-jp/HT207092そもそもジョブカンとは
勤怠管理とシフト作成が同時に行える、クラウド型の業務支援システムです。 複数拠点の勤怠データをリアルタイムに確認・集計・抽出ができ、給与支払いまでの業務を簡素化、迅速化します。
ジョブカンの主なキモイ機能
参考
https://imitsu.jp/matome/attendance-management/8716491050376361GPS機能
勤怠打刻した時に、その時の位置情報がバレる
勤怠異常の自動検出
シフトから大幅に外れた時間に勤務していたりすると、バレる
役に立ったと感じたら LGTM を押してください。
ありがとうございました。
- 投稿日:2020-09-21T16:25:08+09:00
iOS14でReact Nativeアプリで画像が表示されない
パッチファイル作成
以下コマンドでルートディレクトリ配下に
patches/react-native+0.61.5.patch
を作成する。$ npx patch-package react-native npx: 150個のパッケージを3.464秒でインストールしました。 patch-package 6.2.2 patch-package: you have both yarn.lock and package-lock.json Defaulting to using npm You can override this setting by passing --use-yarn or deleting package-lock.json if you don't need it • Creating temporary folder • Installing react-native@0.61.5 with npm • Diffing your files with clean files ✔ Created file patches/react-native+0.61.5.patch以下コードを追加する。
diff --git a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m index 01aa75f..4ef8307 100644 --- a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m +++ b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m @@ -269,6 +269,8 @@ - (void)displayLayer:(CALayer *)layer if (_currentFrame) { layer.contentsScale = self.animatedImageScale; layer.contents = (__bridge id)_currentFrame.CGImage; + } else { + [super displayLayer:layer]; } }以下のコマンドを叩き、
node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m
に反映させる。267行目付近。$ patch -p1 -i patches/react-native+0.61.5.patch- (void)displayLayer:(CALayer *)layer { if (_currentFrame) { layer.contentsScale = self.animatedImageScale; layer.contents = (__bridge id)_currentFrame.CGImage; } else { [super displayLayer:layer]; // 記載されていることを確認 } }キャッシュクリアしてビルド
$ watchman watch-del-all $ rm -rf ios/build $ rm -rf node_modules $ rm -rf ~/Library/Developer/Xcode/DerivedData $ yarn $ cd pod; pod install参考
- 投稿日: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-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ヶ月ぐらい開発したかったですが、とても楽しめたので参加してよかったです!ありがとうございました。