プログラマ英語学習日記

プログラミングと英語学習のまとめなど

WWDC 2017 Customized Loading in WKWebView まとめ

WKWebViewの新機能に関する話

動画はこちら

Customized Loading in WKWebView

  • WebContentsを表示するにはいくつかの方法がある

    • WKWebView
    • SFSafariController
  • SFSafariControllerを使えばボタン等をOS側で処理してくれて楽

    • ただし画面の一部に組み込みたいときには使えない
    • その場合は WKWebView
  • WKWebViewのプロセスはAppと分離される

    • セキュリティ上の理由など
    • それによる制約も強かった
  • 今回は開発者から要望の多かった機能を3つ実装した

    • Manage Cookies
    • Filter unwatned content
    • Provide custom resources

Manage Cookies

  • WKHttpCookieStore
    • クッキーを個別に追加/削除できる
    • すべてのクッキーにアクセス可能
    • クッキーの変更を監視可能
//GET
let cookieStore = webView.configuration.websiteDataStore.httpCookieStore;
cookieStore.getAllCookies() {(cookies) in 
}

//ADD
let cookie = HTTPCookie(properties: [
   HTTPCookiePropertyKey.domain: "canineschool.org", HTTPCookiePropertyKey.path: "/",
   HTTPCookiePropertyKey.secure: true,
   HTTPCookiePropertyKey.name: "LoginSessionID", HTTPCookiePropertyKey.value: "5bd9d8cabc46041579a311230539b8d1"])
cookieStore.setCookie(cookie!) {
    webView.load(loggedInURLRequest)
}

//remove
cookieStore.delete(cookie!) {
    webView.load(loggedOutURLRequest)
}

Filtering unwanted content

  • 見せたくないコンテンツをブロックできる

  • WKContentRuleList

    • SafariAdBlock系Extensionと同じ書き方
      • 読み込みのブロック
      • 見えなくする
      • SSLSSLに切り替える
  • JSONで記述、コード中でロードしWebViewに適用させる

//json-file in Bundle
[{
  "trigger": {
  "url-filter": ".*" },
  "action": {
  "type": "make-https"
} }]

//swift-code
//compile
let jsonString = loadJSONFromBundle()
WKContentRuleListStore.default().compileContentRuleList(
    forIdentifier: "ContentBlockingRules",
    encodedContentRuleList: jsonString
  ) { (contentRuleList, error) in
     if let error = error {
      return
     }
    createWebViewWithContentRuleList(ruleList!)
  }

//access rules
WKContentRuleListStore.default()
  .lookUpContentRuleList(
    forIdentifier: "ContentBlockingRules") { (contentRuleList, error) in
      //取得した情報を使う部分
   }

//apply
let configuration = WKWebViewConfiguration()
configuration.userContentController.add(contentRuleList)

Provide custom resources

  • リソース読み込みをアプリで処理できるようになる
  • ただしカスタムURLスキームに限る?)
  • 将来的にも問題ないようにカスタムスキームを設定するように

    • Bad : local
    • Good : [AppName]-local
  • WKURLSchemeHandler を使う

class MyCustomSchemeHandler : NSObject, WKURLSchemeHandler {
  func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {}
  func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {}
}

let configuration = WKWebViewConfiguration()
configuration.setURLSchemeHandler(MyCustomSchemeHandler(), forURLScheme: “custom-scheme")
  • WKURLSchemeTask に結果を渡す
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
  //Bundleからデータを読み込み、対応するResponseクラスを作成し渡す
  let resourceData : Data = loadDataFromBundle()

  let response = URLResponse(
    url: urlSchemeTask.request.url,
    mimeType: "text/html",  //mimeType必須
    expectedContentLength : resourceData.count,
    textEncodingName : nil
  )

  urlSchemeTask.didReceive(response)
  urlSchemeTask.didReceive(resourceData)
  urlSchemeTask.didFinish()
}

Kotlin-AltJSの初心者向けTips&インプレ

AltJS-Kotlin

Android公式言語採用で一気にメジャーになったKotlinですが、実は JavaScriptに変換できます。いつからかは知りませんが、かなり当初からできたはず(1.0より前からあったはず)。

しかしマイナーすぎてあまり使っている人を見たことがありません。ネットの記事も少なめ。

軽くですが触った範囲でのTips&を記述していきます。

Tips

最初にコツ&ハマりポイントを。あまり出回ってないと思いますので

イベントハンドラ

DOMのクリックイベント等をWindowにイベントハンドラを追加したいことはよくあると思います。こんなやつです

<script>
function onLinkClick(param) {
}
</script>
<a href="javascript:onLinkClick('PARAMETER')">LINK</a>

で、このonLinkClick相当をKotlinにしたい。コイツが地味にkotlinだと面倒、というより最初やり方がわかりませんでした。結論からいうと asDynamic() を使います

fun main(args: Array<String>) {
   window.asDynamic().onLinkClick = {param : String -> 
      //do anything
   }
}

しかもmain関数内。最初はグローバル関数として記述すると思ってました。

jsの感覚で window.onLinkClick = と書くとビルド通らないので注意してください。

console.log

コイツも地味にはまりました。 print ですね。

一番ハマったのが、 改行がくるまでログに出力されない ことです。

このため、 print 単体では出力されません。そのあとの println で一気に出力されます。

これ結構ハマったので注意してください。

もいっこ、Object詳細をログに出してくれません。以下のようなパラメータをjs/kotlinに渡してログ出力させると…?

<a href="javascript:onLinkClick({text: 'I love Kotlin!'})">LINK</a>

f:id:nexus1:20170803230930p:plain

上がconsole.log、下がkotlinのprintlnです。見ての通り、オブジェクトの詳細がわかりません

なので kotlinでも console.log を使ったほうがいい です。printを使うメリットが分かりませんでした。記法が少し短い程度でしょうか? 基本はconsole.logがいいと思います。

js連携

jsのコードと連携する場合、external で定義ファイルを記述します。Typescriptでいう、 .d.ts ですね。

ためしに以下のようなjs用をKotlinで使おうとすると…?

//myMathをモジュール的に使ってるイメージ
myMath = {}
//モジュール内関数
myMath.sin = function(degree) {
    return Math.sin(degree * Math.PI / 180.0)
}
//クラス
myMath.Point = function(x, y) {
    self = this;
    this.x = x;
    this.y = y;
    this.abs = function() {
        return Math.sqrt(self.x * self.x + self.y * self.y);
    }
}

Kotlin用の定義ファイルはこうなります。

//モジュールはclassで包む。もっといい方法あるかも
external class myMath {
    //myMath.sinはstatic関数相当なので、companion objectで記述
    companion object {
        fun sin(degree: Double) : Double
    }
    //内部クラスはそのまま。ただしコンストラクタは constructor に限定される模様
    class Point {
        constructor(x: Double, y: Double)
        fun abs() : Double
    }
}
//ためしに使う
fun main() {
   println(myMath.sin(90.0)) //1
   val p = myMath.Point(1.0, 2.0)
   println(p.abs());  //2.23...
}

こんな感じ。正直自分も把握しきってないです(触り始めたばかりですし)

適当に書いたあと出力JSファイルをみて、それが意図した結果になってるか確認してることが多いです。.d.ts があるならそれベースでコンバートするのが楽でしょう。

小数はDoubleで

個人的にですが、小数は全部Doubleで扱ったほうが楽でした。Floatだといちいち末尾に f が必要でちょっと面倒。またKotlinはこの辺結構厳格なので、Double/Floatが入り乱れるとキャストが面倒です。

その上、jsにコンバートされたら全部number です。Java変換なら意味ありますがjs変換ではDouble/Floatを使い分けるメリットがありません。そんなわけで記法が楽な Doubleに統一がベスト と判断しました。


IntとLongについてもほぼ同等です…が、こっちは気分で分けています。

ユーザIDなど64bitなものは明示的にLongにして、それ以外はIntとしてます。 普段からLongは逆に面倒 なので。

またLongはID程度でしか使わないので加減算などをしないんですよね。よってLongだから扱いが面倒ということはないです(時刻はDoubleで扱う)

こちらもコンバートされた結果はIntだろうがLongだろうが一緒のはず。

ここは地味にKotlinToJSで使いづらい部分ですね。Typescriptと違って整数と小数を分けれるのはいいと思うのですが、それ以上は過剰機能という印象です。

一致しないオブジェクトを渡したとき

たとえば次のような定義ファイルを記述したとします。

external class User {
   var id : Long
   var name : String
}

idとnameがあるユーザ、よくあるパターンですね。これに {name: "NAME"} というオブジェクト、idが不足してるオブジェクトを渡した場合…?

//htmlが、href='javascript:onClick({name: "NAME"})'

window.asDynamyc().onClick(user : User) {
   println(user.id) //undefined
   println(user.name) //NAME
}

と、undefinedになります。当然といえば当然ですが。このへんは型に厳しいKotlinといえど注意する必要があるので気をつけましょう。

定義ファイルを全部をNullableとして定義するのも手ですが、コードがかなりめんどくさくなるのがネックです。 このへんはバランスをみて、になるかと思います。

インプレッション

まだ軽く触り始めたばかりですが、インプレッションを

Cons

  • 型があるのはやはりいい。TypeScriptより強力!
  • 言語仕様はTypeScriptより組みやすい気がする
  • IntelliJが賢い。VSCodeより補完がきく印象
  • IntelliJが一つのJSファイルとして出力してくれるのでwebpack等を記述する必要がない。楽。
    • どうもmain関数がある部分をエントリーポイントとして出力する模様

Pros

  • HTML/JSとの連携が難しい。ここはTypeScriptに軍配があがる
  • 型定義ファイルがほとんどない。自作する覚悟が必要。
  • 標準クラスの設計がJava基準で、JSの標準APIとは相性が悪い
  • 規模が大きい場合フレームワークをどうするか。
    • React/Angular/Vueのどれとも相性悪そう
    • フルスクラッチでいく覚悟が必要かもしれない

個人的印象ですが、 Kotlin内で閉じれるならすごくいい。閉じれない(外部JSと連携必須)ならTypeScript ということろでしょうか。

もちろんKotlin用外部ライブラリも少ないです。Androidのものなんて当然使いまわせません。

それに加え規模が大きくなるとReactやAngularは使いたくなるでしょう…ここは鬼門ですね。 小規模なプロジェクト以外は導入が難しそうなのに、小規模なプロジェクトで厳しい型チェックをするメリットは薄いというジレンマ。主力SPA系フレームワークとの相性悪そうなのは痛い。かろうじてAngular?

ちょっと面白いので趣味開発はありですが、業務採用となるとかなりハードルたかそうです。

こんな記事を書いておいてなんですが、個人的にはKotlinToJSよりELMのほうが面白いと思いますw