Subscribed unsubscribe Subscribe Subscribe

Hatena Developer Blog

はてな開発者ブログ

【7月4日(月)正午まで】はてなサマーインターン2016締め切り間近!!!

はてなでのひと夏があなたをつくります

下記の記事などでお伝えしております、今年のはてなサマーインターンの応募締め切りが迫ってきました。応募は7月4日(月)正午までとなっていますので、検討中の方はお早めに申し込みください。

developer.hatenastaff.com

developer.hatenastaff.com

今年はアマゾン ウェブ サービス ジャパン株式会社様より講師を招いての特別講義や、機械学習演習など、前半の講義パートも去年以上に充実しています。また、4つのコースから選択いただく後半課程も、はてなのスタッフと実際のサービス開発を体験できる密度の濃いカリキュラムです。

はてなで過ごす夏の20日間。皆さんとお会いできるのを楽しみにしています!!

募集概要

開催日程

2016年8月15日(月)〜9月9日(金)平日のみ20日間
(期間中フル参加できない場合の個別の日程調整は可能です。ご相談ください)

開催会場

株式会社はてな 京都本社(京都市中京区)

実施内容

1〜10日目: Webサービス開発/コンピュータサイエンス 講義・課題
11〜20日目: サービス開発/研究(コースごとに実施)

応募資格

2017年以降に卒業予定の高専生、大学生、ならびに大学院生
当てはまらない場合は応相談

待遇

日給 10,000円 (11日〜20日目の10日分を支給)
オフィスから片道1時間以上要する遠方からのご参加の場合
期間中ホテルを当社負担で手配(朝食代含む)
京都までの往復交通費として最大25,000円まで支給

募集人数

最大8名
※選考状況によっては一部コースが開催されないことがあります

選考

書類(場合により対面またはSkypeにて面接選考を行います)
選考結果のご連絡は7月中旬ごろを予定しています。

昨年のインターンの様子は、下記のレポートサイトをご覧ください。

TypeScript の型定義ファイルと仲良くなろう

はじめに

こんにちはアプリケーションエンジニアの id:t_kyt です。

このエントリでは TypeScript (以下 TS)における型システムの概要に触れつつ、型定義ファイルに何が書かれているかを理解するのに必要な知識を解説していきます。ある程度TSを書いたことあるが、なんとなく .d.ts ファイルをダウンロードしてきて使っている人向けです。

TypeScriptの型システム

型定義ファイルの書き方読み方に入る前にまず TS の型のシステムについて軽く触れておきます。

この章を読まずとも定型的に型定義ファイルの読み方書き方を覚えることはできますが、何が起こっているのかわからないと応用が効かないと思うので一読しておくことをおすすめします。

Declaration space

TSには declaration space という概念が存在します。同一の declaration space 上で同じ名前の entity (宣言した変数や型など)があった場合、コンパイルエラーとなります。ただし宣言が open-ended な場合や特別に振る舞いが定義されている場合はその限りではありません。 open-ended に関しては後ほど解説します。

declaration space には3種類あり、それぞれ ValueTypeNamespace です。

var X: string;    // Value named X

type X = number;  // Type named X

namespace X {     // Namespace named X  
    type Y = string;  
}

結果

このコードはすべて同一の X という名前で宣言されていますが、 declaration space が異なっているため、コンパイルエラーにならずそれぞれ有効です。

同一空間ではコンパイルエラーになるので

type X = number;
type X = number;

結果

のようにするとエラーになります。

var X: string;    
var X: string;

こちらの場合も、Valueに関して同一の declaration space に同じ識別子で宣言していることになりますが、振る舞いが特別に定義されているためエラーにはなりません。

Multiple declarations for the same variable name in the same declaration space are permitted, provided that each declaration associates the same type with the variable.

5.2.1 Simple Variable Declarations より

また namespace についても同一の declaration space に同一識別子で宣言してもコンパイルエラーになりません。 namespace の場合は後述する open-ended の仕様によるため例外的扱いになります。

(例外的なものも多いですが、原則的には同じ declaration space には同じ識別子で宣言はできません。)

ある宣言がされた時、どの declaration space に宣言されるかは Declaration Type によって変わります。

Declaration Type Namespace Type Value
Namespace X X
Class X X
Enum X X
Interface X
Type Alias X
Function X
Variable X

Declaration Mergingより。

Declaration Type は宣言の種類です。チェックマークはその各宣言がどの declaration space に属するかを表しています。

このうち実際の JS のコードになるのは Value と Namespace です。Type は生成された JS のファイルには影響せず、TS の世界でしか影響がありません。 Namespace の場合は Namespace 内に Value がないと何も生成されません。

参考: 10.1 Namespace Declarations

Open-ended

open-ended とは同じ装飾名の宣言があった時、自動的にマージされる性質のことです。

open-ended な宣言として有名なのは interface 宣言でしょう。

interface Document {  
    createElement(tagName: any): Element;  
}

interface Document {  
    createElement(tagName: string): HTMLElement;  
}

interface Document {  
    createElement(tagName: "div"): HTMLDivElement;   
    createElement(tagName: "span"): HTMLSpanElement;  
    createElement(tagName: "canvas"): HTMLCanvasElement;  
}

このコードはマージされるので

interface Document {  
    createElement(tagName: "div"): HTMLDivElement;   
    createElement(tagName: "span"): HTMLSpanElement;  
    createElement(tagName: "canvas"): HTMLCanvasElement;  
    createElement(tagName: string): HTMLElement;  
    createElement(tagName: any): Element;  
}

とした場合と同等になります。 namespace 宣言も open-ended であるため、前述した例外のような振る舞いとなります。つまり

namespace X {
    type X = string;  
}

namespace X {
    type Y = string;  
}

はエラーとなりません。

さて、 open-ended な宣言がマージされる条件に root container が共通であるということがあります。まずは container の概念について説明します。

ある entity が宣言された時、その container も自動的に決定します。具体的には

namespace N {
    let x = 1;  
}

ここで宣言された x の container は namespace N となります。

また TS では Top-Level に importexport があるファイルが module とされ、 module内の宣言では container はその module になります。例としては以下のようなものです。

export {}
var x = 1;

この場合 x の container はこの module になります。

また、 module 自体の container は global namespace になります。

まとめると

  • ある namespace 内で宣言された entity の container はその namespace
  • ある module 内で宣言された entity の container はその module
  • global namespace 内で宣言された entity の container は global namespace
  • module 自体の container は global namespace

です。

次に root container ですが、root container は宣言が export されているかどうかで変わります。

  • export されていない entity の場合 root container はその entity の container
  • export されている entity の場合の root container はその entity の container の root container

直感的には一番外側の module や namespace が root container ということですが、例を見ないと意味がわからないでしょう。

// A.ts
namespace outer {  
    var local = 1;           // export されていない
    export var a = local;    // outer.a  
    export namespace inner {  
        export var x = 10;   // outer.inner.x  
    }  
}

// B.ts
namespace outer {  
    var local = 2;           // export されていない
    export var b = local;    // outer.b  
    export namespace inner {  
        export var y = 20;   // outer.inner.y  
    }  
}

Top-Level に namespace で宣言した場合、その container は global namespace になります。したがって、2つの outer はどちらも global namespace が root container ということになります。

open-ended のルールが適用されるのは root container が共通で、宣言が同じ declaration space で行われている場合ですから、 outer は条件を満たし定義はmergeされます。

また outer 内で宣言されている innerexport されているので root container は global namespace となり open-ended の性質が適用されます。したがってこの outer の instance type は

{  
    a: number;  
    b: number;  
    inner: {  
        x: number;  
        y: number;  
    };  
}

と同等になります。 export されていない宣言( local )については root container が共通でないためマージされません。(そもそも export していないので外からは見えません。)

ここまでの確認

いろいろ書きましたが、これで TS を書いている時「なんで?」となる(型定義に関する)ハマりポイントが仕様的にどう解釈されているかある程度理解できるようになったかと思います。

これまでの知識の確認をするため、ありがちなコードを例に少し今までの知識を振り返ります。

以下のコードは上手く行かない例です。

import $ from "jquery";

// interface をマージして window.originalFunction() を使えるようにしたい
interface Window {
    originalFunction(string): void
}

window.originalFunction("test"); // => 使えない…

window: Window; // type annotationしてみる

window.originalFunction("test"); // => 使えるようになった!!
window.alert('hello,world');     // => 元から Window あった定義が使えなくなった……

このコードで何が起きているかというと、 Window は新しく宣言されただけでマージされていません。type annotation した時点で window (変数)の方が既存の Window (interface)から新しく定義された Window に書き換わっています。なので window の型は既存の定義か、新しい定義のどちらかになってしまうのです。

では何故マージされないのか?についてですが 既存の Window と新しく定義した Window の root container が異なっているためです。既存の Window の root container は global namespace ですが、上記のコードで宣言された Window の root container は import $ from "jquery"; が存在するため、この module になります。 Top-Level に import を書いた場合 module になること、 export されていない entity の root container の決定の仕方を思い出してください。

ではどうすればいいのかについてですが、 root container が共通になるように宣言すれば良いのです。具体的には別ファイル( module になっていない = importexport が書かれていない)に宣言を移すか、後述する declare global { } を使うといいでしょう。

次に TS では Top-Level に importexport があると module として扱われる確認です。

// x.ts
namespace N {
    export x(): void;
}
// y.ts
namespace N {
    export y(): void;
}

この場合 module にはなっていないので使う側は特に import する必要はなく

N.x()
N.y()

のように即使用できます。使う側も module にはなっていません。また N の定義も統合されるので N 以下に xy が存在する形になります。

対して以下のような場合は module として扱われるので namespace の場合とは違います。

// x.ts
export namespace N {
    export x(): void;
}
// y.ts
export namespace N {
    export y(): void;
}

使う側としては

import {N as N1} from "./x.ts";
import {N as N2} from "./y.ts";

N1.x()
N2.y()

のように使います。使う側も import があるため module として扱われます。

そのファイルがいま module なのか?を意識すると定義ファイルでハマることも少なくなると思います。

型定義ファイルを読み書きできるようになるために

さてここからは、declare の説明をしたあと、型定義ファイルを読み書きできるように典型的な例を紹介していきます。

何が起こっているのかについては前章の知識で大体カバーできるかとおもいます。ただ、型定義ファイルの書き方に関するベストプラクティスは歴史的な経緯が入る部分もあるため深入りはしません。幾つかの典型的な例を示しつつ、標準的な型定義ファイルに対して何をやっているのか理解でき、標準的な書き方ならばできる、また標準的ではない型定義ファイルに対しても「何故」はともかく「何をやっているか」は理解できるようになることを目指します。

declare キーワード

declare キーワードを使うと既存の JavaScript の型情報が表現できます。

例えばグローバルで宣言されたtest()という関数を使うというシチュエーションを考えた時、以下コードは JS としては問題ない(本当にグローバルに test() があれば)ですが、tsc でコンパイルしようと思うとエラーになります。

test()
test.ts(1,1): error TS2304: Cannot find name 'test'.

これはどういうことかというと TS の declaration space に test が宣言されていないということです。ではどうすればいいかというと、ないのであれば宣言すれば良いのです。そんなときに declare キーワードを使います。

declare function test()

declare を使うと JS のコードとして出力されません。これにより既存の test() のコードを上書きすることなく test() が宣言することができますので、ブラウザや既存の JS によって提供される変数や関数を宣言する事ができます。これが ambient (包囲した、取り巻く)宣言です。

宣言すれば、以下のコードはコンパイルが通ります。

declare function test()
test();

みれば分かるように declare 自体は特に type annotations は必須ではありません( --noImplicitAny が有効になっていない場合。有効な場合明示的に any が必要です )。ただ declare は型情報もかけるので書いたほうが便利ということで普通は書きます。ambient 宣言を集めたファイル( .d.ts )は型定義ファイルなどと呼ばれて利用されています。

// x.d.ts
declare function test(): string;

// y.ts
let t = test(); // t は型推論で string になる

.d.ts ファイルはコンパイルした結果、JS のコードは生成されません。というよりは、生成しない宣言しかしてはいけません。

既存のオブジェクトの型定義を拡張する

ここからは今までの知識を利用して実際に定義ファイルを読み書きできるようになるための実例を示していきます。

実行環境がブラウザの場合 window オブジェクトが存在していますが、その型定義を拡張したい場合を考えます。

自分で拡張せずとも標準的なものは Window として typescript/lib/lib.dom.d.ts に定義されていますが、今回は例えば window.fetch() など polyfill した場合や自分で独自のプロパティを生やしているシチュエーションを想定しています。

そういった場合は既に定義されている Window に対して新しい定義をマージすることで対処します。

interface Window {
    fetch(url: string|Request, init?: RequestInit): Promise<Response>;
}

これは window.fetch の例ですが RequestInit などの定義は省略します。これで既に TS が標準で定義している Windowfetch がマージされます。

グローバルなオブジェクトに対する宣言

例えば jQuery を script タグで読み込んだ時のようなグローバルにオブジェクトが存在しているような場合を考えます。その場合

declare namespace $ {
    function x(): void;
    var y: string;
}

のように namespace で対処すると良いでしょう。これにより $.x()$.y みたいな呼び出しが可能になります。なお declare namespace 内では定義された entity はデフォルトで ambient 宣言になるため declare は必要ないです。

少し混乱すると思いますが、実際の DefinitelyTyped で公開されている jquery.d.ts はこの方法では書かれていません。これは歴史的経緯や他の定義ファイルとの依存関係の話になるので、特に気にしないで namespace を使うといいでしょう。

もちろん単なる関数オブジェクトの場合は

declare function f(): void;

のようにするだけで十分です。

ここで1つ今ある定義ファイルを読みやすくするポイントして、 namespace が導入されたのは結構最近なので、以前からある定義ファイルでは module となっている場合があるということを頭に入れておくといいでしょう。ES6のモジュールシステム導入にともなって

  • internal module( module 'N' {} ) → namespace( namespace 'N' {} )
  • external module( module M {} ) → (単に)module( module M {} )

というように用語とキーワードが改められました。意味は同じですが module(旧 external module)と紛らわしいので namespace が推奨されています。

module

npm module を使いたいとき、単に npm i lodash しただけでは TS の世界では import できません。

import * as _ from "lodash";

module の場合は別ファイルにそれ用の宣言が必要になります。

declare module 'lodash' {}

module の後が文字列なのがポイントです。識別子だと namespace になります。

ただし、これだと lodash という module があるという情報しかないので定義を追加していきます。

declare module 'lodash' {
    interface Hoge{}
}

にすると

import * as _ from "lodash";
var hoge: _.Hoge;

がコンパイルを通るようになります。

module も open-ended なためマージすることができます。

declare module 'lodash' {
    interface I {
        x(): string
    }
}

という既存の .d.ts ファイルが存在しているが、自分で定義を追加したい場合は以下のようなファイルを用意すれば良いということです。

declare module 'lodash' {
    interface I {
        y(): string
    }
}

ただし、module が open-ended になったのは最近のことなので、namespace を使って無理やり拡張できるように定義されている場合もあるので頭に入れておくと混乱せずに済むでしょう。

参考: JS資産と型定義ファイル

Export Assignments

Export Assignments(export = )を使った例を見ていきます。

現在の TS では ES2015 形式のモジュールシステムが使えるため、Export Assignmentsで書く必要がない場合もあります。しかし、 export =export default に互換性がないこともあり、既存の定義ファイルの大多数は Export Assignments で書かれていると思うので、むしろ ES2015形式 より多く見かけると思います。

参考: ES6 Modules default exports interop with CommonJS

import sayHello = require("say-hello");
sayHello("Travis");

このように単体のオブジェクトが export されている module の場合、定義ファイルは

declare module "say-hello" {
    function sayHello(name: string): void;
    export = sayHello;
}

のようになります。 export = は単体のオブジェクトを export できます。

import M = require("M");
// ES2015でいうと
// import * as M from "M";

のように利用する module 、つまり M というオブジェクト以下に複数の export されたオブジェクトがぶら下がっているような場合は namespace を Export Assignments で export します。

declare module "M" {

    namespace N {
        function x(name: string): void;
        class C {}
     }

     export = N;
}
import m = require("M");

m.x("Travis");
let C = new m.C();

interface も利用できます。

declare module "M" {

    interface I {
        (): I;
        new (): I;

        x: string;
        y(): void;
    }

    declare var i: I;

    export = i;
}

間違いやすいポイントとしては export = に何を指定するかです。 interface である Iexport = をしてしまうと、 インスタンスではなく型そのものが export されてしまうのでうまくいきません。type annotation された変数を export = しましょう。

Relative or Non-relative module imports

補足ですが、以上はすべて non-relative module の例です。つまり require()from で指定する部分を

import $ = require("jquery");

import * as $ from "jquery";

のように module 名でする場合です。npm の module を利用する場合などはこうする場合が多いと思います。

対して relative module import とは

import Entry from "./components/Entry";

のように相対パスで指定する場合です。自前の module はこちらでしょう。

既存の(自前の) JS を .d.ts ファイルとともに使用する場合は .js ファイルと .d.ts ファイルは同じディレクトリに入れておくのが普通だと思います。その場合、 from 以降で指定されている path が一致しているので、今までの例にある一番外側の declare module M は必要ないです。declare module M は non-relative module import における module 名を指定していると考えてください。

TS における module の解決の仕方も頭に入れておくと理解しやすいです。

参考: Module Resolution

ES2015形式

ES2015 形式の module でも記述することが出来ます。 declare キーワードが付いてても export が書けるので以下のようになります。

// m.ts
export declare function f(): void;
export declare class C {}

この場合 namespace でまとめてから export = みたいなことはせずに済みます。

import する側は

import * as m from "./m";

のように利用します。これは

import m = require("./m");

と同義です。

実際の定義ファイル

これで大体の型定義ファイルのパターンがカバーできたと思います。ここで一つ実際の例(jQuery)を見てみましょう。

declare module "jquery" {
    export = $;
}
declare var jQuery: JQueryStatic;
declare var $: JQueryStatic;

ここより上の部分はひたすら JQueryStatic の定義をしているだけなので割愛します。Top-Level に interface が宣言されているのであまり良いとは言えないのですが、型定義ファイルの書き方のベストプラクティスは事情によりけりなので今は触れないことにします。 参考: JS資産と型定義ファイル

declare var jQuery: JQueryStatic;
declare var $: JQueryStatic;

この部分で global に $ 及び jQuery という変数が存在すること、またその変数は JQueryStatic であるということが TS のコンパイラに伝わります。これで global な jQuery の読み込み(scriptタグで読み込んだ場合など)には対処できます。

しかし、JQuery を npm module として読み込みたい場合これでは不十分です。それを補うのが

declare module "jquery" {
    export = $;
}

この部分です。

この記述により jquery という module が存在することが TS のコンパイラに伝わります。この記述があって初めて

import * as $ from 'jquery'

という module 名の読み込みが可能となります。なぜ interface を直接 export しないのかについては前述のとおりです。

これでめでたく script タグで読み込んだ場合と npm module として扱いたい場合の対応ができました。

既存の定義ファイルを拡張する

基本的には既存のオブジェクトの型定義を拡張するで触れたように open-ended な宣言に対するマージで対処します。 interface だけでなく module( declare module 'M' {} )や namespace( declare namespace N {} )も open-ended であるため、マージされる条件が整えば既存の型定義ファイルを拡張することが出来ます。

ただし export = が使われていた場合注意が必要です。

declare module 'M' {
    namespace N {
        var x:string;
    }
    export = N;
}

このようになっている定義ファイルは外から拡張する方法はありません。なので定義ファイル自体書換える等の処置が必要になります。

ES2016 形式で書かれている場合は外から拡張可能です。

declare module 'M' {
    export var x: string;
}

に対しては別ファイルに

declare module 'M' {
    export var y: string;
}

と書けば定義は拡張されます。

export = が使われていた場合、外部から拡張する術がないのは微妙に困ることが多いですが、TS 2.0 (次期バージョン)以降では以下のような書き方ができます。

import * as M from 'M';

declare module 'M' {
    function x(): void;
}

M.x();

参考: Unable to augment export = function/namespace

ちなみに TS の nightly builds は npm install typescript@next でインストールできます(現行は Version 1.9.0-dev.20160611-1.0 ですが上記の書き方はできるようになっています)。

declare global { } について

TS 1.8 より declare global { }導入されました。この記法により module 内でも global namespace に型を定義を宣言できます。

import $ from "jquery";

// interface をマージして window.originalFunction() を使えるようにしたい
declare global {
    interface Window {
        originalFunction(string): void
    }
}

window.originalFunction("test"); // => 使える
window.alert('hello,world');     // => 使える

先ほど上手く行かない例としてあげたコードもこのように書けばファイルを分割することなく Window を拡張できるようになります。

Typings について

最近、長らく TS の型定義ファイル管理ツールであった TSD が非推奨になり、Typingsが登場しました。

TSD にはいくつが問題点があったのですが、特に型定義ファイルの global な読み込みについてこのエントリと関連深いため触れておきます。

前述の jQuery の例を見るとわかるのですが、 jQuery を module として読み込んだ場合でも $JQueryStatic が global に宣言されてしまいます。 declare が付いているので declaration space は Type となり、実際の成果物である JS には影響を及ぼさないため害がないといえばないですがあまりいいものではありません。たとえば jQuery を import していない場合でも以下のコードは問題なくコンパイルされます。

 $('.xxx').hide();

しかし実際の JS は module 化されているので $ オブジェクトは存在せず、実行時エラーとなります。 これを解決するには import しているファイルでのみ定義ファイルを referenceタグ( /// <reference path="path/jquery.d.ts"> )を使って読み込む等の地道な作業が必要でした。

Typings はこの問題を解決しています。Typingsが提唱する形式の型定義ファイルは module として読み込むか、global に読み込むかを選択できます。module として読み込む場合 Typings 側で自動で global に散らばった定義を隠蔽してくれます。したがって、script タグで読み込んだ場合でも npm module として読み込んだ場合でも両方適切に対処できるようになります。

ただし、 module 化した場合、外から拡張する手段が現状(TS 1.8)では存在しないので定義ファイル自体を書き換えるしかありません。先ほど触れた 1.8.2 での変更が入れば外部からの拡張が可能です。

Typings の形式に対応した型定義ファイルはまだ多くないのですが、オプションを付けることで DT のファイルも使うことができます( module 化はしてくれないので tds で読み込むのと同じになる)。

ちなみに TS2.0 からは npm で型定義ファイルを管理できるようになるため Typings も一時の繋ぎになるかもしれません。 参考:The Future of Declaration Files

おわりに

TS の躓きポイントの1つである型定義ファイルの読み方書き方について、型システムを仕様を少し掘り下げることで何が起こっているのかを解説しました。

TS の型定義ファイルで困ったらむやみに <any> する前に一度立ち止まり正しく型を付けれないかを考えてみましょう。

インターン募集中

hatenacorp.jp

応募締め切りは2016年7月4日(月)までです!奮ってご応募ください!!

ペパボ・はてな技術大会〜インフラ技術基盤〜の参加者募集中!!!


上記エントリにて先日お知らせしたペパボ・はてな技術大会〜インフラ技術基盤〜の募集を開始しています。すでにconpassのイベントページを公開していますが、本ブログでも改めてお伝えします。

以前にお伝えしたとおり、福岡と京都で開催します。福岡編は、7月9日(土)開催です。参加者募集ページは以下になります

京都編は、7月2日(土)開催です。参加者募集ページは以下になります。

福岡、京都ともにすでに定員を上回る申し込みをいただいています。ただし、先着順ではなく抽選で募集していますので、今からでもふるって申し込みください。

【Apple WWDC 2016】iOS 10のApp Transport Securityと2016年末からのATS必須化についてAppleのエンジニアに聞いてきた

Apple WWDC 2016に参加するためにサンフランシスコへ来ているid:niwatakoです。

WWDC 2016のセッション にて、App Storeに公開するアプリは今年中にATS(App Transport Security)が要求されるようになるという発表がありました。

アプリからの通信をhttps接続のみに制限するATSの有効化がApp Storeへのアプリ提出には必須になるとのことですが、はてなブックマークのようにhttpのウェブページを含む不特定多数のコンテンツの表示が必要なアプリはどこまで制限されるのでしょうか。

WWDC期間中はAppleのエンジニアに質問が出来るLabが設けられているので、ATSとiOS 10でのATS周りの仕様について質問してきました。

2016年末にATSが必須化されることが言及される

ATS(App Transport Security)とは、iOS9とOS X 10.11から導入されたアプリとネットワーク間のセキュアな通信を保証するための機能で、安全に暗号化されたhttps通信のみを許可し、安全でないレベルのhttps通信やhttp通信を遮断します。

How iOS Security Really Works および What's New in Security というセッションで、2016年末にApp Storeに公開するアプリにはATSが必要になるということが発表されました。

セッションスライド資料 Page 100/116

App Transport Security
• Required by App Store at end of 2016
• TLS v1.2, with exceptions for alreadyencrypted bulk data like media streaming

ATSの必須化とiOS 10の登場で、何が変わるのか

ATSはこれまで、ATS自体の無効化や、ホワイトリストを作成してATSの通信遮断対象から除外するドメインを指定したり、逆に全体のATSを無効にしながら一部のドメインについてはATSの対象としたり、あるいはATSが許容する暗号水準を下げたり、といった詳細なカスタマイズが可能でした。

ATSの設定は、アプリケーションのInfo.plistにキーを追加して値を設定しますが、利用可能なキーはこちらで確認出来ます。

developer.apple.com 注意:こちらのドキュメントは開発者向けのプリリリースのドキュメントであり、iOS 10の確定された最終仕様ではありません。今後正式なiOS 10がリリースされるまでの間に変更が加えられる可能性があります。


iOS 10 では、新たに NSAllowsArbitraryLoadsInWebContent というキーが追加され、このキーの設定値を YES にすることでウェブページの読み込み(と、AVFoundationのStreaming media)に限ってはhttp通信を許可することが可能になります。

つまり、iOS 9 では不特定多数のウェブページを表示するようなアプリはATSを無効化するしかなく、それによってウェブページの表示以外のhttp通信まで許してしまっていたのに対し、iOS 10では、不特定多数のウェブコンテンツを表示しつつも、それ以外の通信は安全なhttpsへの接続のみを許可することで、セキュリティを向上させることが可能になるのです。

また、それが可能になることで、ATSを無効化しなければならないケースはかなり限定的になると考えられるため、App Storeに公開されるアプリの全体的なセキュリティ水準の向上のために、2016年中のATS必須化がアナウンスされたのだと考えられます。セキュリティが向上しただけで、アプリの開発者がきちんと対応すれば、これまでどおりhttpのウェブページにもアクセスすることができます。

条件ごとにATSの仕様を把握し、適切にATSを設定する

ATSの設定の影響を受けるのは、ネットワーク通信を行うNSURLSessionや、ウェブページを表示するUIWebView、WKWebViewなどの通信を行うクラスです(アプリケーションから独立してウェブページの表示を行う SFSafariViewController はATSの影響を受けません)。

また、iOS 10から利用可能になるATSの新しい設定キー NSAllowsArbitraryLoadsInWebContent と、従来からあるATSの無効化設定キー NSAllowsArbitraryLoads が同時に存在した場合にはどちらが有効になるかの力関係があります。

iOSのバージョン、ATSの設定、通信を行うクラスの組み合わせ毎に、ATSがどのように機能するかを一覧にまとめました。

f:id:niwatako:20160616114803j:plain

iOS 9では従来通り、NSAllowsArbitraryLoadsYES であれば、自由にhttp通信が行えます。

iOS 10では、まず NSAllowsArbitraryLoadsInWebContentYES かどうかで条件が別れます。NSAllowsArbitraryLoadsInWebContent が、設定されていないか NO である場合は、iOS9と同じ挙動になります。 YES に設定されている場合は、iOS 10では NSAllowsArbitraryLoads が無視され、WKWebViewのみがhttp通信を許されるようになります。

このとき、データ通信を行うNSURLSessionに加え、UIWebViewについてもhttp通信が許可されないのがポイントです。なるべくWKWebViewを利用して欲しいとのことでした。また、NSExceptionDomains を設定すれば、指定したドメインへのNSURLSessionやUIWebViewからのhttp通信も許可されるそうです。

ただし、Apple のエンジニアの間でも情報が錯綜している

UIWebiewにも NSAllowsArbitraryLoadsInWebContent は有効なはずだ、とAppleのウェブテクノロジーエバンジェリストがTwitterで述べているようです。

ちなみに私がLabで質問したのはセキュリティの担当者でした。

ATSはセキュリティの機能で、2016年末に必須化することが発表されたのもセキュリティがテーマのセションです。なのでセキュリティエンジニアの言っている内容がよりATSの方針・意図を知るためには適切ではないかとは思っています。

しかし、Apple内部でも見解が統一されていない可能性があり、正式リリースまでの間に変更が加えられる可能性もありますのでご注意ください。

どのように対応すべきか

少なくとも今日私が話を聞いたLabのエンジニアの話をもとに考えると、これまでhttpのウェブページを利用するために NSAllowsArbitraryLoadsYES にしていた場合は、NSAllowsArbitraryLoadsInWebContent を追加して YES を設定し、WKWebViewのみhttp通信を許可するのが良いでしょう。

UIWebViewやNSURLSessionで不特定多数のサーバーに対してhttp通信を行わざるを得ない場合は、そのまま NSAllowsArbitraryLoads のみ YES に設定していれば良さそうです(審査時に理由の説明が必要)。

UIWebViewを利用している場合、特別理由がなければWKWebViewに移行することが強く推奨されそうです。


はてなのサマーインターンに参加するiOSエンジニアを募集中

はてなではSwiftを使ってiOSアプリを開発しています。今回のWWDCの基調講演のスライドの中にもSwiftで作られたアプリの一つとして、はてなブックマークアプリのアイコンが掲載されています。

そんな株式会社はてなでは "iOSアプリ開発実践コース" がある、はてなサマーインターン2016を開催します。

hatenacorp.jp

応募締め切りは2016年7月4日(月)となっています。宿泊費は出ますので、全国より奮ってご応募ください!

【Apple WWDC 2016】WWDC 初日 Keynote、State of Union、Design Award と Realm WWDC Livestreamed Swift Panel 参加レポート

こんにちは。はてなブックマークのiOSアプリ開発チームで働いている id:niwatako です。

6/13から6/17にかけて、アメリカ・サンフランシスコではAppleの開発者向け年次カンファレンスであるWorldwide Developers Conferenceが開催されています。

私はそれに参加するためにサンフランシスコへやって来ました。現地から、WWDC初日に参加した様子をレポートいたします。

はじめに

アメリカでは先日オーランドで痛ましい事件がありました。

半旗を掲げたWWDC会場や、冒頭で決して例年のように "Good morning" とは言わなかったティム・クック氏の様子、そして参加者への黙祷の呼びかけに、Appleの犠牲者への強い哀悼と追悼の意を感じました。

f:id:niwatako:20160614202014j:plain

テクノロジーが人類の多様性や相互理解の促進に貢献できることを切に願います。

犠牲者の冥福を心からお祈りするとともに、ご遺族や関係者の方々に心よりお見舞い申し上げます。

WWDC 2016

基調講演 (Keynote) の様子

f:id:niwatako:20160614202006j:plain

27回目のWWDCとなる今回は、チケットの抽選に申し込みをした人の数も、スカラーシップで招待された学生の最年少者の年齢(9歳!)も、記録を更新したのではないでしょうか。74カ国から参加者が集まり、国数も最多記録です。WWDCは毎年多くの、そして多様な開発者が参加するようになっています。

今回のWWDCは例年と異なり、WWDCに先駆けて一部ジャーナリストがAppleへのインタビューを許され、App Storeへの検索広告の導入やサブスクリプションの解放など、アプリ開発者にとって影響のある新制度について事前に発表がありました。日本では林信行さんがフィル・シラー氏への独占インタビューを公開されています。

この異例の事前発表の理由は、今回のWWDCではあまりにも多く発表がありすぎるため、デベロッパーやメディア向けに新機能や新制度の概要をまとめて話すWWDC初日午前中の基調講演(Keynote)では、すべてを発表しきることが出来ないから、というものでした。

実際に、基調講演はwatchOS、tvOS、OS X、iOSの4つを柱として発表があり盛りだくさんの内容に思われました。

watchOSを紹介したケヴィン・リンチ氏からtvOSを紹介するエディー・キュー氏、そしてOS XとiOSを紹介するクレイグ・フェデリギ氏まで、紹介者が次のOSの紹介者を舞台に招き交代する形で進行していきました。

例年、ティム・クック氏が担当者を舞台に呼んで詳細を説明させ、終わったら軽くまとめを話してから次のOSの話に移りまた担当者を呼ぶというような形式でしたが、沢山のアップデートを紹介するために進行の仕方を工夫したのではないかと思います。

watchOS - アプリの起動が高速化、快適にアプリを利用可能に
  • アプリの起動が高速に
  • Glance が廃止され、Dockが導入される
  • スワイプアップでControll Centerを表示
  • 文字入力の改善
    • メッセージへの返信候補サジェスチョンや、指でなぞる文字入力が可能に
  • Watch Faceの追加と、Watch Faceの切り替えがスワイプで可能に
  • エマージェンシーコール機能
    • 各国各地の緊急連絡先に位置情報と共にSOSを送信
    • 送信後はApple Watchの画面はメディカルIDを表示する
  • アクティビティアプリが車いすの活動に対応
  • スタンド 機能のように取り組む通知し、効果を測定する、深呼吸のサポート機能
  • アプリ開発のための新機能
    • SpriteKitとSceneKitが利用可能に
    • デジタルクラウンやタッチイベントの習得が可能に
    • スピーカー出力が利用可能に
    • ビデオのインライン再生が可能に
    • GameCenterが利用可能に
    • CloudKitが利用可能に
    • Background update
      • Watchアプリやコンプリケーションの情報を最新にしておける
    • ジャイロスコープにアクセス可能に

watchOSでアプリが高速に起動するようになることは、Apple Watchで本格的にアプリを利用することがいよいよ可能になったと言えそうです。

Apple Watchのアプリ開発条件はかなり制限された仕組みから段階的に変化してきました。最初は制約が多くデバイスの力がほとんど生かせない状態、次に実現可能なことが増えたものの、まだ起動には時間がかかり過ぎて起動より前にスリープに入ったりすることがあり、なかなか使い物にならない状態。それが今回ついに、シームレスにアプリを起動して目的を達成できるようになるのではないか、と期待させられるデモの様子でした。

tvOS - 少しずつ不便さの改善や障害の解消が進む
  • 様々な動画プラットフォームやゲーム、アプリが追加される(一部日本は非対応)
  • Single Sign Onでアプリごとにパスワードを入れる必要がなくなる
  • ダークモードの追加(画面が黒基調に)で部屋のコーディネートや時間に合わせた画面にできる
  • iPhoneでインストールしたアプリにtvOS版があれば自動でインストールされる
  • PhotoKit、CloudKit、HomeKitにアクセス可能に

Apple TVへの対応がスタンダードにはなっていない中で、iPhoneでインストールしたアプリがApple TVにも自動的にインストールされることで、アプリを発見して起動してもらえる機会が増えそうです。

この後、iOSにはSiriのサードパーティー開放がアナウンスされましたが、tvOSにはSiriのサードパーティ開放はまだ来ないのでしょうか。Apple TVで個々のアプリを起動したり文字を入力したりするのは手間なので、特定のアプリへの命令をSiriで出来るようになったら大分と使いやすくなりそうです。

tvOSでHomeKitが利用できるようになることで、リビングに据え置かれた小さなコンピューターとして、スマートホームの中心的なデバイスになっていく進化を遂げるかどうか、楽しみです。

- OS X あらため、 macOS
  • 名称をmacOSに変更
  • 新しいバージョンの名前は Sierra(シエラ)
  • Continuity
    • Apple WatchをつけていればMacをAutoUnlockできる
    • iCloudにディスクトップも保存可能に。iPhoneからも閲覧可能
    • クリップボードがiOS端末と同期
  • Siriを提供
    • メッセージの送信、ファイル検索などが可能に。
  • ディスクの最適化
    • 使用頻度に応じてクラウドへの保存なども利用し大幅にディスクを圧縮
  • Apple Pay
    • Safariで利用可能に。指紋認証はiPhoneで行う。
    • 対応国にスイス、フランス、香港が追加
  • Picture in Picture
    • フルスクリーン上でも、動画を再生
  • タブ機能が標準で利用可能になる。デベロッパは特別な対応不要。

macOSのAutoUnlockはユーザーとして個人的にとても便利そうです。指紋認証を搭載したMacの新機種が発表されるのではないかという事前予測もあり(実際には新しいハードウェアの発表は一つもなかった)、ログインパスワード入力の手間が省けることを期待していましたが、AutoUnlockがあればそもそも指紋認証など無くてもログインパスワード入力の手間が省けます。

iOS - iOS 10に10の新機能が発表
  1. ロックスクリーン、通知の再デザイン
    • 端末を持ち上げるだけでスクリーンがOnに(Apple Watchの感覚)
    • プレビュー、メッセージ送信などリッチな通知をExtensionとして作成できる
    • コントロールセンターがページングし、ミュージックコントロール、Homeコントロールが可能に
    • ホームスクリーンを右にスワイプするとTodayExtensionのWidgetが並んでいる。
  2. Siriのデベロッパーへの開放
    • Siriにサードパーティのアプリを指定して命令が可能に(YouTubeなどから検索が可能)
    • Messaging、Ride booking、Photo search、Workout、VoIP、Car Play で利用可能
  3. キーボードでより高速な文書入力をサポートするQuick type
    • 文脈から判断してユーザーが入力する文字を予測して入力候補を出す
    • メッセージで相手の発言を解析して入力内容をサジェスト
      • 「今どこ?」という発言を受信したら、現在位置情報をサジェスト
      • 複数に渡る発言の文脈から予定の決定を読み取りカレンダーをサジェスト
  4. Photoアプリの新機能
    • ローカルでディープラーニングを利用して場所、テーマ、人、被写体などで写真を整理
    • ハイライトムービーの自動作成
  5. Mapの新機能とデベロッパーへの開放
    • カレンダーから位置情報付きの予定を表示
    • ナビゲーション
      • 交通ルールがある箇所では自然に拡大表示し細かく案内
      • ルート中にあるお店を検索することが可能
    • Extensionによってホテル予約やUberを呼ぶ機能を搭載可能
  6. Musicの新機能や新デザイン
  7. Newsアプリの新機能や新デザイン
    • 日本非対応
  8. HomeKitの新機能、Homeアプリの登場
    • 新しい家電カテゴリが追加
    • Homeアプリが登場
      • Siriに "Good morning" と言うと朝電源を入れたい家電に電源が入る
      • Control Centerを横スワイプでHomeKitのコントロールセンターでスイッチをコントロール
    • Apple TVにリモートからアクセス、Apple TVを介して家電をコントロール出来る
    • watchOSにもHomeKitはビルトイン
  9. Phoneアプリの新機能とデベロッパーへの開放
    • 電話主の推測が電話帳だけではなく他のアプリも一緒に検索
  10. Messageアプリの新機能とデベロッパーへの開放
    • ウェブサイトのURLが送信されるとサムネイルを読み込む、RichLink表示
    • 絵文字だけの場合大きさが3倍に
    • 画面が震えたり、指でなぞるとモザイクが消えて文字が読めエフェクトメッセージが登場
    • スタンプ機能が登場、スタンプを送信するだけでなく、画像やスタンプにスタンプを重ねられる。
    • 受信したメッセージへのいいね!などのリアクションを付けることが出来る
    • デジタルタッチによる手書きメッセージの送信が可能
    • メッセージアプリの中でメンバーが一緒に出前のオーダーを作成するなどのExtensionが作成可能に

怒涛のように新機能が発表されたiOSは、電話としてのiPhoneの根幹的な機能の部分にデベロッパーが手を入れられるようになったことは大きな変化だと感じました。

たとえばiPhoneでSkypeを電話の代わりに使おうと考えても、iPhoneの連絡先から直接Skypeで電話をかけることはできなかったり、通話の着信も通知で知らせるような形式になったり、利用者としても開発者としても困った部分でしたが、連絡先アプリとの連携も電話の着信も、かなり自然な形で機能の提供が可能になりそうです。

完全ローカルでディープラーニングを使った顔認識や写っている物体の識別(デモでは馬を識別していた)するのもすごいですね。Appleは顔認識による写真の分類などはGoogleやFacebookなどと比べて遅れを取っているように思われましたが、完全ローカルでセキュリティとプライバシーをしっかり確保して実現する方法を編み出した上で、満を持して投入した形でしょうか。

その他ここまでで発表しきれなかった多くの新機能
  • ノートアプリのコラボレーション機能で友達と一緒に編集が可能に
  • Macのメールアプリでメールを会話形式で表示することが可能に
  • Live Photoの編集が可能に
  • iPad Safariでの 画面分割表示が可能に
  • プライバシーについて
    • 発表したすべての機能は End to End で暗号化されAppleのサーバーは内容を把握しない
    • インテリジェンス機能はデバイス上で行いAppleはサーバーにデータを持たない
  • プログラミング教育をサポートする取り組み
    • 無料でiPad向けにSwiftコードを実行できるSwift Playgroundsの登場
    • コードを書くのに特化したキーボードを表示

Platforms State of the Union

基調講演で発表された内容をデベロッパー向けに掘り下げるような内容です。気になった箇所をピックアップして記載します。基調講演ほど丁寧に説明してくれないので、聞き取った内容は多少不正確な可能性があります。ご了承ください。

  • Messageアプリの中でやり取りしながら編集できるExtension
    • Extensionを使って注文オブジェクト的なものを作成する
    • Messageのグループ参加者でオブジェクトを編集する
    • オブジェクトタップするとExtensionが起動して品目・数量を編集・入力出来る
    • パズルゲームを、Message上でやり取りしながら組み立てて行ったりという用途もありそう
  • Swift 3の公開とともに、Swift 2.3も公開。
  • Xcode でカラーを文字列ではなく色のタイルで表せる
  • Xcode Extention が登場、Storeに公開する事ができる
  • Interface Builder
    • デバイス・サイズクラス毎の制約設定がやりやすく
    • 4段階でIBの画面を拡大・縮小して操作が可能に
  • Xcodeのデバッグ機能が強化
    • 参照関係のオブジェクトグラフで循環参照などを可視化
    • RunTimeのAutoLayoutの問題箇所を示してくれる
  • ネットワーク通信にプライオリティを付けて、よく使う重要な通信を優先して処理する
  • 新しいファイルシステムを macOS Sierra に導入
    • Flash/SSDへの最適化、レジリエントの強化、64bit最適化、暗号化、フルバックアップ
  • NSUserActivity によって、直前に見ていた場所へ "Siri、ここへ連れてって" と言って伝わる
  • macOSアプリはStoreから配布したものでなくてもCloudKitが利用可能になる
  • iCloud のデータを誰かと共有することができるようになる
  • Apple TVを操作するRemote.appは横向きでゲームコントローラのような持ち方が出来るようになる
  • sRGBよりも範囲の広いWide Color P3のAPIを公開し、RAWイメージにアクセスできるようになる

f:id:niwatako:20160614202049j:plain

ログのライブストリーミングというのが何を意味するのかよくわかりませんでしたが、iOS端末からWi-Fi越しにデバッグログを取得できるのだとしたら、Lightningコネクタで接続する外部アクセサリを利用した状態のデバッグが非常に楽になりそうです。

Apple Design Awards

f:id:niwatako:20160614195605j:plain


夜は Realm WWDC Livestreamed Swift Panel へ

終わった後はWWDC Livestreamed Swift Panelへ行きました。

Realm のロゴの前で記念撮影。

f:id:niwatako:20160614195536j:plain

Swiftについてのパネルディスカッションの会です。スライドもなく自由にパネラーがディスカッションしているのは、私の英語力ではなかなか聞き取るのは大変でした。。。とにかく英語をマスターしたい、いま最も学びたいのはiOS10でもSwift 3でも無く英語だ!と、聞いている最中は強く思ったのでした。

終わってからは参加者同士で交流

終わった後にパネラーや他の参加者の方々とコミュニケーションを取ることが出来ました。

try! Swiftに来ていた方々に聞き起こしでブログを書いていた(I'm try! Swift bloggerなどいう適当な表現で)と言ったところ、「あなたが!Twitterフォローしてるよ!」と言われて嬉しかったです。

運の良いことに彼らは日本語も話すことが出来て、私が聞き取れなかったり話せなかったりすると、相手の方が日本語で助けて下さいました。

一緒に帰るのも仲良くなるチャンス

サンフランシスコはストリートやエリアによっては日本ほど治安が良くなく、みんななるべく複数人で行動するようにしています。

参加者同士で交流していて、ホテルが近くだということが分かった者同士で一緒に歩いて帰りました。ただ挨拶したり名刺を交換したり会話をするだけよりも、ちょっと仲良くなれた気がします。


今回のWWDCには株式会社はてなからの出張として経費で参加することが出来ました。

また、はてなではSwiftを使ってiOSアプリを開発しています。今回のWWDCの基調講演のスライドの中にもSwiftで作られたアプリの一つとして、はてなブックマークアプリのアイコンが掲載されています。

そんな株式会社はてなでは "iOSアプリ開発実践コース" がある、はてなサマーインターン2016を開催します。

hatenacorp.jp

応募締め切りは2016年7月4日(月)となっています。宿泊費も出ますので、全国より奮ってご応募ください!

はてなブックマークのデータを用いた機械学習演習やAWSハンズオンなど・はてなサマーインターン講義内容決定!!

f:id:daiksy:20160525121351p:plain

はてなでのひと夏があなたをつくります

以前からお伝えしているとおり、はてなでは8月15日から9月9日までの期間でサマーインターンを開催します。

はてなインターンはカリキュラムが前半と後半に大きくわかれています。前半では講義や課題を中心とした学習プログラムを、後半では実際の開発チームにジョインしてサービス開発を体験していただく、という構成です。

後半のサービス開発は、今年は4つのコースをご用意しており、募集開始のタイミングでお知らせをしていました。

機械学習・アドテク! iOSアプリ! ブログ! Mackerel!「はてなサマーインターン2016」の募集を開始しました! - Hatena Developer Blog

この度、前半の講義課程のカリキュラムが決定しましたので追加でお知らせします。

Perl/ScalaによるWebアプリケーション開発トレーニング

インターン後半のサービス開発に必要なWebアプリケーション開発の基礎を学ぶ5日半のカリキュラムです。 Perl/Scalaの言語仕様やデータベース操作、Webアプリケーションフレームワークの扱い方などについて、講義と課題を組み合わせて学んでいきます。はてなの大規模サービスを支えるインフラに関する講義もあります。

選択コースによって、講義で使用する言語(PerlかScala)が決まり、また、4日目のカリキュラムがJavaScriptかSwiftを用いたiOSアプリ開発かにわかれます。

Web開発におけるコンピュータサイエンス〜機械学習講義〜

機械学習について学ぶ2日半のカリキュラムです。

はてなで機械学習を実際の機能開発に応用しているエキスパートによる講義に加え、大規模データを用いた実践課題を通して、機械学習の勘所と実データに対する応用の方法を見ていきます。

実践パートでははてなブックマークの実際の大規模データを使った実習を予定しています。

はてなブックマークでは研究目的でのデータ提供を認めており、そのポリシーに基いて講義に活用します。

はてなブックマークのデータを研究目的で利用したい場合のお問い合わせにつきまして - はてなブックマーク開発ブログ

【特別講義】AWSハンズオン

アマゾン ウェブ サービス ジャパン株式会社様より講師を招いてのハンズオンです。

AWS(アマゾン ウェブ サービス)の基本的なサービス、Lambdaなどのmanaged serviceについて概要説明とハンズオンを行い、モバイル系のサービスのデモを実施します。APIGW+Lambda+Cognito+DynamoDBでサーバレスアプリを構築する手順を、実際に手を動かしながら学びます。

8月20日土曜日に、特別講義という形式で開催します。

インターン申し込みは7月4日正午まで!

今回ご紹介したこれらの講義は、コースを問わずに参加者全員が受講できます。

それぞれの詳細は、以下のページをご覧ください。

はてなサマーインターン講義内容の詳細

昨年のインターンの様子は、下記のレポートサイトをご覧ください。

Swift 3.0 をいまから学ぶ Swift Evolution ウォッチング

おはようございます。シニアアプリケーションエンジニアの id:cockscomb です。WWDC が目前に迫ったいま、今秋にリリースが予定されている Swift 3.0 について、Swift OSS コミュニティの中心である Swift Evolution から読み取っていきたいと思います。


[PR]

本記事は、筆者が株式会社はてなの協賛を得て主催した「関西モバイルアプリ研究会 #14」において、“Swift Otaku — Nerdy Swift-Evolution Watching” と題して発表したものをブログの記事として再構成したものである。

関西モバイルアプリ研究会は、毎月一度、平日夜に京都や大阪で開催される、モバイルアプリ関連の勉強会である。次回の「関西モバイルアプリ研究会 #15」は6月22日水曜日に開催予定だ。

f:id:cockscomb:20160609234540p:plain


目次


Swift 3.0 はこの秋にリリースが予定されている。Swift 3 Release Process から考えると、1週間後の Apple World Wide Developers Conference (WWDC) 2016 に合わせて最初の Developer Preview バージョンがリリースされるはずだ。オープンソース化されたとはいえ、基本的には Apple のスケジュールに左右される。Swift 3.0 はいったいどのようなアップデートになるのだろうか。

OSS になった Swift は、swift-evolution メーリングリスト での議論や、GitHub の Swift Evolution にまとめられた proposal によって、将来の変更を決定している。これらを追うことで、Swift 3.0 やその先のリリースについて知ることができる。

Focus

Swift Evolution によれば Swift 3.0 は以下のことに焦点を当てるとされる。

  • API design guidelines
  • Automatic application of naming guidelines to imported Objective-C APIs
  • Adoption of naming guidelines in key APIs
  • Swiftification of imported Objective-C APIs
  • Focus and refine the language
  • Improvements to tooling quality

このほか portability もまた重要であることが明言されている。Swift Evolution で議論される proposal は、基本的にはこれらのトピックに関連しているはずだ。

ところで、当初 Swift 3.0 での目標とされていたいくつかのトピックが削除されていることに気付くだろうか。

Winding Down

5月半ばに Chris Lattner は、“Winding down the Swift 3 release” と題したメールをコミュニティに送った。そこには ABI の安定化と、そのために必要な generics の機能を完成を、Swift 3.0 のリリースから見送ることが書かれている。また Swift 3.0 では API naming という困難に向き合い、ソースコードレベルでの安定化が図られたということが強調されている。つまり将来のバージョンの Swift でも、可能な限りソースコードの互換性が維持されることが期待されている。そして、Swift 3.0 以降の 3.x や 4.0 については、今年の8月以降に議論を再開するとされた。

コミュニティからは ABI の安定化が見送られることについてネガティブな反応が見られたが、Chris Lattner を含む Swift Core Team は丁寧にそれに答えている。Objective-C の時代に起きた ABI に関する問題の例を挙げて、スケジュール上の圧力が ABI に問題を引き起こすのを避けるためには急ぐべきではないことを説明している。

ところで今回は見送られた generics の完成や ABI の安定化とは何だろうか。本論からは少し脱線するが、簡単に覗いてみよう。

Complete Generics

標準ライブラリの ABI を安定させるためには、標準ライブラリで本来は利用したいと考えられる generics の機能を実装する必要がある。“Generics Manifesto” と題されたドキュメントは、元は Swift Evolution のメーリングリストに投稿されたものであるが、そういった generics の展望を示したものである。Generics が拡張されることで、Swift の表現力もさらに改善される。

ABI Stability

Swift における Application Binary Interface (ABI) の安定とはつまり、異なるバージョンの Swift コンパイラでコンパイルしたバイナリがリンクできる状態である。このために Swift は、言語機能の重要な部分を確定させると共に、実行時のデータ構造や呼び出しの規約などを仕上げなければならない。標準ライブラリについてもそのインターフェースを確定させる必要がある。

ABI の安定化と共に、Resilience と呼ばれる機能も必要とされている。Resilience (弾力)Fragile (壊れやすい) に対応する語として使われている。Swift で書かれたライブラリが ABI 上の互換性を保つための機能である。

例えばライブラリの公開インターフェースとなっている struct に、新たに stored property を追加したらどうなるだろう。あるいは enum に後から case を追加できるとしたら、網羅性の検査に影響があるのではないか。

今のところの計画では、ライブラリはその API をバージョニングできるようになる。また stored property の追加やその定義順の変化は ABI を壊すことなく実現可能である。enum についても通常は case の追加を許容するが、case の追加が起きない @closed な enum を作ることもできる。一般に、後から変えても ABI 上の互換性を保つことは実行時のパフォーマンスに影響するが、必要な場合にこれを解消できるように、@inlinable な関数や @fixed_contents な struct を作ることもできる。

このような内容は “Library Evolution” と題されたドキュメントにまとめられている。ただしこれらは Swift Evolution での議論を経ておらず、実現する際にはいくらか違ったものになっているかもしれない。

Proposals

本題に戻って、Swift 3.0 に関して Swift Evolution でこれまでに議論されてきた proposal の中から、いくつか興味を惹かれるものを見ていこう。

Naming Guidelines

Swift の新しいネーミングガイドラインに関する proposal は多い。その中でも上記の3つが基本的なものである。新しいガイドラインは “API Design Guidelines” としてまとめられており、それを標準ライブラリや Objective-C からのインポートにも適用するところまでがこれらである。

関連する proposal は他にもある。

この中でも SE-0044 Import as member は、例えば Core Graphics などのフレームワークに見られる CGContextSetLineWidth(_:_:) のような、第一引数に(オブジェクト指向のイメージではメソッドのレシーバに相当する)struct への参照をとる(この場合は CGContextRef)関数を、context.lineWidth = width のようなフォームへ変えられる。Objective-C だけでなく、C の API まで Swift らしくすることができ、画期的である。

Core Libraries

Swift 3.0 では、Apple 以外のプラットフォーム向けにも Core Libraries として Foundation/Grand Central Dispatch/XCTest の3つが提供される。これに伴う proposal もいくつか存在している。

実は SE-0005 Better Translation of Objective-C APIs Into Swift の proposal には当初、Foundation から NS prefix を取り除くことが盛り込まれていた。しかし rationale にあるように、参照型のセマンティクスを持つ Foundation と値型のセマンティクスを持つ標準ライブラリの矛盾といったことを含むいくつかの理由からいったん見送られていた。

SE-0069 Mutability and Foundation Value Types では Foundation のいくつかの class について対応する struct を導入する。例えば NSDateDate のように、NS prefix の取り除かれた struct の導入で値型のセマンティクスを得る。

SE-0086 Drop NS Prefix in Swift Foundation は、ついに NS prefix を取り除こうという proposal である。基本的な方針は、Objective-C ランタイムと関連していたり、Apple のプラットフォームと関連していたりするものは NS prefix を残し、あとは prefix を削除するというものである。現在まだレビュー中であるが、多少議論が分かれている。

Grand Central Dispatch (GCD) の API を Swift らしくしようというのがこの proposal である。GCD はもともと C で API を提供しているが、例えば dispatch_queue_t 型を DispatchQueue 型にしたり、グローバル関数をメソッドにしたりすることで、より Swift らしいインターフェースに変えるというものだ。

Objective-C

Objective-C で書かれたコードとのインタラクションも Swift 3.0 ではさらに改善される。可能な限りコンパイル時に検証しようとするのは良いことである。

ところで、Objective-C の class でも property が利用できるようになる予定である。Swift のバグトラッカーに登録された Unknown property attribute 'class' を見ると(すでに修正されたが)、そのことがわかる。Apple Clang コンパイラは将来のバージョンで Objective-C class properties をサポートする予定だ。このように、Objective-C もまた Swift に合わせて進化しようとしている。

Swift Package Manager

Swift のエコシステムを強化するため Swift 3.0 から新たにツールチェーンに加わる Swift Package Manager (SwiftPM) もまた、活発に議論されている分野である。リリース前であるにも関わらず、専ら実用性を意識した proposal が多いようにみられるが、これはコミュニティの期待感のあらわれではないだろうか。

クローズドソースの Xcode が今年の WWDC 以降にどれくらい SwiftPM と連携していくのか、そういった期待もある。

Swift Language

純粋なプログラミング言語としての Swift にも、様々な変化が待っている。

可視性のスコープ

シンボルの可視性が整理される。Swift 2.2 までの private は、それが書かれたソースファイルの中からだけ参照できるというものだった。Swift の extension 機能には便利な場面がある一方で、他の言語とは一貫しない仕様である。Swift 3.0 からは、このような可視範囲を fileprivate として、新たにその定義中でのみ可視の private を加えた。これによって Swift の可視性のスコープは以下の4つになる。

キーワード 可視性
public モジュールの外部まで可視
internal モジュールの内部だけに可視
fileprivate そのファイルの中だけに可視
private 現在の定義の中だけに可視

関数

関数については、初期の Swift で柔軟だった点を、より厳密にするような方向性の変化が多い。特にコンパイラの実装を複雑にする割にそれほど活用されていないと考えられる仕様が取り除かれている。

ImplicitlyUnwrappedOptional

ImplicitlyUnwrappedOptional という型を Swift から無くし、! のついた型を @_autounwrapped 属性のついた Optional 派生型として取り扱おうという proposal。暗黙的に wrap される Optional 型は、特に型システムが比較的弱い Objective-C との連携のために必要ではあるが、そのような理由がなければ代替手段によって取って代わられるべきものである。このため必要のない場面では使われないように、制限を設けようというものである。

dynamicType

SE-0068 Expanding Swift Self to class members and value types では、インスタンスにおいて Selfself.dynamicType の意味で利用できるようになる。このことでインスタンスから型の持つメンバーへのアクセスが簡単になり、構文における意味も明瞭になる。

さらに SE-0096 Converting dynamicType from a property to an operator では、インスタンスからその型を得る .dynamicType property を dynamicType() 演算子に変える。これは dynamicType の働きが property というよりは sizeof などの演算子に近いのではないかという動機によるものである。

暗黙的な変換

SE-0072 Fully eliminate implicit bridging conversions from Swift では、Swift 標準ライブラリの型と Foundation の型の暗黙的な変換 (String から NSString のような) を一切やめて、必ず as でキャストすることになる。Swift 1.2 のときに Foundation から標準ライブラリの型への変換が無くなったが、Objective-C の lightweight generics 機能などによって多くの変換が不要になった現在では、暗黙的な変換をすべてやめたとしてもその影響は限定的であるとされた。

SE-0083 Remove bridging conversion behavior from dynamic casts はさらに、as によるキャストで変換するのもやめ、イニシャライザを利用しようという proposal である。コミュニティからのフィードバックもポジティブである。ところが前述のSE-0072 について、Objective-C の id 型に関連して極端な問題が起きるという懸念が発生しており、これについて明確になるまでは議論が延期されている。

コンパイラ制御文

#if canImport(ModuleName) というコンパイラ制御文によって、モジュールがインポート可能かどうかのテストを行えるようになる。

Generics

  • SE-0048 Generic Type Aliases (Accepted)
    • 型パラメータを持った typealias を定義できる。ただしあくまでも typealias であって、新たな型が作られるわけではない
  • SE-0081 Move where clause to end of declaration (Accepted)
    • 型パラメータに関連する where 節を、型パラメータの括弧 (<>) の中ではなく、定義本体の直前にする
  • SE-0092 Typealiases in protocols and protocol extensions (Accepted)
    • Swift 2.2 から protocol の associated type が typealias ではなく専用の associatedtype キーワードで表すことになったので、空いた typealias キーワードで protocol 中での typealias に利用できるようにする
  • SE-0095 Replace protocol<P1,P2> syntax with P1 & P2 syntax (TBD)
    • 複数の protocol に準拠している型を表す protocol<P1, P2> 構文を P1 & P2 という構文に変える。当初は Any<P1, P2> 構文が提案されていたが、P1 AND P2 ではなく P1 OR P2 と誤解する恐れがあるというコミュニティのフィードバックを受けて、論理積を表す & 演算子を使うように更新された。ただしこれは意味を明確にするためであって、論理和を表す | 演算子などは型システムがサポートできるものではない(サポートするべきではない)ことが表明されている

Generics Manifesto のうちのいくつかは、すでに proposal として議論が進められており、大半は承認されている。

Standard Library

標準ライブラリに便利なメソッドを増やそうとするような proposal は多い。どれも一度くらいはほしいと思った機能ではないか。


さて、ここまでトピック毎に多くの proposal を見てきた。Swift 3.0 が非常に巨大なリリースであることに疑いの余地はない。とてもたくさんの点で改善される Swift 3.0 が待ち遠しいと思う。しかし同時に、既存のプロジェクトを Swift 3.0 にマイグレーションするのは骨が折れるだろう。

Swift が OSS になって約半年、信じられないスピードで変化を続ける Swift は、いまや広くコミュニティに受け入れられている。変化の早い Swift を使いこなすためには、Swift Evolution をよく観察しておくのが早道だ。


株式会社はてなでは、未来の Swift への興味が抑えきれないエンジニアを募集しています。

hatenacorp.jp

また、はてなサマーインターン2016では Swift を使ったモバイルアプリケーション開発が体験できます。

「はてなサマーインターン2016申し込み」はこちら

ふるってご応募ください。