Posted at

Dart1.6の主な変更点

More than 5 years have passed since last update.

もちろんご存知かとは思いますが昨日8月27日にDartのバージョン1.6が出ました。目玉は「Deferred loading」ということで、来るasync/await実装に向けて非同期処理まわりの改善の第一歩ということでしょうかね?

1.6アップデートの公式ブログ記事はこちらです。以降リリースノートの内容についてサンプルコードと一緒に解説していきます。


ライブラリ読み込みの遅延実行をサポート

今回の目玉である deferred loading は外部ライブラリのインポートを必要な時まで遅延させ、実行時のロード時間を短縮させることが可能になる機能です。公式の解説記事はこちらです。

遅延読み込みされる側のライブラリには何も変更は必要ありません。サンプルではシンプルなhello worldのライブラリを定義しています。


hello.dart

library deferred_loading_example.hello;

printGreeting() {
print('Hello World, from the deferred library!');
}


読み込み側ではまず、import文にdeferredを付け、名前付きでインポートします。


main.dart

import 'package:deferred_loading_example/hello.dart' deferred as hello;


次に、ライブラリを使う時にhello.loadLibraryを実行します。


main.dart

import 'package:deferred_loading_example/hello.dart' deferred as hello;

main() {
hello.loadLibrary().then((_) => hello.printGreeting());
}


loadLibrary()Futureを返すので、読み込みが終わり次第thenでライブラリを使います。


"Deferred loading"のポイント


  • Webアプリでもコマンドラインアプリでも使える


  • loadLibrary()は一度呼ばれるとキャッシュされるので2回目からは速い

  • 読み込み前のライブラリのクラスや、定数を使おうとするとに未定義なる

  • dart2jsするとライブラリごとに別のjsファイルにビルドされ、遅延読込される

  • Polymer.dartはまだ対応してないので遅い

以上です。loadLibrary()されるまでは読み込まれませんが、エディタ上では参照されているのでうっかりインスタンス作ったり、定数呼び出したりしないように気をつけましょう。


Pattern#allMatches()に"start"パラメータ追加

PatternクラスのメソッドallMatchesにオプショナル引数としてint型のstartが追加されました。Patternの主な実装であるRegExpクラスを例に違いを示したのが次のコードです。


pattern_test.dart

  var regex = new RegExp(r"[a-z]+[0-9]+[a-z]+");

var sample = "abc0123def";

test("Pattern.allMatch without [start]", () {
expect(regex.allMatches(sample).isNotEmpty, isTrue);
});

test("Pattern.allMatch with [start]", () {
expect(regex.allMatches(sample, 4).isNotEmpty, isFalse);
});


startはマッチ検索のスタートインデックスになり、指定しない場合は0で、先頭からの検索になります。今までなかったのが不思議な引数でしたが、これで正規表現周りの実装が楽になるかもしれません。また、この変更に関しては既存のコードへの影響はありません。


Durationクラスに負の値関連の機能追加

Durationクラスは時間の長さ、期間を表すためのクラスです。1.5以前でも四則計算は可能で、負の値を取りうることがありましたが特にドキュメントに記述はありませんでした。1.6からは積極的に負の値を取る状態を利用するようにしたようで、負の状態であることを取得するisNegativeや絶対値を取得するabs()、符号を反転させる-演算子がサポートされました。


duration_test.dart

  var duration1 = new Duration(hours:1); // 1 hours (short)

var duration2 = new Duration(hours:2); // 2 hours (long)

var subtruct = duration1 - duration2; // 1 - 2 hours (-1 hours)

test("isNegative", () {
expect(subtruct.isNegative, isTrue);
});

test("abs()", () {
expect(subtruct.abs(), equals(duration1));
});

test("inverse", () {
expect(-subtruct, equals(duration1));
});


数のようにDurationが扱えるようになったので日付の計算なんかに便利になったのかもしれません。これも既存のコードに影響はありません。


FormatExceptionにsourceとoffset引数追加

まずは1.5のFormatExceptionのソースを御覧ください

https://code.google.com/p/dart/source/browse/branches/1.5/dart/sdk/lib/core/exceptions.dart#42

次に1.6のソースを御覧ください

https://code.google.com/p/dart/source/browse/branches/1.6/dart/sdk/lib/core/exceptions.dart#42

めちゃくちゃにソースが長くなってます。今まではmessage引数しか渡せず、エラー表示が貧相だったFormatExceptionですが、新たにsourceoffsetが追加され、表現力のあるエラー表示になりました。試しに不正なjson文字列をエンコードさせてみます。


exception_test.dart

    var wrongJson = "{ key:value }";

try {
var decode = JSON.decode(wrongJson);
expect(0, 1);
} catch(e, s) {
print(e);
}

これを実行して出力されるのがこんな感じです。


stdout

FormatException: Unexpected character (at character 3)

{ key:value }
^

例外が発生した文字列と、その発生位置まで出力されて、デバッグしやすくなりました。自分でFormatExceptionを投げるときもこれを利用するようにしましょう。


Uriクラスのセキュリティ方面のバグ修正と機能追加

Uriに、不正なUriの使用を制限する目的のバグ修正がなされました。詳細の原文はこちらですが、簡単にまとめると


  • 不正なUriをパースしなくなった

  • hasAuthority/hasQuery/hasFragmentが追加された

  • その他パーサーの改善

となっています。が、実際に試した所これでいいのか?という感じです


uri_test.dart

  var wrongUri = Uri.parse("foo:///?#");

var correctUri = Uri.parse("http://www.google.com/");

test("Wrong Uri toString()", () {
// ~1.5: toString() outputs parsed "foo:/"
expect(wrongUri.toString(), equals("foo:///?#"));
});

test("hasAuthority", () {
expect(wrongUri.hasAuthority, isTrue);
expect(correctUri.hasAuthority, isTrue);
});

test("hasQuery", () {
expect(wrongUri.hasQuery, isTrue); // Why is ?# detected as query...
expect(correctUri.hasQuery, isFalse);
});

test("hasFragment", () {
expect(wrongUri.hasFragment, isTrue); // Why is ?# detected as fragment...
expect(correctUri.hasFragment, isFalse);
});


hasQueryhasFragmentに関してはUriの仕様的にこれでいいのか不安なんですが、Uriに詳しい人コメントで教えてください。

また、バグ修正だけでなくUriに新しくreplaceメソッドが追加されています。Uriの各要素を置換して別のUriを作ることが出来ます。文字列操作をしなくてよくなるのでとても便利なメソッドです。


uri_test.dart

  var origin = Uri.parse("http://hogehoge.net:80/A/B/laco#hoge");

test("replace", () {
var replaced = origin.replace(
host: "lacolaco.net",
port: "8080",
path: "B/A/laco",
fragment: "fuga"
);
expect(replaced.toString(), equals(
"http://lacolaco.net:8080/B/A/laco#fuga"
));
});

Uriに関する変更は、 既存のコードへの影響があります 。不正なUriを扱っている可能性がある場合は、1.6にアップデート後に見直しが必要になるかもしれません。


Futureクラスに静的メソッドdoWhile追加

FutureクラスにdoWhile(Function)メソッドが追加されました。引数に渡した関数がFalseを返すまで無限にFutureを投げ続けます。


future_test.dart

    var i = 0;

Future.doWhile(() {
print(++i);
return i != 3;
}).whenComplete(() {
print("comp");
});


このコードの出力はこのようになりました。

12

3
comp

1行目が1と2が並んでいるのは、1回目の処理と2回目以降の処理ではおそらく非同期処理なので改行が入る前に次のprintが呼ばれるからだと思われますが、よくわかりません。詳しい検証は割愛します。これは既存のコードに影響はありません。

また、asyncパッケージ周りではZoneクラスにいくつかの変更が入っていますが内部的な改善なので割愛します。


HTTP通信周りのセキュリティ的改善

見出しにこそなっていませんが変更の規模的にはここが一番大きいような気もします。詳しいアナウンスはこちらです。まだ自分でも把握しきれていないのでおおまかな部分だけ要約すると、


HttpServerクラスの変更点


  • HttpServerに新しいプロパティdefaultResponseHeadersを追加

  • HttpHeaderに新しく以下のデフォルト値を設定


    • Content-Type: text/plain; charset=utf-8

    • X-Frame-Options: SAMEORIGIN

    • X-Content-Type-Options: nosniff

    • X-XSS-Protection: 1; mode=block




HttpClientクラスの変更点



  • autoUncompressプロパティを追加。Bodyのデータの圧縮を自動で解凍するかどうかを設定できるようになった。

ものすごく大雑把なので、HttpServer周りは自分でもう少し情報を集めてから個別に記事を作るかもしれません。


ByteBufferクラスにas***関数大量追加

ByteBufferクラスに多数のas****関数が追加されました。


途中から疲れて最後の方は手抜きになりましたがDart 1.6の変更点はこんな感じです。これに加えて実はpubの方に機能の追加があったはずなんですが、何故かリリースノートに載っていません。"pub global"コマンドに関しては後日アナウンスがあり次第多分記事を書きます。