もちろんご存知かとは思いますが昨日8月27日にDartのバージョン1.6が出ました。目玉は「Deferred loading」ということで、来るasync/await実装に向けて非同期処理まわりの改善の第一歩ということでしょうかね?
1.6アップデートの公式ブログ記事はこちらです。以降リリースノートの内容についてサンプルコードと一緒に解説していきます。
ライブラリ読み込みの遅延実行をサポート
今回の目玉である deferred loading は外部ライブラリのインポートを必要な時まで遅延させ、実行時のロード時間を短縮させることが可能になる機能です。公式の解説記事はこちらです。
遅延読み込みされる側のライブラリには何も変更は必要ありません。サンプルではシンプルなhello worldのライブラリを定義しています。
library deferred_loading_example.hello;
printGreeting() {
print('Hello World, from the deferred library!');
}
読み込み側ではまず、import文にdeferred
を付け、名前付きでインポートします。
import 'package:deferred_loading_example/hello.dart' deferred as hello;
次に、ライブラリを使う時にhello.loadLibrary
を実行します。
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クラスを例に違いを示したのが次のコードです。
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()
、符号を反転させる-
演算子がサポートされました。
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ですが、新たにsource
とoffset
が追加され、表現力のあるエラー表示になりました。試しに不正なjson文字列をエンコードさせてみます。
var wrongJson = "{ key:value }";
try {
var decode = JSON.decode(wrongJson);
expect(0, 1);
} catch(e, s) {
print(e);
}
これを実行して出力されるのがこんな感じです。
FormatException: Unexpected character (at character 3)
{ key:value }
^
例外が発生した文字列と、その発生位置まで出力されて、デバッグしやすくなりました。自分でFormatExceptionを投げるときもこれを利用するようにしましょう。
Uriクラスのセキュリティ方面のバグ修正と機能追加
Uriに、不正なUriの使用を制限する目的のバグ修正がなされました。詳細の原文はこちらですが、簡単にまとめると
- 不正なUriをパースしなくなった
- hasAuthority/hasQuery/hasFragmentが追加された
- その他パーサーの改善
となっています。が、実際に試した所これでいいのか?という感じです
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);
});
hasQuery
とhasFragment
に関してはUriの仕様的にこれでいいのか不安なんですが、Uriに詳しい人コメントで教えてください。
また、バグ修正だけでなくUriに新しくreplace
メソッドが追加されています。Uriの各要素を置換して別のUriを作ることが出来ます。文字列操作をしなくてよくなるのでとても便利なメソッドです。
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を投げ続けます。
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"コマンドに関しては後日アナウンスがあり次第多分記事を書きます。