iOS でパラパラ漫画のような見た目のインジケーターを作った

everysing というカラオケのアプリで、読み込み中に表示されるインジケーターがパラパラ漫画のようになっていて、キュートな感じがしてよい。(マイクやヘッドフォンのアイコンが表示される)

f:id:daisuke-t-jp:20200610220213g:plain:w250



この UI を再現したく、パラパラとアニメするインジケータを作ってみた。


↓こんな感じに画像を設定すれば、パラパラアニメで表示される。

import ParaParaIndicator


class ViewController: UIViewController {
    
    let paraparaIndicator = ParaParaIndicator()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // Images for flipping 
        paraparaIndicator.images = [
            UIImage(named: "camel")!.withRenderingMode(.alwaysTemplate),
            UIImage(named: "deer")!.withRenderingMode(.alwaysTemplate),
            UIImage(named: "elephant")!.withRenderingMode(.alwaysTemplate),
            UIImage(named: "giraffe")!.withRenderingMode(.alwaysTemplate),
            UIImage(named: "kangaroo")!.withRenderingMode(.alwaysTemplate),
            UIImage(named: "mouse")!.withRenderingMode(.alwaysTemplate),
            UIImage(named: "rhino")!.withRenderingMode(.alwaysTemplate),
        ]
        
        
        // Indicator frame
        paraparaIndicator.frame.size.width = 100
        paraparaIndicator.frame.size.height = 100     
        paraparaIndicator.center = self.view.center
        
        // Tint color
        paraparaIndicator.tintColor = .cyan
        
        // Flip interval
        paraparaIndicator.timeInterval = 0.3
        
        // Hide when stopped
        paraparaIndicator.hidesWhenStopped = true
        
        // Start animation
        paraparaIndicator.startAnimating()
        
        // Stop animation
        // paraparaIndicator.stopAnimating()
        
        self.view.addSubview(paraparaIndicator)
    }
    
}

なお UIActivityIndicatorView Like で扱えるように、以下のメソッドを実装し、インターフェースを似せてある。

  • hideWhenStopped
  • isAnimating
  • startAnimating
  • stopAnimating

macOS 10.15 から有効な EndpointSecurity API(SystemExtension.framework)

https://developer.apple.com/jp/system-extensions/

macOS 10.15 から、従来のカーネル拡張(KEXT)を使わずに、ユーザーランドで動作するソフトでシステムイベントを監視(おそらくイベントの許可/禁止も)できるようになった。



https://developer.apple.com/contact/request/system-extension/

まず Apple に SystemExtension.framework / EndpointSecurity API の使用リクエストを出し、許可される必要がある。


https://developer.apple.com/documentation/endpointsecurity

f:id:daisuke-t-jp:20200531152712p:plain:w400

プロジェクトで Endpoint Security API を使用するには、以下の2つをリンクする。

  • SystemExtension.framwork
  • libEndpointSecurity.tdb


f:id:daisuke-t-jp:20200531152915p:plain:w400

entitlements に com.apple.developer.endpoint-security.client = YES を設定する。

AppleAPI リクエストが許可されていないと、アプリがただしく起動しない。



従来の KEXT を使用してシステム監視(アンチウィルスソフトなど)する場合より SystemExtension.framework を使用するメリット

  • Kernel extension で機能を実現した場合、ソフトはカーネルランドで実行されるため、ソフトの処理が遅いと OS 全体も遅くなってしまうなど OS の安定性に関わる現象が起きる。その分、実装もシビアになる。
    • ユーザランド実行になるとここが、そこまでシビアではなくなる?
  • Kernel extension 由来のセキュリティ問題がある場合、カーネル空間でその問題は発生するので OS デベロッパApple)、ソフトデベロッパの責務の線引きが不明瞭になる。
    • カーネルランド、ユーザランド、それぞれのセキュリティレベルが線引きされた状態で、ソフトウェア開発ができる。



不明な点

https://developer.apple.com/documentation/endpointsecurity/client?language=objc

  • Endpoint Security API のサンプルコードをみると panic() 関数を呼んでいる
    • ユーザランドで動作するソフトだが、作法として kernel panic を発生させて良いのだろうか?
  • Kernel extension ではデバッグをするとき、同じモデル・OS の mac 2台を用意して、片方の mac で panic を発生させて、その状態でもう片方の mac で接続してデバッグ・・・という方法があったが、そのあたりのデバッグ方法もしやすい方に変わっているのだろうか

GitHub Action の Private / Public リポジトリの制限

GitHub Actionsの支払いについて - GitHub ヘルプ

Public リポジトリ

GitHub Actions の制限はない

Private リポジトリ

GitHub Actions の実行時間とストレージに制限がある。

アカウントに存在するすべての Private リポジトリの実行時間、ストレージ使用量の合算が制限の対象。個々のリポジトリごとに制限が計算されるわけではない。

  • Free アカウントの場合
    • ストレージ : 500 MB
    • 実行時間 : 2,000分(月)
  • Pro アカウントの場合
    • ストレージ : 1 GB
    • 実行時間 : 3,000分(月)

Private リポジトリにおける GitHub の Actions の使用状況(利用時間とストレージ使用量)はユーザアカウントから確認できる。

GitHub Actions の使用状況を表示する - GitHub ヘルプ

制限を超えて使用したい場合は、料金支払いが必要になる。

まとめ

  • Public リポジトリ
    • Action の実行時間、ストレージは気にしなくて良い
  • Private リポジトリ
    • Action の実行時間、ストレージを気にする必要がある
    • 実行時間を気にするならば Action 内の処理で適切にタイムアウト時間を設定する必要もある
    • タイムアウト待ち時間で、実行時間が思わず消費される可能性があるからだ

GitHub ワークフローで環境変数、シークレットな情報を使用する

これは 2020/05 時点の情報をもとにした記事なので、最新では事情が変わっている可能性を留意すること。

ワークフロー内で使用できる環境変数

環境変数の利用 - GitHub ヘルプ

たとえば GITHUB_REPOSITORY(所有者とリポジトリの名前 octocat/Hello-World など)がある。

API キーなどシークレット情報の扱い

暗号化されたシークレットの作成と保存 - GitHub ヘルプ

ある Web API を呼び出すのに必要な API キーがあったとして、それをコード中にそのまま記載してしまうと API キーの漏洩になってしまう。これを避けるため、シークレット情報は GitHub に設定して秘匿して使用することができる。

シークレット情報は Organization / Repository ごとに設定することができる。

設定したシークレット情報は、以下のようにワークフローで参照することができる。

steps:
  - shell: bash
    env:
      SUPER_SECRET: ${{ secrets.SuperSecret }}
    run: |
      example-command "$SUPER_SECRET"

GitHub ワークフローを使って、プロジェクトを定期実行し、成果物をデプロイする(Python)

GitHub ワークフローを使ってプロジェクトの定期実行→成果物をデプロイする方法を調べたのでまとめる。

これは 2020/05 時点の情報をもとにした記事なので、最新では事情が変わっている可能性を留意すること。

目的

  • GitHub ワークフロー / アクションの意味を理解
  • GitHub リポジトリのプロジェクトを定期実行する(Python
    • 定期実行時の成果物をデプロイする

GitHub ワークフロー / アクションの意味

ワークフロー

https://help.github.com/ja/actions/configuring-and-managing-workflows/configuring-a-workflow#about-workflows

ワークフローとは、GitHubで任意のプロジェクトをビルド、テスト、パッケージ、リリース、またはデプロイするためにリポジトリで設定できる、カスタムの自動プロセスです。 ワークフローを使用すると、豊富なツールとサービスでソフトウェア開発のライフサイクルを自動化できます。

「ワークフロー」に記述した内容で、ビルドやテスト、リリース、デプロイが自動化できるようになる。

ワークフロー自体は .github/workflow の下に yml ファイルで記述する。

アクション

https://help.github.com/ja/actions/getting-started-with-github-actions/about-github-actions

GitHub Actionsは、コードを保存するのと同じ場所でソフトウェア開発のワークフローを自動化し、プルリクエストやIssueで協力することを支援します。 個々のタスクを書き、アクションを呼び出し、それらを組み合わせてカスタムのワークフローを作成できます。

「アクション」は「ワークフロー」を構成する単位となる。

どんなアクションがあるかは Markerplace で確認できる。

GitHub Marketplace · Actions to improve your workflow · GitHub

たとえば git リポジトリからチェックアウトするアクション「Checkout v2」がある。

Checkout · Actions · GitHub Marketplace · GitHub

この Checkout をワークフロー内で使用するには、以下のように記述する。

name: workflow-name

# リポジトリに Push したタイミングでワークフローを実行する
on: [push]


jobs:
  build:
    name: workflow-job
    runs-on: ubuntu-latest

    steps:
    # リポジトリをチェックアウトする
    - name: Checkout
      uses: actions/checkout@v2

    # チェックアウトされたリポジトリで、ビルド、テスト、リリース、デプロイ、などをする。

その他

GitHub リポジトリのプロジェクトを定期実行する(Python

今回のサンプル

GitHub - daisuke-t-jp/jma-hazard-xml2json

今回作成したリポジトリはこれになる。(Python プロジェクト)

この Python スクリプト気象庁で提供されている災害情報(XML)を取得して JSON フォーマットに変換して、ファイルに保存する、ということやっている。

気象庁 | 気象庁防災情報XMLフォーマット形式電文(PULL型)

requirements.txt

Python スクリプトに必要な外部パッケージを requirements.txt ファイルを作成し、そこに記載する。

今回は XMLPython の辞書として扱うために xmltodict を requirements.txt に追加する。

xmltodict

ワークフローを作成する

ローカルでスクリプトの動作が確認出来たら GitHub ワークフローを作成して GitHub 上でも動作するようにする。

今回のワークフローは以下の機能にする

  • 定期実行したい(10分に1回)
  • 成果物である JSON ファイルを保存したい

yaml で記載したワークフローは以下になる。

# ワークフローの名前
name: cron-deploy

# ワークフローの実行タイミング
# この場合は 10 分に 1 回
#
# cron のフォーマットは以下が参考になる
# http://manpages.ubuntu.com/manpages/trusty/ja/man5/crontab.5.html
on:
  schedule:
    - cron:  '*/10 * * * *'

# ジョブの記述
jobs:
  # job_id は "build"
  build:
    # ジョブの名前(GitHub 上で表示される)
    name: deploy
    # Ubuntu で実行する
    # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
    runs-on: ubuntu-latest

    # ジョブのステップ
    steps:
    - name: Checkout
      # チェックアウト
      uses: actions/checkout@v2
    - name: Setup Python 3.8
      # Python のセットアップ
      uses: actions/setup-python@v1
      with:
        python-version: 3.8
    - name: Install dependencies
      # Python 外部パッケージをインストール
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run script
      # Python スクリプトを実行する
      run: |
        python jma-hazard-xml2json.py
    - name: Deploy
      # gh-pages ブランチに成果物をデプロイする
      uses: peaceiris/actions-gh-pages@v3
      with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./json


このワークフローを GitHub にコミットすると、ただしく10分ごとに gh-pages ブランチにデプロイされることが確認出来た。(前のファイルは上書きされる)

f:id:daisuke-t-jp:20200918091413p:plain:w500

DuckDuckGo iOS アプリの「Find In Page(ページ内検索)」で入力文字の候補が選択できない問題を修正

DuckDuckGo Privacy Browser

DuckDuckGo Privacy Browser

DuckDuckGoiOS アプリで「Find In Page(ページ内検索)」で日本語が入力できない現象があったので、原因を調べて修正した。

DuckDuckGoiOS / Android 共にオープンソースなので、第三者でも実際にコードを動かして確認することができる。

https://github.com/duckduckgo/


問題の原因は、ページ内検索をする時に

  1. TextField に入力された文字で「ページ内検索」を実行する
  2. ページ内検索の結果を WebView に反映
  3. TextField に入力された文字を、再度 TextField に設定する(表示された検索結果と入力された文字を同期、確定)

ということになっていたので、このシーケンスでは、たとえば「鴨」をページ内検索したい場合

  1. 「か」と入力する。「か」でページ内検索を実行する
  2. 「か」のページ内検索を WebView に反映
  3. TextField に「か」を設定する

になるため

  • 「か」の候補である「鴨」を選択できない
  • 「かも」と続けて入力した時の候補である「鴨」を選択できない(「か」「も」でそれぞれ確定されるため)

という現象が発生していた。

最初は日本語の問題に見えていたが、強制的に入力文字が「確定」されてしまうため、「候補」が選べない問題だった。(つまり日本語以外にもこの問題は影響している)


https://github.com/duckduckgo/iOS/pull/608

この問題の修正を PullRequest を送って Merge された。


f:id:daisuke-t-jp:20200918091211p:plain:w600

修正後
AppStore のレビューでこの改善に気づいている人がいて、よかったなと。。

iOS の TensorFlow サンプルを試してみる

TensorFlow(テンソルフロー) や iOS でのサンプル実行の方法を調べたメモ。



https://www.tensorflow.org/lite?hl=ja

iOS で TensorFlow を動かすには TensorFlow Lite を使用すると良い。
TensorFlow Lite はモバイルや組み込みデバイス向けにチューニングされた TensorFlow のバージョンである。

CocoaPods で導入するにはこうする。

platform :ios, '13.0'

target 'TargetName' do
  use_frameworks!

  pod 'TensorFlowLiteSwift'

end



https://www.tensorflow.org/lite/examples?hl=ja

実際にどんなことができるか、どんな動きになるかを試したい場合は、ここに各パターン(画像分類やセグメンテーションなど)のサンプルアプリのリンク(GitHub へのリンク)がある。

しかし GitHub 上にはあるが、上記のサンプルのコレクションページにはない物もあるので GitHub の examples を直接見た方が良いだろう。

https://github.com/tensorflow/examples/tree/master/lite/examples



AI / 機械学習というジャンルでは「推論結果を出したい」の前に、その推論に使用するモデルを用意する必要があるので骨が折れそうなイメージがあるが TensorFlow では、学習ずみのモデルを公開しているのでそれを使用することができる。

https://tfhub.dev/

ちなみに Apple の CoreML で TensorFlow のモデルを使用したい場合は、 CoreML 形式にコンバートされたモデルが Apple のサイトにあるので、ダウンロードできる。

https://developer.apple.com/machine-learning/models/



実際にサンプルを試すには各サンプルのルートで pod install してからワークスペースを実行するだけで OK。

たとえば以下のような物がある。


ImageSegmentation

画像に含まれる物体を分解できる。 人と背景や犬などが画像上のどのピクセルなのかを特定できる。

f:id:daisuke-t-jp:20200918090303p:plain:w250



StyleTransfer

ソースとなる画像にスタイルを適用することができる。

たとえば ソースの写真がこれで
f:id:daisuke-t-jp:20200515195125p:plain:w200


適用するスタイル画像にこれを選ぶと(ゴッホの星月夜)
f:id:daisuke-t-jp:20200515195131p:plain:w200


スタイル適用された画像はこう出力される。(ちょっとゴッホぽくなった)
f:id:daisuke-t-jp:20200515195137p:plain:w200



という感じにサンプルとコードを見ている中でいくつか気になる点が出てきたので、プルリクエストを送ってみた。

https://github.com/tensorflow/examples/pull/214 https://github.com/tensorflow/examples/pull/215 https://github.com/tensorflow/examples/pull/216 https://github.com/tensorflow/examples/pull/217

内容は、ダークモード時に UI が一部見えなくなる、不要なメモリ確保をしている、など。

無事プルリクエストはマージされたので、今後サンプルから学ぶ人のちょっとした足しになれば、いいなぁ、、、と。

それと、「不要なメモリ確保を削除」の PR が受け入れられたということは自分の TensorFlow Lite の認識(の一部)が、正解とズレていなかった証明になるので、安心した。。