今回は、iOS14から導入されたWidgetを題材として扱いたいと思います。
既に様々なところで Widget
の導入について説明されていますが、筆者が気になったのは、
『プッシュ通知の受信起因で Widget
を更新することができるかどうか 』
です。
簡単にサンプルを作成して試してみた結果を備忘録として載せておきたいと思います。
まず、 Widget
の更新方法ですが、これは大きく分けて2種類あります。
Widget
自体に定義した更新タイミングで更新する前者の『 Widget
自体に定義した更新タイミングで更新する』は、
Widget
ソース内の getTimeline
メソッドが該当します。
例えば、1時間ごとに更新したいと思った場合には、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
のように定義することで実現できます。
続いて後者の『アプリなどから任意のタイミングで更新する』は、
任意のタイミングで、
1
|
|
を呼び出してやれば良いだけです。
では、プッシュ通知の受信起因で Widget
を更新するためには、どうすれば良いのでしょうか?
答えは、 NotificationService Extension
を利用する方法です。
NotificationService Extension
はプッシュ通知受信時に条件次第で通知内容を変更したい場合などに利用されます。
NotificationService Extension
は下記のようにプッシュ通知の受信を検知してロジックを組み込むことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
この didReceive(_:withContentHandler:)
内で WidgetCenter.shared.reloadAllTimelines()
を実行してやれば Widget
が更新できます。
今回の検証では、node-apnを利用しました。
node-apn
を使えば、プッシュ通知に必要な AuthKey
を用意し、多少の実装をするだけ簡単にプッシュ通知を検証できるのでオススメです。
実装は、プッシュ通知をnode-apnで送ってみよう!で詳しく説明していますので、ご参照ください。
※ NotificationService Extension
を利用するので、ペイロードに "mutable-content": 1
が必要になることだけ注意してください。
1 2 3 4 5 6 |
|
さて如何でしたでしょうか?
Widget
はiOS14から導入され、ホーム画面に多大な影響を与える期待の新機能ですので、
ユーザに更なる利便性を与えるためにも積極的に導入していきたいですね。
といったところで本日はここまで。
]]>先日のWWDC2020で NearbyInteraction Framework
というものが発表されました。
これは複数端末間の距離や方角を計測するために利用できる Framework
だそうです。
今年はコロナの影響で、ソーシャルディスタンスという言葉が叫ばれるようになったこともあり、意識せざるを得ないような内容に感じられたため、
WWDC2020で行われた『Meet Nearby Interaction』セッション動画を元に勉強していきたいと思います。
iPhone11以降でU1チップを備えている端末に限られます。
現段階では、
の3端末に限られています。
※シミュレータが対応しているため、上記実機が手元になくても試すことができます。
まず、 NearbyInteraction Framework
で測定可能なものですが、
の2つになっています。
上記2つは NINearbyObject に格納された状態で取得します。
それぞれ
1 2 3 4 5 6 7 |
|
と定義されています。
ここからは詳細について見ていきましょう。
端末間の距離や方角を計測するとなると、互いの端末を検知し、ペアリングする必要がありそうな気がします。
これを可能にするために、 NearbyInteraction Framework
では NISession
オブジェクトを生成し、
その NISession
が提供する discoveryToken
を利用する必要があります。
下記のように discoveryToken
が NISession
内に定義されています。
1 2 3 4 5 |
|
因みに、この discoveryToken
は
session
と同等の有効期限が存在するsession
の無効にしたり、アプリを終了すると discoveryToken
も無効になるという性質があります。
この discoveryToken
を端末間で共有し合うことで、距離や方角を計測する端末を認識することができるわけです。
では、 discoveryToken
の端末間での共有方法は何があるかというと、
WWDC2020のセッションでは、 Multiple Connectivity Framework
が紹介されていました。
また、 discoveryToken
は NIDiscoveryToken
型であり、
1 2 3 |
|
のように NSSecureCoding
に準拠しているため、下記のように簡単にエンコードすることができます。
1 2 3 4 5 |
|
Multiple Connectivity Framework
を利用する場合は、下記のように送信します。
1 2 3 4 5 6 7 |
|
逆に他端末から送信されたエンコードされたデータを受信したら、
1 2 3 |
|
のようにデコードし、 discoveryToken
を取り出します。
これで互いに計測対象端末の discoveryToken
が共有できました。
あとは、受信した discoveryToken
を元に生成した config
を利用してセッションを開始します。
1 2 |
|
対象端末との距離や方角の値は NISessionDelegate
を通じて取得します。
下記の didUpdate
メソッドにて、最新の NINearbyObject
情報が取得できます。
1 2 3 4 5 6 |
|
NISessionDelegate
には他にも幾つかメソッドが用意されています。
例えば、 didRemove
メソッドは、
1 2 3 4 5 6 7 |
|
ペアリングしたはずの計測対象端末から情報が取得できない場合に呼び出されます。
これが呼び出されるパターンは主に次の2つになります。
上記理由も、メソッド引数の reason
に格納されているため、識別することができます。
1 2 3 4 5 6 7 8 |
|
また、アプリの状態に関わるメソッドも用意されています。
1 2 3 4 5 6 7 8 9 10 |
|
sessionWasSuspended
はアプリがFG起動を終了し、サスペンド状態になった際に呼び出され、
sessionSuspensionEnded
はアプリが再びFG起動に戻し、サスペンド状態が終了した際に呼び出されます。
因みに、セッションは一度停止すると自動的に再開はされないため、 sessionSuspensionEnded
内で再度セッションを開始する必要があります。
1 2 3 4 5 |
|
最後の Delegate
メソッドとして didInvalidateWith
が用意されています。
これはセッションが無効になった場合に呼び出されます。
1 2 3 4 5 6 |
|
セッションが無効になると、 run
メソッドを使っても再開することができないため、
セッションの生成からやり直す必要があります。
(セッションを生成し、 discoveryToken
を交換し、セッションを開始する一連の流れを頭からやり直します。 )
NearbyInteraction Framework
は非常に強力な計測ができますが、
その精度を高めるためには幾つかユーザに制約を課さざるを得ないようです。
その制約とは、
Portrait
の向きで端末を持っていることが上げられています。
以下、セッションの資料がわかりやすかったため、引用させて頂きます。
方角の測定可能フィールド上に両端末が存在していること
両端末ともにPortraitの向きで端末を持っていること
両端末間に壁や人などの障害物がないこと
さて、如何でしたでしょうか。
利用上の制約があることで万能ではないと感じる方もいるかもしれませんが、
cm単位の精度で端末間の距離や方角がわかるという機能は、
これからの新生活で大活躍する可能性も秘めているかもしれません。
個人的には、
何か世の中の役に立つようなサービスを考えるきっかけになったかなと思いました。
最後に、今回説明させて頂いた内容は、冒頭に書きました通りMeet Nearby Interaction動画で確認できますし、
Implementing Interactions Between Users in Close Proximitにてサンプルコードも公開されています。
より詳細を見る場合は当然ですが、上記を見ることをオススメいたします。
といったところで本日はここまで。
]]>最近は、iPhone端末も昔に比べて飛躍的にカメラの性能も向上し、
高品質な写真を撮ることができます。
一消費者としては喜ばしい限りなのですが、エンジニアとしては喜んでばかりもいられません。
というのも、画像をクラウド上で保存する機能があった場合に、
容量と品質を天秤にかけざるを得ないことも、まだまだあるからです。
そこで今回は、画像をJPEG圧縮することで、どの程度の品質で容量が変わるのか実験してみたいと思います。
利用するメソッドは下記になります。
1
|
|
早速、実験方法ですが、
jpegData
の引数である compressionQuality
に0.1刻みで0.0〜1.0の間を指定して、
結果容量を比較してみました。
まずは、区切り線の明瞭なイラスト画像を用いて実験をしてみました。
1.0から0.9が極端に下がっていることがわかります。
また、0.1と0.0とでは画像容量に差分がない結果となりました。
実際に、画像を比較してみると、
compressionQuality: 1.0 の画像
compressionQuality: 0.5 の画像
compressionQuality: 0.0 の画像
のようになります。
当然ですが、1.0の方が全体的に滑らかで、0.0はかなり品質が落ちていることがわかります。
※画像はイラストレインからお借りしました。
続いて写真で実験してみました。
イラストと同じく、1.0から0.9が極端に容量が下がっている一方で、
0.1から0.0は同じ容量となりました。
こちらも画像を比較してみると、
compressionQuality: 1.0 の画像
compressionQuality: 0.5 の画像
compressionQuality: 0.0 の画像
のようになります。 やはり0.0にまでなると品質劣化が肉眼でもわかりますね。
※画像は、ぱくたそからお借りしました。
JPEG圧縮のアルゴリズムは当然ながら、Swiftのメソッドを利用したからといって変わるわけではありません。
なので、Mac上で画像を書き出す際に品質を指定することで確認できます笑
JPEGのアルゴリズムに関しては、
などが参考になるかと思います。
以上の実験からわかる通り、圧縮具合を変えることで容量に大きな変化が生まれますし、
当然ですが、それに伴い品質も劣化していきます。
サービス上で許容となる品質レベルは異なるでしょうし、慎重な意思決定が必要となることでしょう。
個人的には 0.5
でも全然気にならなかったりしますが、許容範囲広すぎでしょうか。。。笑
と言ったところで本日はここまで。
]]>iOS13からダークモードがiPhoneにも追加され、ユーザニーズからアプリ側でもダークモード対応を必要とされる場面が増えてきました。
一方でiOS12以前のOSをサポートする必要がある場合がほとんどかと思います。
「iOS12ではダークモードが利用できないので、ダークモードが導入できないのでは?」
と思う方がいる可能性を踏まえて、
本日は、今更ではありますが、旧OSをサポートしながらダークモード対応するための Color
定義について見ていきたいと思います。
Xcode9より Color Asset
機能が追加されています。
エンジニアだけでなく、デザイナーさんがXcodeを触って色を確認する可能性も踏まえると、
Color Asset
を使わない手はないでしょう。
( Color Asset
が利用できる状況なら、恐らく最も手軽にダークモード対応が可能です。 )
Xcode11より、 Color Asset
でダークモード対応できる機能が追加されました。
方法は簡単で下図のように、右メニュー > Appearances
の設定を Any,Dark
に変更するだけです。
( Light
, Dark
以外にモードを設ける場合は Any,Light,Dark
を選択してください。 )
これでダークモードの対応は完了です。
下記のように、 Color Asset
で設定した名称で呼び出せば、端末のモードに従って色を設定できます。
1
|
|
iOS12以前の旧OSではダークモードがありませんので、
UIColor(named: "mainBackgroundColor")
を利用すると自動的に Any
が採用されます。
さて、 Color Asset
が利用できない場合は、どうすれば良いでしょうか。
この場合、ゴリゴリにコードで記述するしかないでしょう。
iOS13以上のアプリであれば、 UIColor
に、
1
|
|
というイニシャライザーが追加されているため、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
といった形で定義ができるでしょう。
これがiOS12以前も対応するとなると、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
のように条件分岐が必要になります。
さて、如何でしたでしょうか?
案外、簡単にダークモード対応ができることがわかります。
ただし、コードで定義する場合は、色定義ファイルが膨大になることが予想されるため、
もう少し工夫が必要になることでしょう。
と言ったところで本日はここまで。
参考URL:
]]>XcodeでiOSアプリを開発する際に、ほとんどの開発者はドキュメントコメントを書く機会があると思います。
Xcodeでは、メニューバーから
Editor > Structure > Add Documentation
を選択すると、最適な形式のドキュメントコメントを差し込んでくれます。
( または、command + option + /
でショートカットできます。 )
実はこの機能が困ったことにXcode11当初でバグがありました。
例えば、下記のようなメソッドがある場合、
1 2 3 |
|
ドキュメントコメントは、
1 2 3 4 5 6 7 8 9 |
|
のようになります。
Xccode10.xまでは上記期待値の元、ドキュメントコメントを追加していたのですが、
これがXcode11にアップデートしてみると…
1 2 3 4 5 6 7 8 |
|
のように戻り値のあるメソッドだったとしても Returns
を挿入してくれなくなってしまっていました。
また、
1 2 3 4 |
|
という protocol
メソッドを定義した場合は逆に、
1 2 3 4 5 6 7 8 9 |
|
のように不必要な Returns
が挿入されてしまっていました。
上記、Xcode11.4にて、やっと正しく挿入されるように修正されたようです。
因みに、筆者としては、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
のようにXcode10.xまでのメソッド概要と Parameters
の間に1行あける感覚に慣れてしまっていたため、
Xcode11以降の書き方に違和感があり、手動で1行挿入してしまっていたりします笑。
何はともあれ、地味に面倒だと感じていた部分がやっと修正されて何よりです。
Xcode11系は当初から幾つかの致命的なバグを抱えていましたが、
ひとまず細かなところも修正されて安定的に利用できるようになりましたかね〜
さて、本日はXcode11から新規似追加された SwiftUI
によるiOSアプリ開発について勉強していきたいと思います。
筆者がぱっと見る限り、 SwiftUI
は、
xib
や storyboard
によるUI実装のレビューの難しさの解消に一役買っていると感じました。
また、 SwiftUI
での書き方自体は、
これまでのiOSアプリの書き方に慣れている人にとっては、やはりそれなりの準備期間は必要になりそうです。
( RxSwift
使いの方々にとっては比較的とっかかりやすいという話もあります。 )
ということもあり、
実際の業務にて SwiftUI
を利用するシーンはまだそこまで増えることはないかもしれませんが、
iOSアプリ開発の実装方法の幅を増やすに越したことはないでしょう。
では、少しずつ SwiftUI
について勉強していきたいと思います。
さて、まずは新規に SwiftUI
プロジェクトを作成した場合、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
といった形で Hello World!
プロジェクトが作成されます。
シミュレータを起動することなく、ホットリロードしながら開発するには、
Resume
ボタンをクリックします。
その結果、以下のように、あたかもシミュレータが起動したかのような状態で開発を進めることができます。
具体的に SwiftUI
でどのようにUIを作成するのでしょうか。
慣れれば、コードベースで全て書き上げることができると思いますが、
慣れないうちは、GUIと併用して実装するのが良いかと思います。
SwiftUI
で実装する上で最も基本的かつ重要なことは、 body
プロパティです。
body
プロパティは 計算型の View
型プロパティです。
この body
に各種パーツを設定した View
をセットすることでUIを表現します。
言い換えると、『 body
には様々なパーツを包括した1つの View
をセットする』ということです。
例えば、
1 2 3 4 5 6 |
|
といったことはできず、
1 2 3 4 5 6 7 8 |
|
のように VStack
, HStack
, Group
など View
を包括するパーツを利用してまとめる必要があります。
先程記載した通り、コードベースで直接 VStack
で囲っても良いですし、
慣れないうちは Command
キーを押しながら Text
をクリックしメニューを表示させます。
ここから Embed in VStack
を選択すれば、コードに VStack
を挿入してくれます。
続いて、パーツの装飾方法について見ていきます。
今回は最もシンプルな例として、 Text
を扱います。
GUIから編集する方法はシンプルで、
のどちらかを実行することで、右メニューで編集が可能になります。
右メニューから、
がデフォルトで用意されており変更が可能です。
また、 Add Modifier
から多種多様なプロパティ設定を追加することができます。
(単純な装飾に限らないようですが。。。)
因みに、GUIから各種設定を追加すると、
.foregroundColor(Color.blue)
のように省略なしの表記でコードに挿入される場合がありますが、
.foregroundColor(.blue)
とすることも、もちろん可能です。
1 2 3 4 5 6 7 |
|
最後にボタンをタップシた際のアクションの実装について簡単に触れておきましょう。
今回は、サンプルとして、「ボタンをタップした際にアラートを表示する」ことを試してみます。
実装は次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
冒頭で @State
属性を用いて isShown
フラグを定義することで、
View
内で扱うことが可能となり、そのプロパティの状態に従って描画を実行できるようになります。
ここでは、ボタンタップ時に発火される Button(action: { ... })
で、
isShown
フラグを切り替えます。
そして後続の alert
で isPresented
に isShown
フラグを判定として利用することでアラートを表示します。
さて、まずは、ほんの少しの表面だけ SwiftUI
に触れてみました。
まだまだ複雑なアプリを開発するには練習が全然足りないため、継続して学びつつブログに書き起こせていければと思います。
と言ったところで本日はここまで。
]]>昨今、 Flutter
や React Native
などによるiOSアプリとAndroidアプリの同時作成が少しずつ現実的に実践されるようになってきており、
筆者的には、2010年代前半以来のリブームのように感じられる今日此頃です。
2010年代前半は、Facebookを筆頭に、最終的にはフルネイティブに舵を切り直すプロダクトが多かったイメージがあるのですが、
各種OSの浸透および安定化に伴い、今回の流れはある程度続く可能性があるのではと思わずにはいられません。
しかしながら、 Flutter
は Dart
というGoogle製の言語を利用し、
React Native
は JavaScript
および React
の知識が必要になります。
既に Swift
でのiOSアプリの開発や Kotlin
によるAndroidアプリの開発に慣れているエンジニアであれば、
言語書式が比較的似ていることから、OSやIDEの違いさえ把握できれば学習コストは大幅に抑えられる可能性があります。
一方で、それでは両OSアプリの同時作成の恩恵に預かることができないため、
何か良いものがないかな〜と思っていたところ、
『 Scadeは、Swiftを使用してAndroidアプリ開発を可能にすることを目指す』という記事を見つけました。
これは面白そうだなということで、今回はScadeについて見てみたいと思います。
公式ホームページによると、 Scade
とは『次世代のモバイルアプリの開発基盤』と言われています。
具体的にできることとしては、
Swift
言語でiOS&Androidの両OSのアプリを開発できるSCADE Simulator
という Scade
アプリ内で提供されているシミュレータが利用できるXcode
や Android Studio
で利用している各々専用のシミュレータも利用できるなどがあります。
Swift5にてABIの安定化を達成したこともあってか Scade
自体のバージョンも v1.0
に到達しています。
しかしながら、現在、最新の Scade
は Xcode11 ( つまり Swift5.0 )までしか対応しておらず、
お使いのMacで Xcode11.3
が入っている場合は Scade IDE
上でのコンパイルに失敗してしまうため注意が必要です。
何はともあれ、 Scade IDE
を使ってチュートリアルを進めてみましょう。
Scade
のドキュメントは非常に充実しており、チュートリアルとして Hello World
が用意されています。
参考:Scade: チュートリアル – Hello World
上記リンク先に動画付きで説明がなされているので、大筋で困ることはないと思います。
ただ、ドキュメントが古いのか、全く同じように作成することはできなかったため、紹介がてら使い方を見ていきたいと思います。
まず、ソフトウェアのDLページですが、こちらになります。
※無料で利用できます。
続いて、プロジェクトの作成方法を説明します。
① ナビゲーションバーから、Scade Projectを選択する
② プロジェクト名を決める
これだけで、IDE上に②で指定した名前のプロジェクト名が作成されていることを確認できると思います。
Scade
の特徴でも説明したように、 Xcode
や Android Studio
同様にGUIからレイアウトを決めることが可能になっています。
今回は、『チュートリアルのHello World』ということで下図のようなレイアウトを作成します。
では説明していきましょう。
① main.pageファイルを開く
② 右メニューのPaletteのWidgetsからLabelとButtonをドラッグ&ドロップする
③ 右メニューのPaletteのLayoutsからGrid1つとVertical2つをドラッグ&ドロップする
④ Grid内にVerticalを2つ子要素として追加し、それぞれのVerticalの子要素としてLabelとButtonを追加する
⑤ Gridの上下左右にレイアウトを設定する
⑥ Label側のVerticalのレイアウトを設定する
⑦ Button側のVerticalのレイアウトを設定する
⑧ Labelのレイアウトと色を設定する
⑨ Buttonのレイアウトと色を設定する
今回のチュートリアルでは、下記機能を持たせます。
これを実現するために、 main.page.swift
ファイルを開き、実行処理を書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
処理内容を説明すると、下記の通りです。
① getWidgetByName
を使ってボタン要素を取得しています
② onClick
でボタンのタップを補足し、 append
で処理を追加しています
③ ラベルの文字列変更のために getWidgetByName
で取得したラベルの text
に変更したい文字列を代入しています
左上からターゲットとなるプロジェクトを選択すると、実行するシミュレータを選択できます。
シミュレータを選択した後で、三角の実行ボタンを押下することでシミュレータを実行できます。
因みに、下図のように簡単にシミュレータの端末を変更する機能も備わっています。
さて、如何でしたでしょうか?
SwiftでiOS/Androidアプリを同時に開発できるなんて夢のような企画ですよね!
ただ、
Scade
のIDEが Eclipse
ベースでもっさりしているXcode
と同じ名称の部品があるので、同じように使えると思うと案外うまくいかないといった課題も感じました。
しかしながら、 Scade
が実用レベルに到達するようになれば、 Flutter
や React Native
と同じく1つの選択肢として普及するようになる可能性もあることでしょう…
まあ、今回は単純に発想が面白かったということで紹介にとどめたいと思います。
と言ったところで本日はここまで。
「iOS #2 Advent Calendar 2019」の22日目の記事です。
筆者がiOSアプリの開発を始めたのは、約8年ほど前でしょうか。
iOS史上でも初期から強力な機能だったこともあってか、当時はiOSアプリに位置情報の機能を載せることが流行っており、
筆者も漏れなく位置情報に関する調査や機能実装を永遠とこなしていた気がします。
今年でiOSもバージョン 13
となり、昔はなかった機能がたくさん登場しています。
新しい機能は当然、開発者の意欲を掻き立て、未知の世界をユーザに届けることに寄与することでしょう。
ただ、位置情報に人一倍強い思いがあることを自負している筆者ですから、
iOS13が出た今でも位置情報に関する仕様変更があることには感動もひとしおです。
今日は、iOS13からの位置情報に関する仕様変更を『WWDC2019 – What’s New in Core Location』を元に紹介して1年を締めくくりたいと思います。
約8年前は位置情報に関する様々な国内での取り決め事がまだまだ調整途中だったかと思います。
ただし、当時から「電池の消耗が激しいこと」と同じくらい「自身の位置情報を知られたくない」というプライバシーに対する強い意識があった気がします。
(もちろん現在の方がプライバシー意識は劇的に高まっていると思います。)
一方で、企業側としては、位置情報を利用した新しい便利な体験をユーザに提供するために、
位置情報を積極的に許可してほしい気持ちがあったかと思います。
これまでiOSはそんなユーザとアプリ提供者側の双方の立場を考慮して改善を進めてきました。
例えば、初め「常に許可」「許可しない」の2つの選択肢しかなかったところに、
「使用中のみ許可」が加わったりといった取り組みです。
iOS13では、この取り組みが更に一歩進んだ形になります。
iOS12までは、
1 2 3 4 5 |
|
とした際に、
の3つの選択肢を持つ確認ダイアログが表示されていました。
これがiOS13では、
の3つの選択肢に変更となりました。
一見すると、アプリ開発者にとって辛い仕様に思えるのですが、「常に許可」がなくなったわけではありません。
「常に許可」を求めるタイミングが変更になった
というのが正しい解釈となります。
因みに、ここで「使用中のみ許可」を選択した場合、
CLAuthorizationStatus
は実は .authorizedAlways
になります。
どういうことなのか詳しく説明しましょう。
先程の「使用中のみ許可 / 一時的に許可 / 許可しない」の3つの選択肢から 「使用中のみ許可」 を選択した場合、OSがユーザが忙しくない時を自動的に狙って、
かを問いかけてくれます。
ここで「常に許可に変更する」を選択して初めて、「常に許可」状態に設定することができます。
この仕様がアプリ提供元の企業やアプリ開発者に対して、何を問うているのでしょうか。
筆者は、
という大前提に加えて、
という釘を差しているようにも捉えています。
後者は、OSや純正アプリによって担保されていますが、前者はAppleの審査やそもそもの規約があるものの、作り手の仕組みにも依存する側面は完璧には拭えないでしょう。
因みに、
「使用中のみ許可のままにする / 常に許可に変更する」かの確認は1度のみであるため、
これを逃すと、ユーザが自主的に設定画面から設定を変更しない限り「常に許可」に変更する導線がなくなります。
iOS12ではユーザが「使用中のみ許可」を選択した場合、下記のようにできることが限られていました。
もし、ユーザが「常に許可」を選択すれば、上記に加えて下記も利用することができるようになります。
※WWDC2019の資料から該当箇所を抜粋して紹介させて頂いています。
https://developer.apple.com/videos/play/wwdc2019/705/
このため、アプリ開発者は、『どんな機能をアプリに持たせたいか』次第で「常に許可」「使用中のみ許可」のどちらをユーザに求めるべきかを決めていました。
これがiOS13では非常にシンプルになりました。
※WWDC2019の資料から該当箇所を抜粋して紹介させて頂いています。
https://developer.apple.com/videos/play/wwdc2019/705/
つまり、機能間の差異をなくして、『ユーザに求めた許可状態に即したタイミングで機能を提供する』ことが可能になったということです。
「常に許可」な状態はアプリがForeground/Background起動に関わらず、常に機能を利用できる状態であることがわかるかと思いますが、
「使用中のみ許可」な状態とは具体的にどんな状態を指すのでしょうか。
ここで簡単なサンプルアプリを作成して説明してみます。
下記のような簡単なサンプルアプリを作成してみました。
実験のためのアプリなので、機能は下記のみです。
実際の画面は下記の通りです。
実際のソースコードは下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
|
では、具体的に「使用中」状態を見るための実験をしてきましょう。
まずは、「使用中のみ許可」設定をしてから、 BackgroundモードOFFの位置情報取得開始 を実行してみましょう。
1 2 3 4 5 |
|
この結果、アプリがForeground起動の場合、 didUpdateLocations
が呼び出され、最新の位置情報を取得できます。
しかし、アプリをBackground起動にした場合、 didUpdateLocations
が呼び出されることはなくなりました。
※タイミングによっては呼び出されることがありますが、それはアプリはBackgroundで数秒Foregroundと同じ扱いになるOS仕様のためです。
上記の例では、この Foreground起動中が「使用中」 に当たります。
そのため、再び、Foreground起動にアプリを戻すと「使用中」状態に戻るため、 didUpdateLocations
が呼び出されるようになります。
続いて、 BackgroundモードONの位置情報取得開始 を実行してみましょう。
1 2 3 4 5 6 7 |
|
このとき、アプリがForeground起動の場合、 didUpdateLocations
が呼び出され、最新の位置情報を取得できます。
それに加えて、アプリをBackground起動にした場合でも、 didUpdateLocations
が呼び出され続けます。
この場合の例では、 ForegroundおよびBackgroundともに「使用中」 に当たります。
Backgroundで位置情報を利用していることが、ユーザにも伝わるように、ステータスバーの左側に青色で囲まれた矢印マークが表示されます。
これで 使用中 とはどういった状態を指すのか、おわかり頂けたかと思います。
基本的にはアプリはユーザが利用したい時に利用することになると思いますが、
アプリ提供元がアプリを利用する最適な場面を知らせたいといったこともあるでしょう。
そういった時には、 UNLocationNotificationTrigger
を使ったジオフェンスによるローカルプッシュが役に立つかもしれません。
特定の場所にジオフェンスを仕掛けておくことで、
領域侵入 or 領域退出時にローカルプッシュでユーザを気づかせ、
アプリを起動してもらうことで 使用中 状態に誘導し、更なる価値提供を狙えることでしょう。
参考までに UNLocationNotificationTrigger
の利用方法も記載しておきます。
まずは、 AppDelegate.swift
で最低限の準備をしておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
続いて、ジオフェンスを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
上記のように、「使用中のみ許可」の場合にのみ UNLocationNotificationTrigger
を仕掛けておくことも一つの手です。
「常に許可」は既にユーザから最大限の承諾を得ているため、更なる価値を届けるためのプッシュ通知という意味では特に意味を持たないためです。
さて、ここまで「常に許可」「使用中のみ許可」について説明してきましたが、
iOS13からは「一時的に許可」という選択肢が新たに追加になりました。
「一時的に許可」とは文字通り、 その時の使用中の間のみ許可をする ということです。
よって、 CLAuthorizationStatus
も .authorizedWhenInUse
になります。
また、 その時の使用中の間のみ許可する の「使用中のみ」は、「使用中のみ許可」と同じ定義になります。
つまり、
allowsBackgroundLocationUpdates=false
で locationManager.startUpdatingLocation()
するとForeground起動中が「使用中」に当たりますallowsBackgroundLocationUpdates=true
で locationManager.startUpdatingLocation()
するとBackground起動中も「使用中」に当たりますということです。
その時の使用中の間のみ許可する ため、「使用中」状態が終了したタイミングで 「未設定」状態に戻ります 。
実際に、 CLAuthorizationStatus
も .notDetermined
に戻ります。
一度「未設定」状態に戻ると、改めて位置情報の利用許可を求めなければ、位置情報サービスを利用することができません。
この「改めて位置情報の利用許可を求めるタイミング」ですが、アプリのフローに密接に結びつくため、非常に重要です。
全く関係のないタイミングで位置情報の利用許可を求めると、ユーザに不信感を持たれたり、煩わしさから離れていってしまう可能性もあるでしょう。
では、最適なタイミングとはいつになるのでしょうか?
それは、ユーザが再び「位置情報を利用する機能を使いたい」と思ったタイミングになります。
例えば、レストランの検索アプリの場合、「検索開始ボタンをタップしたタイミング」となるでしょう。
大事なことは、
『適切なタイミングで、ユーザが位置情報サービスを承諾するまで問いかける導線を用意しておく』
ということになります。
因みに、「一時的に許可」を選択した後で、『設定アプリ > プライバシー > 位置情報サービス』を見ると、 『次回確認』 が設定されています。
さて如何でしたでしょうか。
iOS13で仕様がシンプルになったからこそ、「位置情報サービスの利用許可を求める最適なタイミング」をしっかりと考える必要が出てきました。
また、この仕様変更は、アプリ提供元だけでなくユーザにとっても、わかりやすい仕様になったと言えるのではないでしょうか。
昔からある機能で慣れ親しみがあるものの、その機能全てがユーザに受け入れられているわけではありません。
少なからず、デメリットを感じてしまう側面もあることでしょう。
でも、だからこそ、継続して安心安全な機能提供をブラッシュアップしていくことは非常に大切なのでしょう。
我々、アプリ開発者もそういったことを十分に理解した上で、より良い形でユーザに価値を提供し続けていけると良いですね。
といったところで本日はここまで。
CocoaPods
はiOSアプリエンジニアであれば誰もが知るライブラリ管理ツールだと思いますが、
v1.7.0
以降では xcfilelist
を Input/Output File Lists
で指定できるようになりました。
( CocoaPods Blog – CocoaPods 1.7.0 Beta! )
Input/Output File Lists
とはXcode10で追加された Input / Output Files
に代わる、
xcfilelist
をパスとして指定できる新たな枠組みです。
既に v1.8.4
までリリースされており、Xcodeも11まで上がっているので今更感はありますが、気になったので少し具体的に差分を見てみました。
CocoaPods
の『 v1.7.0
未満のバージョン』と『 v1.7.0
以上のバージョン』で比較するため、 Podfile
を用意します。
因みに、今回の比較で利用したそれぞれのバージョンは下記の通りです。
v1.7.0
未満のバージョン: v1.6.1
v1.7.0
以上のバージョン: v1.8.4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
それでは早速比較をしてみましょう。
まずは v1.6.1
の方で pod install
を実行してみます。
インストール完了後、 Xcode
の対象ターゲットから Build Phases
> [CP] Embed Pods Frameworks
を開いてみましょう。
設定状態は、
"${PODS_ROOT}/Target Support Files/Pods-HogeHogeSample/Pods-HogeHogeSample-frameworks.sh"
が指定されているInput Files
と Output Files
にそれぞれパスが指定されているとなっています。
次に v1.8.4
の方で pod install
を実行してみます。
同じく Build Phases
> [CP] Embed Pods Frameworks
を開いてみましょう。
設定状態は、
"${PODS_ROOT}/Target Support Files/Pods-HogeHogeSample/Pods-HogeHogeSample-frameworks.sh"
が指定されているInput File Lists
と Output File Lists
にそれぞれパスが指定されているとなっています。
比較してみると、
v1.6.1
で利用していた Input / Output Files
から v1.8.4
では Input / Output File Lists
に変わっていることがわかると思います。
この Input / Output File Lists
で指定されているファイルの中身を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
結果、これまで Input / Output Files
にベタッと指定していたパスと同じであることがわかると思います。
因みに、上記中身が記載された xcfilelist
はプロジェクト自体には組み込まれていません。
ファイルからパスが読み取れさえすれば問題ないので、そのままなのでしょう。
ただし、 Git
で xcworkspace
も管理する場合は、
この xcfilelist
も Git
にアップロードする必要がありますので注意しましょう。
(プロジェクト内でファイル指定されているので、存在しないとビルドが通らなくなります。)
CocoaPods
自体が優秀なため、バージョンアップして pod install
を実行することで、
自動的に整合性を取ってくれるはずなので、意識する必要はそんなにないかもしれません。
しかし、こうした変更を理解しておくことで、開発中の想定外の差分に気づけたりすることもあります。
知っておいて損はないことなので、細かいところも注視していけると良いな〜というところで本日はここまで。
先日ふと調べ物をしていたところ、
iOS 13 – Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid
という何やら気になる話を見つけました。
どうも iOS13
からは、 UserDefaults
に保存できる容量が 4194304 bytes
と制限が追加されたようだと言うのです。
これは実際にやってみるっきゃない!ということで実験をしてみました。
実際に筆者が実行した実験内容は下記の通りです。
iOS13.1.3
の端末を用いて、 UserDefaults
への保存の合計容量が 4194304 bytes
を超過すると怒られるのかそれでは実際に見ていきましょう。
制限が 4194304 bytes
であるため、文字列を保存するのは手間がかかりそうだったので、
Assets.xcassets
に予め用意した画像を Data
型に変換後、 UserDefaults
に保存することにしました。
用意した画像は下記になります。
262KB
の画像実験に利用したソースコードは下記になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
早速、実験結果について見てみましょう。
1回の保存サイズが 350129 bytes
だったため、12回で 4201548 bytes
となり、
宣言である 4194304 bytes
を超えることになります。
今回の実験では、 print
文で『ランダムなキー名とバイト数』を出力するように組み込み、
端末の該当アプリに対して Download Container...
から UserDefaults
の中身を取得し比較してみました。
出力結果は、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
となり、 13個目 の保存時にエラーが出力されました。 (途中キー名が被っており、上書きとなるため、その回数はスキップしています。)
Download Container...
で取得した UserDefaults
の中身はどうなっているかと言うと、
となっており、12個目までは保存されているようでした。
厳密には少々超える分には、許容されているように見えましたが、少なくとも、はっきりと超えた場合は保存されないことがわかりました。
さて如何でしたでしょうか?
UserDefaults
は大量の重要データを保存するのには不向きであることは、これまで通り自明であったと思いますが、
それがより厳密化されたと言えるのではないでしょうか。
今後に備えて、データの保存方法を今一度見直すにはいい機会かなと思います。
と言ったところで本日はここまで。
参考URL:
]]>今回はiOS13から導入された Sign In with Apple
について見ていきたいと思います。
もし、 Facebook / Google / Twitter
などのアカウントを用いたログインを可能とする機能をアプリが持っている場合、
Appleが新たに提唱した Sign In with Apple
の機能を実装する必要性が出てきたようです。
一方で Facebook / Google / Twitter
などの本家のアプリでは恐らく実装する必要はないと思われます。
(ログイン機能を実装するのであれば、 Sign In with Apple
が必須という話ではなく、サードパーティ製のログイン機能を持つアプリに限るようです。 )
とは言え、「急にそんなことを言われても対応工数がかかるし、他に実装したい機能もあるし…」と困るエンジニアもいるかもしれません。
ですが、 Apple
が要求する以上、 iOSアプリを開発し続ける上で避けては通れない問題ですので、簡単に対応方法を紹介したいと思います。
まずは、コードを書く前に、 Sign In with Apple
の機能を有効にした App ID
を作成し、 Provisioning Profile
に紐付ける必要があります。
下記手順で Sign In with Apple
対応の App ID
を作成します。
まずは、 Apple Developer Program > Certificates, Identifiers & Profiles > Identifiers にアクセスします。
追加するための「+」ボタンを選択後に表示される下記の画面で App IDs
を選択します。
必要事項を入力します。
入力例)
SignInWithAppleSample
com.xxx.SignInWithAppleSample
下にスクロールすると Sign In with Apple
の項目が追加されているので、チェックをつけます。
上記状態で次に進み、確認画面で問題ないことを確認すれば、 Register
ボタンを選択するだけで作成完了です。
下記手順で App ID
を紐付けて Provisioning Profile
を作成します。
Apple Developer Program > Certificates, Identifiers & Profiles > Profilesにアクセスします。
追加するための「+」ボタンを選択後に表示される下記の画面で iOS App Development
を選択します。
(開発時を想定しているため、リリース時であれば、 App Store
など適切なものを選択してください。)
先程作成した App ID
を選択します。
必要な Certificate
を選択します。
利用したいデバイスを選択します。
Provisioning Profile
の名前を決めます。
Generate
ボタンを選択すれば作成完了です。
Apple Developer Program
上での準備が整ったら、 Xcode11
で Sign In with Apple
の Capability
を追加しましょう。
プロジェクトを選択し、 Signing & Capabilities
タブから追加します。
ソースコードを書いていきましょう。
これは Apple
が公開しているサンプルを一部抜粋して説明します。
サンプルはこちらからダウンロードできます。
Sign In with Apple
は AuthenticationServices
の各種メソッドを利用するため、
まずは AuthenticationServices
をインポートします。
1 2 3 4 5 6 |
|
続いて、ログイン画面に Sign In with Apple
のボタンを配置します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
上記を見てわかるように、タップ時に呼び出すメソッドを定義して、その中で必要なリクエストを生成し、実行しています。
requestedScopes
は必要なアクセス情報の範囲を定義するようですが、
今のところは .fullName
と .email
しか用意されていないようです。
また、 delegate
と presentationContextProvider
を呼び出す必要があります。
これは、
ASAuthorizationControllerDelegate
の authorizationController:didCompleteWithAuthorization:
で認証完了をキャッチするASAuthorizationControllerPresentationContextProviding
の presentationAnchorForAuthorizationController:
で認証画面に必要な画面の提示場所を設定するためです。
1 2 3 4 5 6 7 8 9 10 11 |
|
ここまで実装した内容で、どんな動きになるのかを見てみます。
あまりケースとして多くはないかもしれませんが、普通にありえるので実装時に考慮が必要です。
この場合、 performRequests()
を実行した時点で『設定アプリでAppleIDアカウントでサインインするかどうか』を聞かれます。
サインイン後に、改めてアプリに戻りログインを実行することで先に進めるようになります。
改めて Sign In with Apple
のボタンをタップすると、以下の画面が表示されます。
Continue
ボタンのタップで先に進むと、以下の画面が表示されます。
ここで Share My Email
または Hide My Email
のどちらかを選択することになります。
Hide My Email
はよりユーザにとってセキュアな状態を保つために用意されており、以下の特徴があります。
上記選択した上で Continuer with Password
ボタンをタップして次に進むと、以下の画面が表示されます。
パスワード入力して次に進むと、二段階認証を求められます。
二段階認証してやっと認証が完了となります。
さて如何でしたでしょうか。
アプリの機能次第で対応必須な内容ですので、今からでも知っておいて損はないかなと思います。
SDK
自体は非常に簡単に扱えますので、アプリ側の実装は早めに目処をつけておいて、データ構成など課題がある部分にフォーカスした方が良いでしょう。
と言ったところで本日はここまで。
]]>今回はiOS13のプッシュ通知用デバイストークンについて見ていきたいと思います。
Swift
に関して言えば、歴史的変遷から、問題のない現場が多いと思うのですが、
Objective-C
を中心に活用している現場では注意が必要かもしれません。
具体的には後述しますが、
description
を利用してデバイストークンを取得する方式は iOS13
から見直す必要がありそうです。
折角なので、基本的な設定についても説明していきます。
まずは、Xcode11上で下図の状態まで設定します。
▼確認観点
* Team ID
が利用想定のものであること
* Bundle Identifier
が利用想定のものであること
* Signing Certificate
が利用想定のものであること
* Provisioning Profile
の Capabilities
に Push Notifications
が含まれている
* Capability
として Push Notifications
が追加すること
続いて、 AppDelegate.swift
上の実装です。
基本的なステップは4つです。
① プッシュ通知の利用許可のリクエストを送信します
② 利用許可を得た場合に、プッシュ通知の利用登録を実行します
③ プッシュ通知の利用登録が成功したことをキャッチする Delegate
メソッドを書きます
④ プッシュ通知の利用登録が失敗したことをキャッチする Delegate
メソッドを書きます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
さて本題に入ります。
Objective-C
時代に主流と言われていたデバイストークンの取得方法は、以下のような description
を利用した手法でした。
1 2 3 |
|
これは、 [deviceToken description]
で取得される値が、
<7097978d 1e438923 ... 6fdbf111>
というような先頭と末尾を <>
で括られていたためです。
そして、1文で書けるというのも当時から魅力的だったのかもしれません。
Swift
に言語が変わってからも、上記手法が多くの場面で使われてきたことと思います。
しかし、 Swift3
になったタイミングで、状況が変わりました。
プッシュ通知の利用登録成功時の Delegate
メソッドの deviceToken
が NSData
型から Data
型に変わったことで、この手法が利用できなくなったのです。
この時点で
1
|
|
という書き方がデバイストークン取得の主流に完全に取って代わったと思います。
もしかしたら、 Data
型を NSData
型に変換することで、
引き続き旧来の手法を利用している現場があるかもしれませんが、
その場合は、即刻デバイストークンの取得方法を見直しましょう。
これはあくまでも Swift
におけるデバイストークンの取得の変遷であって、
Objective-C
は何ら変わることがなかったため、
旧来の書き方が続いている現場がまだまだ多い気がしています。
しかし、 iOS13
からはどうやら見直しが必須になったようです。
というのも、 description
を利用すると
1
|
|
のような形で返ってきてしまうため、
<>
を除去することでデバイストークンを取得することができなくなったようなんです。
では、 Swift
は先程書いた通り1行で書けますが、
Objective-C
ではどう書いていくべきなのでしょうか。
安心してください。 Facebook
が1つの解を示してくれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
流石に1行で済ませることは難しいですが、
これで Objective-C
でも正しくデバイストークンを取得できるようになりました。
さて如何でしたでしょうか。
iOS13は細かいところも含めて様々な変更が入っているため、日々最新情報をキャッチしつつ、自身でも積極的に使い倒していく必要がありますね。
と言ったところで本日はここまで。
]]>今回はiOS13で新たに追加された BackgroundTasks Framework
について見ていきたいと思います。
基本的には、 WWDC2019動画の『Advances in App Background Execution』を見ながら実践してみました。
ですが、微妙に躓くところもあったのでメモとして残しておきたいと思います。
まず、 BackgroundTasks
の説明です。
BackgroundTasks
はiOS13から利用できる新しいFrameworkになります。
BackgroundTasks
には大きく分けて下記2つのAPIが存在します。
Background Processing Tasks
は以下シーンでの利用が想定されています。
そのため、 数分間 の処理実行が許されています。
また、 requiresExternalPower
というフラグを true
にすることで、CPU消耗によるプロセスキルをさせないよう制御することができます。
(iOSの世界でこれって結構スゴイ気がしますね。)
Background App Refresh Tasks
は以下シーンでの利用が想定されています。
これまで上記のような対応をする際には Background Fetch
を利用していたことと思いますが、
今回の新APIの発表により、旧APIはdeprecatedになったそうです。
1 2 3 |
|
続いて具体的な使い方を見ていきます。
① Xccode上でCapabilityを追加します
バックグラウンド処理を利用する場合はこれまで通り Capability
の Background Modes
が必要になります。
Xcode11で追加する方法が少々変わっているので気をつけましょう。
② Background Modesにチェックを入れます
今回は、 Background Processing Tasks
と Background App Refresh Tasks
なので下図の通りです。
③ Info.plistにIdentifierを登録します
Info.plist
に Permitted background task scheduler identifiers
を追加します。
また、 Background Processing Tasks
と Background App Refresh Tasks
用にそれぞれ Identifier
を定義します。
因みに、この Identifier
は例によってユニークであることが求められるので、
com.xxxx.XXXXSample.process
, com.xxxx.XXXXSample.refresh
といったDNSの逆書きが推奨されています。
ここまででソースコード以外の準備は完了です。
続いて、ソースコードを書いていきましょう。
Background Processing Tasks
と Background App Refresh Tasks
それぞれ記載します。
① BackgroundTasksをimportします
1 2 3 |
|
② didFinishLaunchWithOptions内にバックグラウンドタスクを登録します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
③ バックグラウンドタスクにスケジューリングします
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
④ 実際に実行する処理を定義します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
① BackgroundTasksをimportします
1 2 3 |
|
② didFinishLaunchWithOptions内にバックグラウンドタスクを登録します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
③ バックグラウンドタスクにスケジューリングします
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
④ 実際に実行する処理を定義します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
上記で実装が完了しました。
実際に挙動を試すためには、特別な手順が必要になります。
① アプリを起動します
② アプリをバックグラウンドに移します(登録トリガーのためです)
③ 再度アプリを起動します
④ Xcodeで Pause Program Execution
をタップします
⑤ LLDBに以下コマンドを入力して実行します
1 2 3 4 5 |
|
⑥ 再度、Xcodeで Pause Program Execution
をタップします
以上で実際の実行処理が見れるようになるはずです。
因みに、実行処理の期限切れを試したい場合は、以下のように⑤のLLDBで以下を入力して実行します
1 2 3 4 5 |
|
筆者が試しに実装してハマったところを参考までに載せておこうと思います。
筆者の場合、なぜかシミュレータで実行しようとすると、以下のエラーが発生してしまいました。
1
|
|
これは、サンプルコードで試しても同様でした。
ただ、実機で試したところ問題なく通ったんですよね…
さて如何でしたでしょうか。
iOS13で追加された新APIは旧バージョンサポートのため、
すぐには利用されないかもしれませんが、iOS13の普及に伴い、利用シーンは確実に増えていくことでしょう。
そのため、サンプル程度の実装でも、使い方を学んでおくことは今後の役に立つと思っています。
と言ったところで本日はここまで。
]]>筆者はこれまでLLDBコマンドでは po
を利用することが多かったのですが、
WWDC2019のビデオであるLLDB: Beyond “po”を視聴して改めて v
コマンドの使いやすさを勉強しました。
今回はその v
コマンドについて実例を交えながら見ていきたいと思います。
( p
や po
コマンドでなぜか変数の中身が見れない…と思っていた方、必見です。 )
まず、各種コマンドについて簡単に見ていきます。
po
は print object
の略だそうです。
po
は expression
コマンドのエイリアス(ショートカット機能)であり、簡略化して書くことができます。
具体的には、以下の通りです。
1 2 3 |
|
また、 po
は式を評価した結果のオブジェクトを返すため、非常に見やすい形式で出力されます。
早速例を見てみましょう。
下記のような構造体が定義されているとします。
1 2 3 4 5 6 7 |
|
これを初期化した直後にブレークポイントで止めているとしましょう。
1
|
|
ブレークポイントで止めた状態で po
コマンドを打ち出すと、
1 2 3 4 5 6 7 8 9 10 |
|
という出力結果が得られます。
p
は print
の略だそうで、po
と同じくコンパイルして式を評価します。
po
と異なるのは、出力結果の形式です。
以下はその例です。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
po
と違って $R4
のような名前がつけられていることがわかります。
この $R4
のような名前を引数に指定して po
や p
コマンドを実行することができます。
1 2 3 4 5 |
|
このように以前の結果から更に値を取得するような場面で使い勝手が非常に良いです。
もちろん p
も expression
コマンドのエイリアスになっています。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
v
は variable
の略のようです。
出力形式自体は p
コマンドと同じになっています。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
po
や p
と同じくエイリアスなのですが、 expression
ではなく frame variable
コマンドのエイリアスです。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
一見、 p
コマンドを使えば良いのでは?と思われそうですが、 v
コマンドは以下の特徴があります。
上記のどちらにも言えることですが、代わりに式の評価ができないので使い分けが必要です。
もう少し具体的に v
コマンドの良さを見ていきましょう。
次のように定義された構造体があったとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上記の通り、 iOSEngineer
はプロトコル Engineer
に準拠しています。
加えて独自に swiftLevel
と obcLevel
という変数を持ちます。
この構造体を下記のように初期化している場面があったとします。
1
|
|
これを p
や po
コマンドで iOSEngineer
独自のフィールドである swiftLevel
を出力させようとすると、
1 2 3 4 |
|
このようなエラーが表示されます。
p
や po
コマンドは1回しか動的解決をしないため、
Engineer
型として格納された engineer
には swiftLevel
はメンバ変数として持ち合わせていないと見なされてしまうのです。
もちろん、p
や po
コマンドは式の評価が可能なので
1 2 |
|
のようにキャストすることで中身を見ることはできます。
ですが、少々手間ではありますよね…
(深い階層のサブフィールドを見ようとすればするほど…)
これが v
コマンドでは、
1 2 |
|
とキャストせずとも結果を得ることができます。
もう一つ、LLDBコマンドを利用する上で役立つのが filter
機能です。
折角なので使い方を見ていきましょう。
フィールドの多い変数や、その変数と同じ型の要素を数十個持つ配列などを出力させる場合、とてもではないですが特定のフィールドを見つけることは困難です。
そんな時には filter
機能を利用して、特定の型であれば、特定のフィールドのみを表示するといったことができます。
指定の方法は簡単で、
1
|
|
のようにするだけです。
筆者の例で言えば、
1
|
|
のような感じです。
これにより、
1 2 |
|
のように今必要のないフィールドを省略して、結果を得ることができます。
※便宜上、 v
コマンドを使いましたが p
コマンドでも同じ結果を得ることができます。
因みに、機能利用後は忘れずに filter
を解除しましょう。
1
|
|
で解除できます。
因みに、 filter
機能も万能ではなく、場合によっては複数回 filter
をセットすることで結果を得なければいけないこともあります。
例えば、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
のように新たに Engineer
プロトコルに準拠した WebEngineer
を定義し、以下のような配列を作成します。
1 2 3 4 5 6 |
|
engineers
をそのまま v
コマンドで出力すると少し見えにくそうなので filter
機能が利用したいのですが、
Engineer
に準拠しているからと言って
1
|
|
としても思ったようには動かず、全フィールドが出力されてしまいます。
そこで、
1 2 |
|
としてやれば、
1 2 3 4 5 6 |
|
このように期待した結果を得ることができます。
さて如何でしたでしょうか。
p
, po
, v
コマンドは各々用途がありそうですが、特に v
コマンドの利便性を感じてもらえたら幸いです。
筆者も積極的に v
コマンドを使っていきたいと思います。
といったところで本日はここまで。
参考URL:
]]>これまでメソッドの処理時間を計測するには、
Date
関数の timeIntervalSince
メソッドを以下のように利用する場面が度々ありました。
1 2 3 4 5 |
|
しかし実は iOS12
から os.signpost
を利用してもっと便利に処理時間を計測することができるようになりました。
本日は os.signpost
を利用したことをメモとして記録しておきます。
os.signpost
を利用すると何が良いかと言うと、
Instruments
による他の計測結果と並行して状況を把握できるなどが上げられます。
一方でデメリットは os.signpost
がiOS12以降でしか利用できないということです。
よって、iOS11以下をサポートするアプリの場合、
1 2 3 |
|
の条件分岐をした上で os.signpost
を利用する必要があります。
しかし、これは時間が解決してくれるでしょう。
(あまり気にするデメリットではないということです。)
それでは早速、 os.signpost
を使ってみましょう。
os.signpost
は特にSDKを追加することなく、 import
が可能です。
① import os.signpost
を追加する
② OSLogオブジェクトを初期化する
Apple Developer Document: OSLog – initに記載のある通り、初期化をします。
1
|
|
subsystem
には com.your_company.your_subsystem_name
の形で定義するようにと例として書かれています。
category
はログのカテゴリ分けに利用できるため、カテゴリ種別で命名すると良いでしょう。
③ 計測開始時に OSSignpostType
に .begin
を指定した os_signpost
メソッドを呼び出す
Apple Developer Document: os_signpostに記載のあるメソッドを呼び出します。
1
|
|
④ 計測完了時に OSSignpostType
に .end
を指定した os_signpost
メソッドを呼び出す
③と同じメソッドですが、計測完了時には OSSignpostType
に end
を指定します。
1
|
|
これで、後は Instruments
を起動して、os_signpost
を追加して計測すればOKです。
今回は、下記のようなサンプルを用意して計測してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Instruments
で計測した結果は下記のように見ることができます。
上記結果から、計測処理が 平均88.97 [ms]
かかっていることがわかります。
またこの処理の実行中はCPUが100%に張り付いていることも見て取れます。
今回はサンプルなので print
で1万回出力するメソッドを計測しました。
本物のアプリではボトルネックとなる処理を見つけ、如何にして処理を改善できないか検討することになるでしょう。
いかがでしたでしょうか。
iOS13のリリースが近づいてきた(iOS12の普及が十分に達しつつある)今だからこそ改めて signpost
を積極的に利用していきましょう。
といったところで本日はここまで。
最近、業務で形態素解析の話が出ました。
形態素解析はサーバサイドで実行していることが多く、あまりアプリでは馴染みがないと思っていたのですが、
意外とそんなことはなく、iOSでも簡単に試すことができることを知り、今更驚きました。
因みに、筆者が形態素解析と聞いて真っ先に思い出したのは、
「幽遊白書の海藤戦のタブー能力」でした笑。
今回はiOSでの形態素解析を、海藤戦のタブー能力を織り交ぜつつ説明したいと思います。
iOSでは、形態素解析のための標準APIとして NSLinguisticTagger
クラスが用意されています。
これを利用すると、解析は非常に簡単で下記のように数行で実行できてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上記の結果は、
1 2 3 4 5 6 7 8 9 10 11 12 |
|
のようになりました。
なかなか正確に単語ごとに分解できているように見えますね。
因みに、全てひらがなの文章があった場合にはどうなるのかというと…
1 2 |
|
結果は、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
のようになりました。
なかなかに難易度が高いようですね…
では、基本的な説明が終わったところで、いよいよ海藤戦の再現をしてみましょう。
まずは、先程の実装を元に、形態素解析をかけた日本語の文章が特定の単語を含むかどうか調べる処理を以下の通り作ります。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
続いて、単純に文字列を含んでいるかどうか調べる処理も以下の通り作ります。
1 2 3 |
|
この2つのメソッドに、
1 2 |
|
を引数として設定してみましょう。
結果は、
1 2 |
|
となりました。
前者の analyzeMorphologic
は形態素解析をすることで、『意味を持った単語が文章内に存在しているか』を見るのに対して、
後者の contains
は target
となる文字列が『文章内に存在しているか』だけを見ています。
よって、海藤のタブー能力が前者だと気を抜いていると、
実は後者がルールであったがために引っかかり、魂を抜かれてしまうことになってしまうのです。
海藤のタブー戦であれば、むしろ形態素解析をする必要はなかったのですが、
現実にソフトウェアを実装する現場では形態素解析が重要な場面は多分にあることでしょう。
と言ったところで、本日はここまで。
大量のファイルを新規作成して、そのファイルが一定のルールを持った名称であるかどうかをチェックする際に、流石に目視で確認するのは大変過ぎるので、
シェルスクリプトで確認できるようにしてみました。
その時のメモとして残しておこうと思います。
例として、今回は、iOSアプリ内で利用する画像ファイル名に @2x
, @3x
が付与されていることを確認するためのシェルスクリプトを作りました。
実際の処理内容は下記の通りで、ルールにそぐわないファイルを出力してくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
実行方法は下記の通りです。
相対パスの場合は、
1
|
|
絶対パスの場合は、
1
|
|
のように指定できます。
極力、ミスの起こりうる手動ではなく、自動で効率的に業務を遂行していきたいですね。
と言うことで本日はここまで。
iOSアプリを開発する中で避けて通れないのが Xcode
周りの扱いです。
とは言え、個人でのアプリ開発であれば、そこまで工夫せずともビルド待ち時間に困ったりすることはないかもしれません。
ですが、業務で扱うような大規模なアプリ開発となると、やはり様々な工夫が必要になることでしょう。
その中の一つに『 Xcode
の環境変数を有効に利用すること』が上げられます。
今回はその『 Xcode
の環境変数』に関するメモ書きを残しておこうと思います。
Xcode
の環境変数と一言で言っても、種類はかなりの数に上ります。
Xcode Help > Build settings referenceに一覧があるので、ざっと見てみるのも良いかと思います。
ただ、毎回、「あの環境変数はどんな名称だったっけ…?」という時にリファレンスを見るのも手間がかかります。
そこで、Xcode
で用意されている、「環境変数を確認するためのコマンド」を積極的に利用しましょう。
使い方はシンプルで下記のようになります。
.xcodeproj
ファイルと同階層に移動して、以下コマンドを叩きます。
1
|
|
すると、
1 2 3 4 5 6 7 8 9 |
|
のように結果が表示されます。
思ったよりも大量に出力されるため、初めから探したいものが決まっている場合は grep
コマンドを利用して出力させて見るのが良いでしょう。
例えば、
1
|
|
のような感じで…。
環境変数の調べ方がわかったところで、実践例を紹介しておきたいと思います。
例えば SwiftLint
を利用して静的解析をかける場合、常に SwiftLint
を実行する必要はありません。
手元での開発時や、社内展開時のCI/CDツールにかける時に実行する場合が多くなるかと思います。
そんな時には、 Build Phases
で下記のように設定することでしょう。
1 2 3 4 5 |
|
例えば、 Carthage
でOSSライブラリを管理している場合、 Debug
時と Release
時で利用する/利用しないライブラリが別れていることがあるかもしれません。
その場合は、 Build Phases
の Input File Lists
と Output File Lists
で指定するファイルのパスを工夫する必要があります。
まずは下記のように、 Input File Lists
と Output File Lists
用のファイルを格納する構造をプロジェクト内に作ります。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
続いて、 Carthage
利用時に指定する Build Phases > Run Script
内の Input File Lists
と Output File Lists
にそれぞれパスを指定するだけです。
1 2 3 4 5 |
|
因みに、 Input File Lists
や Output File Lists
を使わずとも、実践例①と同様にシェルスクリプト内で ${CONFIGURATION}
を利用する方法もあるのでは?と思われるかもしれません。
確かに、その方法でも問題なく成立する場合もありますが、
diff
として確認ができるので、レビューしやすいXcode10
からの新ビルドシステムが厳密なビルドを実行するため、エラーが出るパターンがあることから Input File Lists
や Output File Lists
を利用することを筆者はおすすめしたいと思います。
(後者でうまくいかないパターンは、 CONFIGURATION
を変えると、複数のターゲット間で Input/Output
するモジュールが入れ替わる場合などです。 )
さて、最近、 Xcode
の環境変数周りを触る機会が久しぶりにあったためメモ書き程度に書き記しました。
忘れがちなので心に留めておきたいですね。
と言うことで本日はここまで。
約3年前にjazzyを利用していました。
(以前の記事:jazzyを使って、Swiftで書いたプロジェクトのリファレンスを自動生成しよう!)
今回、久々に jazzy
を触ってみて .jazzy.yaml
を利用する機会があったので、その時のメモを書き記しておきます。
では早速、 .jazzy.yaml
の書き方を見ていきます。
作成する階層はアプリのプロジェクトの xcodeproj
と同階層です。
実際の .jazzy.yaml
は下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
このように定義しておけば実行するときのコマンドは
1
|
|
で済みます。
今回は簡単に .jazzy.yaml
の書き方についてまとめました。
本日はここまで。
今回は GitHub
と Slack
の連携を自作でやってみる話の備忘録です。
と言っても、手法は本当に簡易で、
GitHub
上で Webhook
を設定するGitHub
からの POSTリクエスト
を受け付けるサーバを用意するPOSTリクエスト
から各種値を抽出して、Slackに通知するを対応することで実現することが可能です。
それでは早速見ていきましょう。
GitHub
上で Slack
と連携したい Repository
を開いて、Settings
ページにアクセスします。
Settings
ページの左メニューから Webhooks
を選択して、Add webhook
を選択します。
続いて、Add webhook
ページを編集します。
① Payload URL
にPOSTリクエストの送り先を設定します
② Content type
は application/json
を設定します
③ Which events would you like to trigger this webhook?
に設定したいものを設定します
※筆者の場合は、個別に細かく設定可能な Let me select individual events.
を選択しています。
ここは開発者の好きなインフラを用意すれば良いと思います。
AWSでもGCPでも良いかと思います。
また、APIサーバの作り込みに関しても、好きな言語で、自由にフレームワーク等を使って実装すれば良いと思います。
ポイントは、時間をかけずに作ることです。
(あくまでもチーム開発などを円滑に進めるための取り組みであって、この作り込みが目的ではないためです。)
因みに、筆者は、Node.js
とExpress
を利用してAPIサーバを作ることにしました。
では、少しAPIサーバの実装について紹介します。
まず、package.json
は下記のようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
先程申し上げた通り Express
を用いているのと、Slack Web API
を簡単に利用できる @slack/web-api
も導入しています。
続いてメインとなるserver.js
です。
POSTリクエスト
を受け付けるところまでですが、下記のように実装しています。
※ただ、Express
を利用している、よくある書き方かなと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
肝心のPOSTリクエスト
で受け付けた後の処理ですが、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
のようになっています。
先程導入した @slack/web-api
を利用すれば、いとも簡単にSlack
への通知が可能となっています。
そしてSlack
にどんな通知を送るかについてですが、GitHub
上で発生したイベント毎にメッセージが異なると考えられます。
よって、GitHub
からPOSTリクエスト
された値を分析して、最適なメッセージを送る処理を導入してみましょう。
今回、筆者は下記のイベントをハンドリングすることにしました。
PullRequest
にReviewer
をセットした時に、レビュー依頼を流すPullRequest
のレビューをして、ステータスを変更した時に、レビュー依頼者に知らせるPullRequest
のdiff
に沿ってコメントを書いたことを流すPullRequest
のConversation
にコメントを書いたことを流すそれらをハンドリングするメソッドをまずは用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
それぞれイベントによって作成するメッセージが異なるため、1つずつメソッドを切り出しました。
『PullRequest
にReviewer
をセットした時に、レビュー依頼を流す』場合の処理は下記です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
|
『PullRequest
のレビューをして、ステータスを変更した時に、レビュー依頼者に知らせる』場合は下記です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
『* PullRequest
のdiff
に沿ってコメントを書いたことを流す』場合は下記です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
『* PullRequest
のConversation
にコメントを書いたことを流す』場合は下記です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
さて、割と手軽に実装できることがわかったところで本日はここまで。
今後、もっと様々なイベントをキャッチしてSlack
に流す必要性が出てきたら、どんどん足していこうかな。