iOS 14 以降の Google AdMob 対応(AppTrackingTransparency Framework)

資料

前提

iOS 14 では AppTrackingTransparency Framework が追加され、ユーザのトラッキング収集はこのフレームワークを介して、ユーザに承認を得る必要がある。

もし iOS 13 以前に IDFA を利用して広告を表示していたアプリがあったとして、iOS 14 で AppTrackingTransparency を使ってユーザの承認を得ない場合は、IDFA が無効になった状態で広告が表示されることになる。 (=収益が下がることが予想される)

Google AdMob で必要な変更

Google AdMob のバージョンを更新する

  • Google Mobile Ads SDK は 7.64.0 以降を使用する

ラッキング利用の承諾をリクエス

Info.plist にトラッキング利用の説明文を追加する

NSUserTrackingUsageDescription を Info.plist に追加する。

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

ラッキングの承認をリクエストして、その結果 Google AdMob を表示する。

import AppTrackingTransparency
import AdSupport
...
func requestIDFA() {
  ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
    // Tracking authorization completed. Start loading ads here.
    // loadAd()
  })
}

ユーザへのトラッキング利用の許諾アラートに説明文が表示される。 f:id:daisuke-t-jp:20200918092200p:plain:w300

iOS 14 でトラッキング利用をユーザに拒否されたら?

Google AdMob ではトラッキング広告は表示できない。

そういう時は、かわりに Google AdMob は SKAdNetwork を使用して、アプリインストールをアトリビューションすることができる↓

https://developers.google.com/admob/ios/ios14?hl=ja#skadnetwork

Apple の住所(地名)検索 API を比較する(iOS)

テキストから住所(地名)を得たい場合 (たとえば、"嵐山" という入力から "京都府京都市右京区" という結果が欲しい)

その用途に使用できそうな AppleAPI は、以下がある。

これらの違いや使い道を確認したかったので、テキスト入力をしてそれぞれの API の結果を一覧表示するサンプルを作った。


調べて使ってみてわかったこと

CLGeocoder のリクエスト制限

CLGeocoder は短時間に大量リクエストを行うと失敗する。(CLError.Code.network

この動作はドキュメントにも明記されている。

Apps must be conscious of how they use geocoding. Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. (When the maximum rate is exceeded, the geocoder returns an error object with the CLError.Code.network error to the associated completion handler.) Here are some rules of thumb for using this class effectively:

ただし、以下の記載があるので、ユーザアクションに紐づいた頻度程度の API 呼び出し自体は考慮されているようだ。

• Send at most one geocoding request for any one user action.

アプリ側で呼び出すタイミングを気を付ければ、あまりエラーになることは無さそうだ。(逆に言えば、ユーザー入力など関係なく、アプリの動作として大量リクエストを送ったりする用途には使えない)

MKLocalSearch

CLGeocoder とは違い、MKLocalSearch はリクエスト制限でエラーにはならない(連続リクエストをしてエラー発生を観測できなかった)

こちらは、現在地と範囲を指定して地図上にある"近場"の Placemark を検索するのが本来の用途のようだ。(LocalSearch なので)

CLGeocoder とはちがい、住所だけではなく、店舗の名前を知りたいならばこちらを使う。

ただし、MapKit の機能なので、地図表示がないアプリで住所検索をしたいために MKLocalSearch だけ使う、というのはレギュレーションに反していないのか、疑問である。

CLGeocoder では複数の結果が得られない?

f:id:daisuke-t-jp:20200822185109p:plain:w300

iOS に入っている Apple の天気アプリではこのように、テキストに対して住所候補が複数あれば、複数表示される。

このような動作を期待して CLGeocoder を使ってみたが、CLGeocoder はレスポンスは配列であるが、結果は常に単一になっていた。

たとえば "中央区" の場合、iOS の天気アプリのように、日本国内にある中央区を複数候補を返して欲しいが、ひとつの中央区しか CLGeocoder は返さない(現在位置から一番近い "中央区" になる?)

この CLGeocoder のレスポンスが一つしか得られない問題は StackoverFlow でも散見されるので、どうにかなる話では無さそうだ。

「候補が複数欲しい」場合は、AppleAPI は使わず他のサービス(GoogleAPI など)を使う必要があるようだ。

OSS の iOS の Web ブラウザアプリで、UserAgent を設定している箇所を見比べてみる。

気になったので、オープンソースiOS の Web ブラウザアプリで、 WKWebview のカスタムユーザーエージェント(customUserAgent) を設定している部分を見てみる。

Chrome

ユーザーエージェントの例

Mozilla/5.0 (iPhone; CPU iPhone OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/84.0.4147.71 Mobile/15E148 Safari/604.1

UserAgent関連コードを抜粋(これがすべてではない)

std::string BuildOSCpuInfo() {
  std::string os_cpu;
  // Remove the end of the platform name. For example "iPod touch" becomes
  // "iPod".
  std::string platform =
      base::SysNSStringToUTF8([[UIDevice currentDevice] model]);
  size_t position = platform.find_first_of(" ");
  if (position != std::string::npos)
    platform = platform.substr(0, position);
  base::StringAppendF(&os_cpu, "%s; CPU %s %s like Mac OS X", platform.c_str(),
                      (platform == "iPad") ? "OS" : "iPhone OS",
                      OSVersion().c_str());
  return os_cpu;
}

/* 中略 */

std::string BuildMobileUserAgent(const std::string& mobile_product) {
  std::string user_agent;
  base::StringAppendF(&user_agent,
                      "Mozilla/5.0 (%s) AppleWebKit/605.1.15"
                      " (KHTML, like Gecko) %s Mobile/15E148 Safari/604.1",
                      BuildOSCpuInfo().c_str(), mobile_product.c_str());
  return user_agent;
}

Firefox

ユーザーエージェントの例

Mozilla/5.0 (iPhone; CPU OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/28.0 Mobile/15E148 Safari/605.1.15

以下、UserAgent関連コードを抜粋(これがすべてではない)

    public static let uaBitSafari = "Safari/605.1.15"
    public static let uaBitMobile = "Mobile/15E148"
    public static let uaBitFx = "FxiOS/\(AppInfo.appVersion)"
    public static let product = "Mozilla/5.0"
    public static let platform = "AppleWebKit/605.1.15"
    public static let platformDetails = "(KHTML, like Gecko)"
 
 /* 中略 */

public static func defaultMobileUserAgent() -> UserAgentBuilder {
        return UserAgentBuilder(product: UserAgent.product, systemInfo: "(\(UIDevice.current.model); CPU OS \(UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "_")) like Mac OS X)", platform: UserAgent.platform, platformDetails: UserAgent.platformDetails, extensions: "FxiOS/\(AppInfo.appVersion)  \(UserAgent.uaBitMobile) \(UserAgent.uaBitSafari)")
    }

DuckDuckGo

ユーザーエージェントの例

Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.4 Mobile/15E148 DuckDuckGo/7 Safari/605.1.15

UserAgent関連コードを抜粋(これがすべてではない)

 struct UserAgent {
    
    private struct Constants {
        // swiftlint:disable line_length
        static let fallbackWekKitVersion = "605.1.15"
        static let fallbackSafariComponent = "Safari/\(fallbackWekKitVersion)"
        static let fallbackDefaultAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_5 like Mac OS X) AppleWebKit/\(fallbackWekKitVersion) (KHTML, like Gecko) Mobile/15E148"
        static let desktopPrefixComponent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15)"
        static let fallbackVersionComponent = "Version/13.1.1"
        // swiftlint:enable line_length
    }

メモ

  • customUserAgentはそれぞれのアプリで基本的には、コード内でリテラルで指定している
  • AppleWebKit, Safari のバージョンもコード内で固定値で記述されている
    • iOSAPI で取得できる内容だと思ったが、そうではないようだ
  • Chrome は CriOS, Firefox は FxiOS, DuckDuckGoDuckDuckGo がそれぞれ設定される。

Xcode 12.0 beta 2 (12A6163b) に含まれる Framework

Xcode 12.0 beta 2 (12A6163b) の iOS platform の Frameworks の中をみると

$ cd 
/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks

$ ls
ARKit.framework
AVFoundation.framework
AVKit.framework
Accelerate.framework
Accessibility.framework
Accounts.framework
AdSupport.framework
AddressBook.framework
AddressBookUI.framework
AppClip.framework
AppTrackingTransparency.framework
AssetsLibrary.framework
AudioToolbox.framework
AudioUnit.framework
AuthenticationServices.framework
AutomaticAssessmentConfiguration.framework
BackgroundTasks.framework
BusinessChat.framework
CFNetwork.framework
CallKit.framework
CarPlay.framework
ClassKit.framework
ClockKit.framework
CloudKit.framework
Combine.framework
Contacts.framework
ContactsUI.framework
CoreAudio.framework
CoreAudioKit.framework
CoreAudioTypes.framework
CoreBluetooth.framework
CoreData.framework
CoreFoundation.framework
CoreGraphics.framework
CoreHaptics.framework
CoreImage.framework
CoreLocation.framework
CoreMIDI.framework
CoreML.framework
CoreMedia.framework
CoreMotion.framework
CoreNFC.framework
CoreServices.framework
CoreSpotlight.framework
CoreTelephony.framework
CoreText.framework
CoreVideo.framework
CryptoKit.framework
CryptoTokenKit.framework
DeveloperToolsSupport.framework
DeviceCheck.framework
EventKit.framework
EventKitUI.framework
ExposureNotification.framework
ExternalAccessory.framework
FileProvider.framework
FileProviderUI.framework
Foundation.framework
GLKit.framework
GSS.framework
GameController.framework
GameKit.framework
GameplayKit.framework
HealthKit.framework
HealthKitUI.framework
HomeKit.framework
IOKit.framework
IOSurface.framework
IdentityLookup.framework
IdentityLookupUI.framework
ImageCaptureCore.framework
ImageIO.framework
Intents.framework
IntentsUI.framework
JavaScriptCore.framework
LinkPresentation.framework
LocalAuthentication.framework
MLCompute.framework
MapKit.framework
MediaAccessibility.framework
MediaPlayer.framework
MediaSetup.framework
MediaToolbox.framework
MessageUI.framework
Messages.framework
Metal.framework
MetalKit.framework
MetalPerformanceShaders.framework
MetalPerformanceShadersGraph.framework
MetricKit.framework
MobileCoreServices.framework
ModelIO.framework
MultipeerConnectivity.framework
NaturalLanguage.framework
NearbyInteraction.framework
Network.framework
NetworkExtension.framework
NewsstandKit.framework
NotificationCenter.framework
OSLog.framework
OpenAL.framework
OpenGLES.framework
PDFKit.framework
PassKit.framework
PencilKit.framework
Photos.framework
PhotosUI.framework
PushKit.framework
QuartzCore.framework
QuickLook.framework
QuickLookThumbnailing.framework
RealityKit.framework
ReplayKit.framework
SafariServices.framework
SceneKit.framework
ScreenTime.framework
Security.framework
SensorKit.framework
Social.framework
SoundAnalysis.framework
Speech.framework
SpriteKit.framework
StoreKit.framework
SwiftUI.framework
SystemConfiguration.framework
Twitter.framework
UIKit.framework
UniformTypeIdentifiers.framework
UserNotifications.framework
UserNotificationsUI.framework
VideoSubscriberAccount.framework
VideoToolbox.framework
Vision.framework
VisionKit.framework
WatchConnectivity.framework
WebKit.framework
WidgetKit.framework
_AVKit_SwiftUI.framework
_HomeKit_SwiftUI.framework
_MapKit_SwiftUI.framework
_QuickLook_SwiftUI.framework
_SceneKit_SwiftUI.framework
_SpriteKit_SwiftUI.framework
iAd.framework

という感じだった。

AppClips や ScreenTime など iOS 14 のあたらしい framework は含まれているが、Apple Archive はなかった。

macOS Big Sur の framework はこんな感じ。こちらも AppleArchive はない。

$ cd /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/iOSSupport/System/Library/Frameworks 

$ ls
ARKit.framework
AVKit.framework
AddressBook.framework
AddressBookUI.framework
AssetsLibrary.framework
AuthenticationServices.framework
BusinessChat.framework
CarPlay.framework
ContactsUI.framework
CoreAudioKit.framework
CoreNFC.framework
EventKitUI.framework
GameController.framework
GameKit.framework
GameplayKit.framework
HealthKit.framework
HealthKitUI.framework
HomeKit.framework
IdentityLookupUI.framework
IntentsUI.framework
JavaScriptCore.framework
LinkPresentation.framework
MapKit.framework
MediaPlayer.framework
MessageUI.framework
Messages.framework
MetalKit.framework
MobileCoreServices.framework
MultipeerConnectivity.framework
NetworkExtensioniOSSupport.framework
NewsstandKit.framework
OpenAL.framework
PDFKit.framework
PassKit.framework
PencilKit.framework
PhotosUI.framework
QuickLook.framework
RealityKit.framework
ReplayKit.framework
SafariServices.framework
SceneKit.framework
ScreenTime.framework
Social.framework
SpriteKit.framework
StoreKit.framework
SwiftUI.framework
Twitter.framework
UIKit.framework
UserNotificationsUI.framework
VisionKit.framework
WatchConnectivity.framework
WebKit.framework
WidgetKit.framework
_AVKit_SwiftUI.framework
_MapKit_SwiftUI.framework
_SpriteKit_SwiftUI.framework
iAd.framework

本『リーダブルコード』の感想

原題は

The Art of Readable Code: Simple and Practical Techniques for Writing Better Code

である。

邦題では「リーダブルコード」であるが、原題では「The Art of Readable Codes」なので本来は Art の意味もタイトルに含まれている。

その言葉の意味を考えると、これは内容的には、コーディングに関しての Art, 芸術に関する本である。

基本的なところだと、たとえば命名や改行の仕方で、そのコードの読みやすさというは変わる。(命名が適切であり、役割を簡潔に示せているか? 改行された位置により処理が一つのブロックであることを示せているか?)

残されたコードが読みやすく、理解しやすく、あとで読む人(それはすっかりコーディングした当時のことを忘れた自分かもしれない)がストレスなく開発できるかどうか?

そういったコードを通した情報伝達の精度をいかによくするか、という芸術だ。

逆に、この情報伝達がうまくできていないと、コードというダンジョンを、頼りない松明片手に探索する考古学をしなければならない。 (そして運が悪ければダンジョンから出れずに、壁に「yyyy/MM/dd 私は解析に失敗した。ここに眠る ◯◯」とコメントする羽目になる)


本は 200 ページくらいで読みやすい。

「こうしたらコードが読みやすくなる(Readable)」という tips は、自分でコーディングしたり、ほかの人のコードを読んだりして無意識に獲得していくものも多いが (本を読んで、これはやっているなぁ、、という部分もある)、文書にして明文化したことにより、それらが共通知識として残るので価値がある。

個人的には範囲を示すときに start/stop は包括的で、begin/end は終端が排他的なものに使うというのが、なるほどなあ、と思った。

たとえば

2020/01/01 の1日を範囲としたい場合

  • start 2020/01/01 00:00:00
  • stop 2020/01/01 23:59:59

ではなく

  • begin 2020/01/01 00:00:00
  • end 2020/01/02 00:00:00

とできるし、後者の方が自然である。

前者だと厳密に1日の終わりを示すことは難しい。なぜなら 59 秒は1日の終わりでなく、まだ1秒あるからだ。(さらに 999... とミリ秒単位の指定を続けると精度はあげることはできるが、、)

AppStoreConnect での米国輸出規制の暗号に関する質問をスキップする

免除される暗号のみを使用しているのに App Store Connect でアップロードごとに毎回、暗号に関する質問を答えるのが面倒。

その場合は、

Info.plist に以下を追加する。

    <key>ITSAppUsesNonExemptEncryption</key>
    <false/>

これでアプリには免除されていない暗号を使用していない、とマークされるので暗号に関する質問をスキップできる。
(もし、true にした場合はこの逆で、免除されていない暗号を"使用している"、とマークされる)

https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption


そもそもなぜアプリを公開する時に、暗号に関する確認があるのか?

暗号利用環境に関する動向調査(独立行政法人 情報処理推進機構)

アメリカに限らずに「暗号」というは、国家の安全保障・戦略にかかわるものなので、その技術は国外に出て(輸出)は困る、ということらしい。(もちろん、普及している標準的な暗号技術はその対象にはならない)

そして、AppStore のアプリはアメリカからの配信(輸出)になるので、米国輸出管理法の規制の対象になる、ということみたい。

iOS 14 の App Clips について

iOS 14 の App Clips に関して調べたことのまとめ

資料一覧

資料から重要そうなところを抜粋

概要

f:id:daisuke-t-jp:20200918091805p:plain (画像は Apple のもの)

App Clips とはアプリ全体をダウンロードしなくても、ユーザがアプリの必要な機能だけをすばやく取得し、使用できる仕組み。

アプリが、ユーザに「瞬間的なエクスペリエンスを提供」する場合は App Clips の採用を検討する。

たとえばコーヒーショップアプリが AppStore にあるとすると、ユーザはそのアプリを使ってドリンクを注文したり、お気に入りのドリンクを保存したり、クーポンを収集したり、特別オファーを取得したりできる。

対照的に、App Clips はドリンクを注文する機能のみを提供する。ユーザがコーヒーショップを通り過ぎると、システムは Siri からの位置ベースの提案をデバイスに表示する。

ユーザーは提案をタップし、システム提供の App Clips カード(上記の図の中央で示されている)で App Clips を起動し、すぐにドリンクを注文できる。

ユーザはどこから App Clips を見つけて起動できるか?

https://developer.apple.com/documentation/app_clips/developing_a_great_app_clip

ユーザが App Clips を見つけて、起動する方法は以下がある。

  • 物理的な場所での NFC タグまたはビジュアルコードのスキャン
  • Siri から場所ベースの候補をタップする
  • Map アプリでリンクをタップする
  • Webサイトでスマートアプリバナーをタップする
  • メッセージアプリで誰かが共有したリンクをタップする

App Clip Codes

https://developer.apple.com/app-clips/

App Clips にアクセスするためのビジュアルコード。

見た目も美しく、はっきりしている、ユーザが App Clips を見つけるための最良の方法となる。

この App Clips のコードを作成するツールは 2020年後半に提供される予定。

優れた App Clips の設計

https://developer.apple.com/design/human-interface-guidelines/app-clips/overview/#designing-a-great-app-clip

  • 必要な機能だけ。ユーザが目の前のタスクを処理することに集中できるように機能は限定する。
  • App Clips のみで価値があり、タスクを完結できるようにする。App Clips をサービスの宣伝に使用しないように。
  • わかりやすく、直線的にタスクを実行できるようにインターフェースを提供する。App Clips にはタブバー、複雑なナビゲーション、設定は含めることはできない。目の前のタスクを簡潔なインターフェースでユーザが実行できるようにする。
  • 小さいほど良い。小さいほどユーザはすばやくダウンロードでき、実行できる。できるかぎり不要なアセット、コードを削除する。
  • Apple Pay のサポートを検討する。支払い情報の入力は、長くて間違いが起こりやすい作業のため Apple Pay が使用できると良い。
  • アカウント作成を要求しない。ユーザがすぐタスクを完了できるように、アカウント作成はタスク完了後にユーザに促す。
  • App Clips からアプリの移行をスムーズにする。アプリ全体をダウンロードすると、以前の App Clips はアプリに置き換えられる。ユーザが App Clips を使用していた時のエクスペリエンスをアプリでも提供する。

App Clips のライフサイクル

https://developer.apple.com/documentation/app_clips#overview

  • ユーザがアプリをインストールしない場合、システムは一定期間操作がないと、 App Clips を自動的に削除する。
  • ユーザが App Clips に対応するアプリをインストールすると、 App Clips がアプリに置き換わる。

App Clips の開発

https://developer.apple.com/documentation/app_clips/creating_an_app_clip

アプリのプロジェクトに App Clips のターゲットを追加する。

App Clips の信頼性の検証をするために Web サーバ構築も必要になる。

考察

以下、個人的な考察。

  • App Clips は一定期間操作されないと、システムにより削除される。
    • この期間が不明だが、App Clips を気にいって繰り返し使うユーザは、App Clips の入手の手間を嫌って、どこかのタイミングで AppStore から完全なアプリを入手することなりそう。
    • App Clips で良い体験をユーザが感じれば、アプリ本体のダウンロードに繋がる。
  • 開発時点で機能が分離できていないコードでは、App Clips 用に機能提供することのコストが大きくなるため、プロジェクトは疎結合の状態が望ましい。