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 用に機能提供することのコストが大きくなるため、プロジェクトは疎結合の状態が望ましい。

iOS / Android 用のローカライズ文字列ファイルを生成するツール「strgen」

iOS / Androidローカライズ文字列ファイルを一括で生成するツールstrgenPython)を作った。

PyPI に登録してあるので、 pip でインストールできます。

概要

多言語情報を記載した CSV と、設定を記載した YAML ファイルを入力として、以下のファイルを出力する。

  • iOS 用ファイル
    • 各言語用の Localized.strings
    • ローカライズのキーが定義されている Swift ファイル(LocalizedStrings.swift
  • Android 用ファイル
    • 各言語用の strings.xml

strgen を使うメリット

一元管理できる

iOS / Androidローカライズファイルのソースが、単一の CSV になり、多言語が一元管理できる。

多言語更新のミスが発生しにくい

ツールで生成したファイルをプロジェクトに反映させるだけなので、ファイル(Localized.strings, strings.xml)を直接編集するのに比べ、ミスが発生しにくい。

iOSローカライズの効率がよくなる

hello というキーでローカライズ文字列が存在する場合、 iOS はキーを文字列で指定してローカライズ文字列を得るので、以下のようになる。

NSLocalizedString("hello", comment: "")

しかし、これは存在していないキーも指定できる問題がある。

たとえば、以下のようにキー指定をミスした場合、ビルド時には分からず、ミスがわかるのは実行時に確認した時だ(ローカライズ文字列が正しく表示されないので)

NSLocalizedString("hellow", comment: "")

この問題の対応として strgen は iOS の多言語ファイルを生成する時に、一緒にキーを列挙した swift ファイル(LocalizedStrings.swit)も作成する。

import Foundation

class LocalizableStrings {

    enum Key: String {
        case hello = "hello"
    }

}

この swift ファイルからキー定義を参照するようにすると、ローカライズのコーディングの際に、ビルド時点でミスに気付けるので、効率がよくなる。

NSLocalizedString(LocalizableStrings.Key.hello.rawValue, comment: "")

strgen を試してみる

サンプルで動作を確認できます。

インストール

まず、 pip で strgen をインストールします。

$ pip install strgen

これで、コマンドラインから strgen を実行できるようになる。

$ which strgen
/usr/local/bin/strgen

サンプルを動作させてみる

  1. プロジェクトを clone します。
    • $ git clone https://github.com/daisuke-t-jp/strgen
  2. カレントディレクトリを clone したプロジェクト内の strgen/sample フォルダに変更します。
  3. 以下のコマンドを実行。
    • $ strgen strgen.yml
  4. build フォルダを確認します。

使い方

そのほかの使い方、YAML ファイルの仕様などは、README を参照してください。

https://github.com/daisuke-t-jp/strgen

Python パッケージ作成に関する情報まとめ

Python パッケージ作成、PyPI(パイ・ピーアイ) への登録などに役立つ情報を、まとめる。

setup.py だけ使用するパターン、setup.cfg も使用するパターン、__init__.py に多くの役割を持たせるパターン/ほとんど役割を持たせないパターン、などパッケージ作成に関してさまざま選択肢があるので、いろいろな例を見ておくと良い。

関連公式ドキュメント

参考情報

参考になる Python パッケージ(OSS

パッケージのインストールテスト

パッケージを作ったらインストールして試してみる

開発用

開発中のパッケージは、こちらの方法でインストールしてテストする。

$ python setup.py develop

# もしくは

$ pip install -e .

配布用

# 配布用パッケージの作成
# これで dist/ 以下にパッケージ(tar.gz)ができる
$ python setup.py sdist

# 配布用パッケージのインストール
$ pip install dist/*

パッケージ情報の確認

$ pip show <パッケージ名> 

アンインストール

$ pip uninstall <パッケージ名>

PyPI への登録

登録するためのツール twine が入っていないならインストールする。

$ pip install twine

登録。

$ twine upload --repository pypi dist/*