なるようになるかも

力は多くの場合、その人の思いを超えない。

Javaと偽Javaの話。

qiita.com

これの話。ブコメに書こうとしたら4000字は入らなかった。

Microsoft Java VM

かつての WIndows には MS 製の Java VM が搭載されていました。

古代の Java は「Write once, run anywhere」を掲げていた通り、クライアントサイドで Java アプレットとして利用されるのが主流でした(サーバーサイドで動くようになって、真価を発揮した感じがあります)。

しかし Java VM の仕様は、パフォーマンスについての記述は曖昧になっており、OS ごとの実装の違いによって、実行速度に顕著な差がありました。

Windows の Sun 純正の Java VM は性能が悪かったため、MS は独自の Java VM を開発し、Internet Explorer にバンドルしました。調子に乗った MS は Windows GUI ライブラリを利用できる、Sun Java VM と互換性のない Java 統合開発環境 Visual J++ をリリースしたりしていました。

これが 1997年に「Java の互換性を破壊した」として訴訟に発展、2004年に和解に至りましたが、現在 MS の Java VM は頒布されなくなりました。

Visual Studio 2015 で Android がサポートされ、Java モドキを再び書けるようになったとき、J++ の面影を思い出した人は少なくないはずです。J# は知らんです。

Apache Harmony

Apache 財団が開発を進めていた、オープンソースライセンスフリーJava 実装でした。

「自由な Java」を求めた IBMIntelGNU Classpath に関わってきたフリーソフトウェア開発者たちが一同に介して開発を進めた夢のプロジェクトでした。

プロジェクトの開始以来、多くの企業や団体がJava SEのオープンソース実装に対する賛同を示し、協力を申し出てきた。例えばIBMがコア・クラスやクラスライブラリ/VMのインタフェース、Eclipseプラグイン等の実装への協力を申し出ているほか、IBM developerworksは評価ライセンスでのJVMの提供に同意している。また、Intelはセキュリティや認証系、正規表現RMIなどの実装に協力すると表明している。オープンソース団体ではクラスライブラリの実装を行っているGNU Classpathプロジェクトが、Apache Harmonyとの提携を開始している。その他多くの協力を得た結果、現在はプロジェクトの大部分において実際に動作するコードが集まっているという。

JavaOne 2006 - オープンソースのJava実装「Apache Harmony」に期待

JavaOne というのは Sun が主催していた Java の祭典です。Sun のお膝元でプロジェクトの進捗が公表されていた Apache Harmony ですが、悲劇的な末路を辿ります。

Sun が JSPA(Java Specification Participation Agreement)の供与を拒否したため、Java テクノロジ互換キット (Technology Compatibility Kit) と呼ばれる「Java 互換を名乗る資格を得るためのテスト」を受けられませんでした。

Apache 財団は Sun に公開書簡を送りましたが、Sun からの返答はありませんでした。

やがて、Sun 自ら Javaオープンソース化する OpenJDK プロジェクトを始めると、それまで Apache Harmony に関わってきた開発者たちは撤退し、OpenJDK へ移行しはじめました。

IBMApple(当時の AppleCocoa Java Bridge など Java テクノロジに熱心だった)が OpenJDK への参加を表明したことで、Apache Harmony プロジェクトは事実上終結しました。

Android

Apache Harmony は、「Java 互換」を名乗ることができない偽 Java として天命を迎えるはずのプロジェクトでした。

その成果を利用したのが Google です。Android の当初の Java 標準ライブラリは Apache Harmony が採用されており、このため「本物の Java」と挙動が違いました。貧弱な端末で動作させるために機能を制約した Dalvik VM 上で動作していながら、JIT もなく性能面で大きく劣っていたりしました。

歴史の流れを見ると、Sun を買収した Oracle が「Java の互換性を破壊した」として Google と訴訟に至るのは疑問の方が強いです。なぜオープンソース指向へ変わりつつあった Sun の意思が失われたのか、また Google が MS の取った行動をなぞるかのごとくリスクを犯したのか。

APIフェアユースが認められた今回の裁判結果が一つの契機となってくれることを願うばかりです。

Android N 現在の Java 標準ライブラリは Apache Harmony を捨て、Open JDK を採用しています。Dalvik VM は ART ランタイム(Java のクラスファイルを、Dalvik VM 向けに変換して APK 化した後、更に端末上で AOT コンパイラが走り、ネイティブなコードに変換している)に置き換えられました。

その他の Java

Oracle Java VM は唯一絶対な Java VM ではありません。

たとえば、IBM の WebSphere で利用されている IBM J9 VM のように、公式にライセンス供与された Java VM が存在しています。

ガラケー時代に流行ったアプリは Java ME で書かれており、Sun とのライセンス契約が締結されていました。

これらの中には、MIDP(Mobile Information Device Profile)に従っていた EZアプリや S!アプリも存在していましたが、当時主流だった iアプリは DoJa プロファイルという互換性のない Java で動作していました。ただライセンス料金を徴収されていたので非互換の偽 Java ですが、正式なサブセットという扱いでした。

歴史との diff

Android 登場以前までは、業界関係者が皆協力しており、Oracle といえども JCP に則って仕様策定を進めていました。

MS のような Java の破壊者は Android 以前にもいました。

仕様を策定する際には、RI (Reference Implementation) (リファレンス実装) と TCK (Technology Compatibility Kit) (テスト群) も同時に用意します。これは、策定した仕様が現実的に実装可能であるかどうかを確認するため、および、第三者が仕様に則って実装をおこなったときにその実装の互換性を確認できるようにするためです。

API デザインの互換性を担保する上で、TCK を公開することの重要性は、「Practical API Design: Confessions of a Java Framework Architect(API デザインの極意)」などでも説かれていますが、Oracle は自社のビジネスの都合で TCK の利用に制約を掛け、「正当な Java」として認定する対象を選別しています。

それが Oracle のビジネスモデルだと思うので、しょうがないですね。

一方 Oracle は、JCP に則り、エキスパートを集めて Java API の仕様策定作業をしています。時間はかかりますが、Java の新バージョンで追加される言語仕様や API の設計が洗練されているのは、そういう理由です。

かつて OpenJDK を支えていた AppleIBM も今は Swift を盛り上げる方向にいっていますし、Java の進化が遅くなったのは仕様策定が綿密というよりも、業界の巨人の Java 離れが進んだ結果なんじゃないかなーと個人的には思っているのですが、どうなんでしょう?

JCP についてはまったく詳しくないので、的外れなのかも。

Oracle 提供の JDK / JRE に比べ、Android の品質はどうでしょうか? こちらは、平気でゴミクズのような API が公式 API として追加されます。

AndroidAPI の品質が悪いのは関係ないだろ!!!!

往年の HttpURLConnection とか SecureRandom とか酷かったですよね。Fragment も酷い(Fragment in Fragment の闇は深い)。

TCK のような互換性維持のためのテストセットもありませんから、String.indexOf(String,int) という基本 API に実装不具合があっても、それを事前に検出できずにリリースが行われることがあります。

現在の標準ライブラリの実装は OpenJDK ですのでご安心ください。N 以降だけですが!

Android 新バージョンの発表があると思うから、Google 主催の次のイベントが楽しみ!」と思っている時点で、Google に振り回されていることに気付くべきです。

今では Google I/OWWDC が盛り上がっていますが、かつては Java の祭典である JavaOne が活発でした。

ところで、あまり知られていない(?)ですが、JavaOneWWDC は日本でも開催されていました。(WWDC は世界開発者会議の略称なので、日本語版は JDC ですが)

最後に開催された JDC の大きなトピックは、「Cocoa on Windowsライセンスフリーで提供」(実現しなかった)だったので、いかにバブルな時代だったかというのが伺えるでしょう。

つまるところ、Sun や Apple に振り回されていたのが、GoogleApple に振り回されるようになっただけですね。

Anyパターンについて考えてみる

Swiftの標準ライブラリだと付属型を持ったプロトコルを変数に格納するため、慣例的にAnyなんとかってクラスを用意してる」というところまでが話の前提です。

Anyクラス実装者の責務

qiita.com

例によって、Pokemonプロトコルで考えてみます。

ポケモンではすべての技(move)を使いきった場合、攻撃を選択しても「わるあがき」しかできなくなります。これはすべてのポケモン共通の挙動なので、Protocol Extensionで実装すると楽ですね。

protocol Pokemon {
    associatedtype PokemonType
    func attack(move:PokemonType)
    func struggle()
}

extension Pokemon {
    func struggle() {
        // 相手にダメージを与えて自分のHPを1/4削る
    }
}

「わるあがき」は技タイプなしの特殊攻撃なので(ゴーストタイプにも当たる)、PokemonTypeには依存しないものとして実装されるとします。

残念ながら、現世代までのポケモンで「わるあがき」で特殊な挙動をする個体はいないみたいですが、将来的に特殊な実装をされる可能性を考慮すると、適切に実装を退避させる必要があります。

class AnyPokemon <PokemonType>: Pokemon {
    private let _attack: ((PokemonType) -> Void)
    private let _struggle: (() -> Void)
    
    required init<U:Pokemon where U.PokemonType == PokemonType>(_ pokemon: U) {
        _attack = pokemon.attack
        _struggle = pokemon.struggle
    }
    
    func attack(type:PokemonType) {
        return _attack(type)
    }
    
    // 省略した場合はデフォルト実装が呼ばれてしまう!
    func struggle() {
        return _struggle()
    }
}

省略するとデフォルト実装になってしまう、というのがポイントです。

Anyタイプを作る開発者は、Protocol Extensionによるデフォルト実装も含めた、そのプロトコルの全ての処理を知る必要がある のです。

AnySequenceを実現している、_SequenceBoxの場合どうなってるのかというと、必ずしも全ての処理が委譲されるわけではないらしく、たとえばdropFirst(_ n: Int)は委譲されるのだけれど、dropFirst()(引数なし版)は委譲されません。

dropFirst()を変な処理でオーバーライドすると壊れます。やる人いないと思いますけど。

Protocol ExtensionでAnyパターンを壊す

SequenceTypeにProtocol Extensionでメソッドを足してみます。

extension SequenceType {
    func hogehoge () {
        print("do")
    }
}

let sequence = AnySequence(["a", "b"])
sequence.hogehoge() // do

AnySequenceSequenceTypeに準拠しているので、Protocol Extensionの恩恵を受けます。ここまでは期待通りです。

ただし、独自のSequenceTypeを作って、Protocol Extensionで追加したものと同名のメソッドを実装すると破綻します。

class MySequence : SequenceType {
    private var values: [String] = ["a", "b"]
    
    func generate() -> IndexingGenerator<[String]>{
        return values.generate()
    }
    
    func hogehoge() {
        print("inherit")
    }
}

let original = MySequence()
let boxed = AnySequence(original)
original.hogehoge() // inherit
boxed.hogehoge() // do
original.dropFirst().hogehoge() // do

これは_SequenceBoxの実装を考えれば自明ですね。dropFirst()でもデフォルト実装が呼び出されてしまうのは、dropFirst()Anyでラップする実装だからですね。

// 何も考えずに、Github上で master branch を見ていたので、
// Swift 2.2じゃなくて3.0のコードですが、大差ないので気にしない
@warn_unused_result
public func dropFirst() -> SubSequence { return dropFirst(1) }

@warn_unused_result
public func dropFirst(_ n: Int) -> AnySequence<Iterator.Element> {
  precondition(n >= 0, "Can't drop a negative number of elements from a sequence")
  if n == 0 { return AnySequence(self) }
  return AnySequence(_DropFirstSequence(_iterator: makeIterator(), limit: n))
}

結論

CollectionTypeなんかの組み込み型に対してProtocol Extensionする場合、Anyで型消去されたときに、Protocol Extensionのデフォルト実装しか呼び出されない点について注意が必要?

これは言語仕様的に防御した方がいいような気がする…何のメリットもないし。

AnySequenceってなんなの?

AnySequenceってなんなの?っていうのを調べようとしたメモです。

JavaGenericsは本当に型情報を消し去るイレイジャなのでまず意識しなくていいし、C#(4.0以上)のGenericsは共変性・反変性を持っているので、Swiftだとなんでややこしいコードになるのかよくわからなかったのです。

まず、SequenceTypeプロトコルに準拠した独自のクラスを作ることを考えます。

// 本
struct Book {
    var name: String
}

// 本棚
final class BookShelf: SequenceType {
    private var books: [Book] = []

    func append(book: Book) {
        self.books.append(book)
    }
    
    // Swift 3.0 だと AnyIterator とかいうストレートな名前になるはず
    func generate() -> AnyGenerator<Book> {
        var index : Int = 0
        return AnyGenerator<Book> {
            // index++ を Swift 3.0 準拠で書くとこうなる、微妙…
            defer {
                index = index.successor()
            }
            return index < self.books.count ? self.books[index] : .None
        }
    }
}

SequenceTypeに準拠することで、自作のクラスでもfor-in構文を利用できます。

let bookShelf = BookShelf()
bookShelf.append(Book(name: "ハイペリオン"))
bookShelf.append(Book(name: "ハイペリオンの没落"))
bookShelf.append(Book(name: "エンディミオン"))
bookShelf.append(Book(name: "エンディミオンの覚醒"))

for book in bookShelf {
    print(book.name)
}

ここまでは簡単です。

問題は、「BookShelfという実装詳細を隠したい場合はどうすればいいか?」と考えたときです。SequenceTypeプロトコルとして扱うという素直なアプローチは使えません。

let sequence: SequenceType = bookShelf

以下のコンパイルエラーになります。

error: protocol 'SequenceType' can only be used as a generic constraint because it has Self or associated type requirements

これがなぜかというと、SequenceTypeの定義を見ると分かるように、

public protocol SequenceType {
    associatedtype Generator : GeneratorType
    associatedtype SubSequence
    // 略

SequenceTypeは付属型(associatedtypeになって意味がわかりやすくなりましたね!)を持っているためです。Swiftの仕様上、付属型を持つプロトコルはgenericの制約としてしか使えません。

GeneratorTypeSubSequenceを具体化したクラスが必要です。そこで、AnySequenceの出番です。

let sequence: AnySequence<Book> = AnySequence(bookShelf)

for book in sequence {
    print(book.name)
}

AnySequenceでラップすることで、BookShelfの実装詳細を隠蔽することができました。

ただし同じElementの型でまとめるのが限界です。AnySequence<AnyObject>AnySequence<Book>を突っ込むみたいな融通は効かない(共変性とか反変性みたいなのはない)です。

ポケモン型消去

この辺の説明をポケモンを使ってすっごいわかりやすく説明したのが、ポケモン型消去の話です。

niwatako.hatenablog.jp

qiita.com

以下、Sequenceよりもポケモンの方が分かりやすそうなので、ポケモンで考えます。

この説明ですが、初見ではAnyPokemonattackの実装をイニシャライザで退避させている理由がわかりませんでした。

class AnyPokemon <PokemonType>: Pokemon {
    private let _attack: ((PokemonType) -> Void)

    required init<U:Pokemon where U.PokemonType == PokemonType>(_ pokemon: U) {
        _attack = pokemon.attack
    }

    func attack(type:PokemonType) {
        return _attack(type)
    }
}

これは実際に手を動かせば答えはすぐに出ました。PokemonTypeAnyPokemonに渡すポケモンの型に束縛される―たとえばイニシャライザにAnyPokemon<Electric>ピカチュウを渡して初めて、attackの型がattack(type :Electric) -> ()に決定される―からです。

先の説明の通り、付随型を持つプロトコルのままポケモンを保持することはできないので、束縛された後の実装を保持するようにしているのです。でもプロトコルのすべての実装を、イニシャライザで退避させるようなAnyクラスを作るのって正直エレガントだと思えません。

そこで、AnySequenceがどうやってSequenceTypeの各処理を移譲しているのか調べようと考えたのです。

AnySequenceは言語仕様内で型安全と型消去を両立している

下調べしたとき、Swift 2.1のバグにちょうどそんなのがあったのでちょろいと思ったのですよ。

AnySequenceのイニシャライザでは、元のシーケンスをさらに_SequenceBoxにラップして処理を移譲していたのですが、S.Generator.ElementS.SubSequence.Generator.Elementとの型が別になるような、変なGeneratorを実装した場合に、移譲がうまくいかないのでイニシャライザに制約を加えたとのこと。

だから、_SequenceBoxの実装を探して終わりだと思ったんですけど、これは自動生成されているボイラーコードなので単純には見つかりませんでした。

自動生成されているコードを、ポケモンの例に置き換えるとたぶんこんな感じになるのだと思います。

gist.github.com

繰り返しますがSwiftでは付随型を持つPokemonプロトコルを型として持てません。

そこでまず付随型をジェネリクスにした_AnyPokemonBaseという抽象クラスを作り、型として保持できるようにします。もちろん、Swiftの言語仕様に抽象クラスがあるわけではないので、空実装を提供した「抽象クラスっぽい何か」です。

そしてその_AnyPokemonBaseを継承した_AnyPokemonBoxに実際のポケモンをラップします。実際のPokemonプロトコルの処理はここから移譲します。

2段階ラップして、抽象クラス経由で操作することで、型消去したい型を保持する(日本語が迷走してる)、というようなことを実現しています。Pokemonプロトコルに機能が増えた場合にはボイラーコードを書くことになることには変わりありませんし(StdLibはこの辺は自動化している)、素直にabstractなクラスが言語仕様にあった方が幸せな気がするのですが、仕組みとしてはうまいと思いました。

AnySequenceの場合、内部にAnyGeneratorを持っているのでより複雑ですが、ほぼ同じ構造です。きっと。

なお、以下の記事を大変参考にしました。

ほぼ真似と言っても過言ではありません。こんなの自力で辿り着けないです…。

いまさら Android 6.0 の Permissions at Run Time について

ここに書いてある内容が正しいという保証が一切持てないので、けっして鵜呑みにしないでください。

とりあえず公式ドキュメントのRequesting Permissions at Run Timeをざっくり読みました。

ランタイムパーミッションの概要

Andorid 6.0(API level23)以降では、ユーザーはインストール時ではなく、アプリの実行中にパーミッションを付与します。

このアプローチにより、ユーザーはアプリのインストールやアップデート時に権限を付与する必要がなくなったため、インストールプロセスがスリムになります。

それだけでなくユーザーはアプリの機能単位で、より詳細な制御を行うことができます。

例えば、カメラアプリが「カメラ」と「位置情報」の機能を要求する場合、「カメラ」へのアクセスを許可し、「位置情報」には権限を与えないことができます。

ユーザーはアプリの設定画面へ行くことで、いつでも権限の取り消しを行うことができます。

今までのパーミッションシステムには以下のような問題がありました。

  • 今まではインストール・アップデート時に必要な権限が長々と並んで、ユーザーが許諾しないとアプリを入れられない仕組みだったけれど、インストール時点で「何のためにその権限を要求しているのか」ということは分かりづらかった。
  • 説明がユーザーにとっては直感的ではない上に、ネタみたいなのもあった。android.permission.BRICK(あなたの端末を文鎮化する可能性があります)とか。
  • すべてのパーミッション要求を許諾しないとインストールできない仕組みだったため、アプリのコア機能に不要な付加的なパーミッションも許諾する必要があった。
  • 開発者でもないとその権限で何ができるのかは分からないので、危険なパーミッションがそれと知らずに紛れ込んでいる可能性があった。

ただし全ての問題が解決したわけではないですし、実装する側にとっては面倒きわまりない話です。これはそんな話です。

パーミッションのON/OFFができる粒度

パーミッションには膨大な数がありますが、それぞれの一つ一つについて、ON/OFFの制御をすることは できません

motida-japan.hatenablog.com

上記の記事が非常に参考になるのですが、パーミッショングループの単位でのみ設定が可能です。

パーミッショングループ

パーミッショングループについて補足しておくと、これはもともとパーミッションをカテゴライズしておくことで、インストール時に整理された状態で表示する機能でした。

f:id:quesera2:20160429124931p:plain

古のインストール確認画面はこんな感じで、「現在地」や「ネットワーク通信」のように分かりやすく分類し、優先度の高いものが上に並ぶように並べ替え、そして本当に危険なパーミッションを「その他」に追いやって分かりにくくするための機能でした。

パーミッショングループ単位での指定の問題

「より詳細な制御」はできない

例えば「カレンダー」というグループ単位でONにすると、「カレンダーデータの読み込み(android.permission.READ_CALENDAR)」と「カレンダーデータへの書き込み(android.permission.WRITE_CALENDAR)」の両方を許諾したことになります。

アプリから「電話」を掛けられるようにONをすると、android.permission.READ_PHONE_STATEが許諾されます。「電話の状態の読み取り」という牧歌的な名前に反して、電話番号から端末の一意識別子まで抜ける超危険なパーミッションのひとつです。

もちろん、マニフェストに宣言されていないパーミッションまでは取得できません。要求する権限は最小限にしましょう。

また、ユーザー視点としては、Permissions at Run Timeがあっても安心せずに、アプリが要求するパーミッションを見定める必要があります。

許諾状態は常に変わる可能性がある

このパーミッショングループの分類について、将来的に変更される可能性があります。

例えば「カレンダーの読み込み」と「書き込み」が分離されたとして、その場合は一度「カレンダー」としてパーミッションの許諾を得ていたとしても、再度パーミッションを取得する処理を入れる必要があります。

また、ユーザーは任意のタイミングで許諾の取り消しを行うことができます。このため、常に最新の許諾状態を取得する必要があります。

例えばカーナビアプリが定期的にバックグラウンドで位置情報を取得している場合、設定から位置情報の許諾を取り消された場合ってどうすればいいんでしょう?

フォアグラウンドのみのアプリならonResume()での確認でよいのではと思うのですが、Android Nのマルチウインドウで設定を変えられたら?

許諾状態は常に変わる可能性を踏まえて、パーミッションの確認、要求ダイアログの表示などを自前で実装する必要があります。

パーミッショングループと危険性には相関がない

もともとAndroidパーミッションにはProtectionLevelという概念があり、個々のパーミッションについてnormaldangerousなどの格付けがありました。

この部分は正直確証がないのですが、Android 6.0ではこの仕組みを投げ捨てていているように思います。

Normal PermissionDangerous Permissionに大分し、前者はユーザーの許諾を必要としないパーミッション、後者は必要とする(=パーミッショングループに定義されている)パーミッションというように再構築されているような…。

その結果、パーミッショングループに含まれない、dangerousパーミッションは現状では許可を取る必要なかったり、整合性を取るためにandroid.permission.INTERNETdangerousからnormalに格下されたりと不穏な感じがします。

どう実装すればいいのか

ここからが本題です。

Android Nが出る時代に、targetSDKを22にしたときの話をしてもしょうがないので、targetSDKを23以上にして開発するものとします。

また、requestPermissions()を投げて、onRequestPermissionsResult()で結果を得る、という基本的な部分は理解しているものとします。この部分についてはいくらでも情報があるので、特に困ることはないでしょう。

パーミッションを得ているかどうか知る方法

checkSelfPermission()

これは、PackageManagercheckPermission(String permName, String pkgName)シンタックスシュガーです。

checkPermission()メソッドはAPI Lv1からあり、指定したパッケージのアプリがパーミッションを宣言しているかどうかを知ることができました。

Android 6.0ではこの意味合いが変わり、「パーミッションを宣言しており、かつユーザーに許可されているかどうか」に変わりました。

checkSelfPermission()ではパッケージ名の指定が不要で、アプリ自身のパーミッションの取得状態を知ることができます。

ただし、checkSelfPermission()API Lv23以降にしかありません。そこで、互換ライブラリのContextCompat.checkSelfPermission()を使うことができます。

実装は単純で、単に自分のパッケージ名を明示的に指定して、checkPermission()を呼び出しているだけです。

このAPIを使うことで、

を知ることができます。

ただしまだrequestPermissions()を呼んでいない場合には、PERMISSION_DENIEDが返ってきます。(targetSDK23以上の場合。22以下ではインストール時点で自動的にパーミッションの承諾が行われるので、ユーザーが明示的に設定を変えない限りはPERMISSION_GRANTEDが返って来る)

PermissionChecker

サポートライブラリには、PermissionCheckerというクラスもあり、checkSelfPermission()など同名のメソッドが用意されています。

これが何のために存在するのかというと、Android 4.3のAppOpsという隠し機能 のためです。

ご存知の方はみんな知っての通り、Android 6.0で追加されたPermissions at Run Timeの原型とも言える仕組みは、Android 4.3の時点から隠し機能として搭載されていました。

恐らくですが、

こうした状況では、PermissionCheckerがより適切です。App Opsによって剥奪されたパーミッションは、通常のrequestPermissions()ではPERMISSION_GRANTEDが返却されますが、PermissionCheckerではAppOpsManagerに問い合わせて、PERMISSION_DENIED_APP_OPという結果を返してくれます。

ContextCompat.checkSelfPermission()PermissionChecker.checkSelfPermission()のどちらを使うべきなのでしょうか。PermissionCheckerを推されている記事もありますが、公式ドキュメントではContextCompatを使うことを推奨しています。

個人的には、今後はtargetSdk 23以降が基本となり、App Opsに対応しなければならない状況はほとんどなくなると思いますので、PermissionCheckerの存在は忘れてしまっても支障はないと思います。

パーミッションのリクエストを得る方法

requestPermissions()

例によって、以下の二つのメソッドがあります。

  • Activity#requestPermissions()
  • ActiivtyCompat#requestPermissions()

引数は取得したいパーミッションの配列(Manifest.permission以下のString定数)と、リクエストコードです。

オリジナルのrequestPermissions()メソッド、およびコールバックとなるonRequestPermissionsResult()は当然ながら、API level23以降のActivityにしかありません。

ActiivtyCompat#requestPermissions()は内部でバージョン判定を行ってくれるのので便利なのですが、両者の仕組みはちょっとだけ違います。

ほとんどの場合、AppCompatActivityやv4パッケージの偽Fragmentを使っていると思うのですが、ActiivtyCompat#requestPermissions()でリクエストした場合、FragmentActivityが実装しているActivityCompat.OnRequestPermissionsResultCallbackインターフェースで結果が処理されます。

このとき、コールバック先がFragmentまたはActivityかの振り分けを16bitのシフト演算で行っています。これはFragmentActivity#startActivityForResult()の原理をご存知でしたらお馴染みですね。ActiivtyCompat版を使う場合には、リクエストコードの値が16bitを超えないようにする必要があります。

ActiivtyCompat版では、 Android 6.0未満の端末でrequestPermissions()を呼んだ場合、即座にonRequestPermissionsResult()コールバックメソッドが呼び出される という違いがあります。

つまりAndroid 6.0未満がターゲットに入っていても、パーミッションリクエストが存在するものとして書いてしまえば、同じ処理で書けるのでは?

www.slideshare.net

PERMISSION_GRANTEDパーミッションに対して、requestPermissions()を行った場合に、何度でもリクエストのダイアログが表示されるという仕様らしく、この方法はダメみたいですね…。

バーミッションが拒絶された場合

  • Activity#shouldShowRequestPermissionRationale()
  • AppCompat#shouldShowRequestPermissionRationale()

もう2つある意味の説明は不要ですよね?

さて、一番良く分かんないメソッドがこれです。メソッドの意味としては「リクエストの根拠を示すべきか?」です。

とりあえず原文にあるサンプルコードを訳してみます。

// 以下、thisActivityが現在のActivityであるとする
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

    // パーミッションを必要とする説明を表示する必要があるか?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {

        // ユーザーへの説明を*非同期*で表示 ―決してブロックしてはいけない―
        // このスレッドはユーザーの応答を待っています!
        // ユーザーが説明を見た後、再度許可の要求を行ってください
        // 註)原文中のexpanationはexplanationのtypoとみなしました

    } else {

        // 何の説明も必要がありません、パーミッションの要求をすることができます
        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS はアプリが定義したint型の定数です
        // コールバックメソッドは要求の結果を得ることができます。
    }
}

まずパーミッションのチェックを行って、PERMISSION_DENIEDの場合に使うものみたいですね。

このメソッドについて理解するには、パーミッションダイアログの初回表示と2回目以降表示、そして「今後表示しない」が選択されたときの挙動を理解する必要があります。

f:id:quesera2:20160429155616p:plain

分かりやすく表にしました

最初からこの表を用意すれば8000文字も書かなくて良かった気がしますね。

メソッド Android 6.0未満 初回表示 2回目以降表示 今後表示しない
checkSelfPermission GRANTED DENIED 前回選択に順ずる DENIED
requestPermissions GRANTED パーミッション許可
ダイアログ表示
パーミッション許可
ダイアログ表示
(「今後表示しない」付き)
DENIED
shouldShowRequest
PermissionRationale
false false true false

※全部AppCompatでやっているものとします。

初回の問い合わせにおいて、checkSelfPermission()PERMISSION_DENIEDshouldShowRequestPermissionRationale()falseを返します。そのままrequestPermissions()を投げましょう。

一度リクエストが拒絶されたかどうかは、shouldShowRequestPermissionRationale()を呼ぶことで知ることができます。

そこで、2度目のrequestPermissions()の前に、なぜパーミッションを必要とするのか、その根拠を説明することができます。この処理は別途ダイアログを表示するなど、非同期で行う必要があります。もしユーザーが心変わりした場合だけ、requestPermissions()を投げましょう。

「今後表示しない」が選択された場合、もはや弁解の余地はないのでshouldShowRequestPermissionRationale()falseを返しますし、requestPermissions()を投げても即断でPERMISSION_DENIEDが返されます。

どうしても必要なパーミッションを拒絶された場合

もし「今後表示しない」が選ばれたパーミッションを、必ず要求するアプリの場合、どうすればいいのでしょうか?

アプリ設定を変更して貰うようなダイアログを表示するフローを用意するのが現実的です。しかしこれは「二度と表示するな」というユーザーの意思を尊重したものとはいえません。

shouldShowRequestPermissionRationale()trueを返したときに、「なぜこのアプリがパーミッションを要求するのか」という論理的な説明を行い、ユーザーに納得して貰うというのが最良の体験になるのではないでしょうか。

「今後表示しない」が選択されたことを知る方法

stackoverflow.com

onRequestPermissionsResult()コールバック内で、結果がPERMISSION_DENIEDかつ、shouldShowRequestPermissionRationale()falseを返した場合、「今後表示しない」が選択されたと判定することができます。

つまり、 必ずrequestPermissions()を呼ぶ必要があります 。もし既に「今後表示しない」が選択されている場合に、そのパーミッションが必要なボタンをdisabledにしておいたり、Snackbarでさりげなく通知するといった気を利かせる方法はありません。

個人的にはこの仕様は、Permissions at Run Time の欠陥だと思っています。公式ドキュメントのサンプルコードだとこのケースについて何も考えてないので注意です。

ちなみにiOSの世界では

iOSでは、マイクや位置情報を利用するAPIの初回に利用許可を求めるダイアログをOSが自動的に表示します。

アプリケーションを再インストールしない限り、再度表示されることはありません。設定から変更して貰う必要があります。

requestPermissions()のタイミングに注意しつつ、shouldShowRequestPermissionRationale()は2回目以降ではtrueを返す性質を利用すれば簡単にiOSと同じ挙動にできて便利ですね!

まとめ(rev2)

Permissions at Run Time は調べれば調べるほど闇が深い…。

Swiftは結局どうオープンソースになったのか?

オープンソース化ばんざーい!!とかそういうのは全然興味ないです、ごめんなさい。

XCode7で何気なくimport Foundationなどをすると、ついでに以下のライブラリがリンクされるそーです。

  • SwiftCoreSwiftのコア言語仕様)
  • DarwinUNIXベースのOSX/iOSの基盤部分。CoreFoundationもここに含まれる)
  • DispatchGrand Central Dispatch
  • CoreGraphics(描画処理の基盤部分。今はOpenGLだと思いますがそのうち中身がMetalになるのでしょうね)
  • ObjectiveCObjective-Cランタイム関数)
  • Security

Swiftオープンソース化される、と言っても本当にpure Swiftコンパイラだけが提供されてもあまり意味がないので、「どこまでがどうオープンソースになるのか?」というのが興味の焦点だったかと思います。

UIKitAPIを呼び出すだけで簡単にアプリケーションを作れるiOS開発とは別物になる」、というのはまず前提として、

  • Darwinの互換実装までが提供される。いまいち誰得な言語になる。
  • Foundationまでが提供される。ObjectiveCの呪縛から逃れられない。

の2択かなーと思ってたのですが、

  • CoreFoundationLinux実装と、その上に Swiftで書かれたFoundation を提供する。

という意外な答えでした。

Swift自身でFoundationを記述している、というのは興味深く(普通のSwiftObjective-CFoundation実装を利用しているだけなので)、ここがオープンソース版との一番の違いになると思います。

Foundationの行方

github.com

を見てみると、アプリケーションフレームワークの基礎として今後もFoundationを生かしたいというようなことが書いてあります。ただ、掲げられているゴールはちょびっと変わっています。

  • Make software development easier by introducing consistent conventions for things such as deallocation.
  • Support Unicode strings, object persistence, and object distribution.

が、

  • Make software development easier by introducing consistent conventions.
  • Support internationalization and localization, to make software accessible to users around the world.

にそれぞれ変わっています。

前者はメモリ管理がARCになったことで「例えばメモリ解放のような」という喩えの意味がなくなったからでしょうか。ARCでもメモリ管理を意識しないと簡単にリークするのは変わらないので、別にそこ変えなくてもよくない?と思うのですが。

後者はNSStringはUTF16のラッパー実装でしたが、今のUnicodeコードポイントは絵文字の侵食で21bitになってたり、オブジェクトをシリアライズして永続化するという発想そのものが時代に合わなくなってきたので表現を変えた感じでしょうか。

NSUnimplemented()で検索検索っ。

なお、現在のオープンソースSwiftFoundationは未実装な部分が多かったり、Objective-Cランタイムには存在するクラスがなかったりします。

以下のような注意書きがあります。

Important: This project is in the early stages of development. It is not yet ready for production use, but it is ready for contributions. It is scheduled to be part of the Swift 3 release.

<私訳>

重要: このプロジェクトは開発の初期段階にあります。これはまだ製品での使用はできませんが、コントリビューションの準備は整っています。Swift 3のリリースの一部として予定されています。

動的型付けを生かしたクラスクラスタの仕組みなんかは抹消されるんでしょうかね。SwiftにはKVCがないので、それを土台にしているKVOもありません。さらにKVOの上に乗っかってるCocoa Bindingsは時代を先取りしすぎた感じですね。

一方で、NSZoneのような今や無意味なクラスが生き残っていますが、これは「最初の1年はFoundationの可搬性を実現することに注力し、その目標と相反するAPIのビッグバンを回避する」というような意味合いがあるみたいですね。

Foundation without Objective-C runtime

This project provides an implementation of the Foundation API for platforms where there is no Objective-C runtime. On OS X, iOS, and other Apple platforms, apps should use the Foundation that comes with the operating system. Our goal is to abstract away the exact underlying platform as much as possible.

<私訳>

このプロジェクトは、Objective-CランタイムのないプラットフォームのためのFoundation APIの実装を提供します。OSXiOS、その他Appleのプラットフォームにおいては、アプリケーションはオペレーティングシステムに付属しているFoundationを利用するべきです。我々のゴールは、可能な限り精密な基盤となるプラットフォームを抽象化することです。

現状では、OSXでFoundationが不完全なオープンソースSwiftを使う意味はないので、XCodeで使えばいいってことですね。

これが現状だけを指すのか、Objective-Cランタイムを必要としないFoundationをオープンソースで開発し、いずれOSXiOSもそちらに移行するのでしょうか…?

さよならNSプレフィクス

ところで、このリポジトリの文章は全部面白いので読んだほうがいいです。すでにバイナリのあるコードをビルドするより有益な情報が得られます。いくつか面白かったところを取り上げます。

API Naming and Foundation

One of the goals of the Swift 3 project is a new set of naming guidelines. The Foundation project will soon update all of its names to match the new guidelines. We will also drop the 'NS' prefix from all classes.

<私訳>

API命名規則とFoundation

Swift 3 プロジェクトのゴールのひとつとして、命名規則の一新があります。Foundationプロジェクトはいずれ新しい命名規則と一致するように、全ての命名が更新されるでしょう。加えて、全てのクラスから'NS'プレフィクスを取り除きます。

FoundationSwiftで再実装して、その上に載っているフレームワークも全部作り直すのでしょうかねぇ。値型のSwift Arrayと参照型のNSArrayはどういう区別になるんです?

How do we decide if something belongs in the standard library or Foundation?

In general, the dividing line should be drawn in overlapping area of what people consider the language and what people consider to be a library feature.

For example, Optional is a type provided by the standard library. However, the compiler understands the concept to provide support for things like optional-chaining syntax. The compiler also has syntax for creating Arrays and Dictionaries.

On the other hand, the compiler has no built-in support for types like NSURL. NSURL also has ties into more complex functionality like basic networking support. Therefore this type is more appropriate for Foundation.

<私訳>

どのようにして標準ライブラリまたは、Foundationのいずれに属するものだと判断すればよいですか?

一般に、言語の機能の一部だと考える人々と、ライブラリの機能であるべきだと考える人々を重ね合わせた境界で分割されるべきです。

例えば、Optionalは標準ライブラリで型として提供されています。しかし、コンパイラはoptional-chaining構文などをサポートするためのコンセプトを理解しています。コンパイラはまた、配列や辞書を生成するための構文を持っています。

一方で、コンパイラはNSURLのような型は組み込みでサポートしていません。NSURLは基本的なネットワークのサポートなど、より複雑な機能との関連性を帯びます。よって、この型はFoundationの方がより相応しいです。

Swiftにおける標準ライブラリは現状に近いミニマルな構成のまま、Foundationに機能を持たせていくみたいですね。Swift 2でNSURLに関連付いたNSStringメソッドStringの拡張に含めるのをやめましたが、NSURLFoundationのものだから標準ライブラリからは隔離したい、というような意図だったのでしょうか。(参照型であることを前提にしているNSPathStore2を値型のStringにキャストすることで起きるパフォーマンス劣化回避が目的だと思っていたのですが)

Why not make the existing Objective-C implementation of Foundation open source?

Foundation on Darwin is written primarily in Objective-C, and the Objective-C runtime is not part of the Swift open source project. CoreFoundation, however, is a portable C library and does not require the Objective-C runtime. It contains much of the behavior that is exposed via the Foundation API. Therefore, it is used on all platforms including Linux.

<私訳>

なぜ既存のObjective-CによるFoundation実装をオープンソースにしないのですか?

Objective-Cおよびそのランタイムで書かれたDarwinのFoundationは、Swiftオープンソースプロジェクトには属しません。しかしながら、CoreFoundationは可搬性のあるCのライブラリであり、Objective-Cランタイムを必要としません。これにはFoundation APIを通じて公開されている振る舞いの多くが含まれています。そのため、Linuxを含む全てのプラットフォームで使われています。

Objective-CのFoundation実装を公開すればいいじゃん?」っていう疑問は、「それはSwiftのプロジェクトとは関係ないから」という正論で一蹴していますが、Foundationを別のオープンソースプロジェクトにすればよかっただけな気もするのですよね。

Swiftオープンソース化はそこから言語コミュニティが活発化してSwift 3へ進化することで結実するって感じなのですかねー。

そのときSwift 2.Xの資産は全てゴミになると思われますが…。

すたっく・おーばーふろー

blog.jnito.com

12位に載ってたので。

おそらくStackOverflowに多くの人が求めているのは「高品質なナレッジ」であり、そのため情報は英語に集約されるべきで、日本語版は不要という考えが強いと思うので、天邪鬼な自分は気ままに回答してます。

ところで、StackOverflowの本質というのは、その質の高いQAを支える コミュニティが存在する ところで、それ自体は日本語圏にもあっても良かったのになーと思います。

というより、10年くらい前のはてダ界隈がたぶんそんな感じだった気がします。適当な技術知識でモノを書くと手斧が飛び交っていましたが、異なる視点で技術論を交わしているのは見ていて非常に面白かったです。

もっとも、自分は一応はてなダイアリー評議会とかがあった頃に、はてなダイアリー市民だったのですが、当時のidは技術的なことをネットに書こうというモチベーションはなかったので見ているだけでしたけど。

かつて存在していたモヒカン族の掟は以下のようなものでした。

校正
間違いを訂正してくれる人を我々は尊敬して評価します。よけいな裏読みをして「人格攻撃している」とは思いません。
共有
アイディアに校正の機会を与えることが生みの親の義務です。「理由が無いけど、これはこれでいいんだ」というエレガントではない開き直りはくだらない。
ツッコミビリティ
校正、反論しやすいエレガントな言説が価値ある言説です。その為には、冗長にならない範囲で、ソースと推論過程を明確化し他へ示します。
全体最適
たくさんの人がハッピーになれるエレガントな方法を見つけた時、我々は最もハッピーになります。
差異
お互いの違いを確認することで、我々はつながります。「自分らにとって良いから他の人にも良いはずだ」とは思いません。
モヒカン族 (ネット用語) - Wikipedia

「社交辞令は抜かして端的に論点を述べる」とか「正しい情報に対して尊重する態度を持つべき」というモヒカン思想を、システム化し、コミュニティが支援しているのが英語版StackOverflowだという新説を唱えてみます。

それを実現させた背景は、Joel氏というカリスマの存在が大きいのでしょうけども。

日本語でおk

以下、全然関係ないんですけど、日本だと分散している日本話者によるQA系サイトで自分が見かけるもの。

MSDNフォーラム

Windowsプラットフォームの質問はほぼここに集約されている気がする。

MS社員(MSFT)やMVPプログラム受賞者(MVP)の回答があるため、回答の速度と質は物凄く高い。

Oracle Technology Network (OTN) Japan - 掲示板

とっくに閉鎖されていて、情報としては古いのですけれど、OracleJavaについて検索すると頻繁に引っかかる。

フォーラム全体としては微妙な空気ですよね。移行先はどうなってるんでしょう?

QA@IT

StackOverflowとほぼ同じシステム。人口も日本語版StackOverflowと拮抗してる?

StackOverflowやQiitaはWeb系開発者寄りだけど、こちらは業務系やインフラも扱っている印象。

teratail

コミュニティの重要度に目を付けながら、当初のログイン実装がセキュリティ的に端的に言ってゴミで、ギークな人は敬遠している印象。

StackOverflowだと「主観的な意見しか集まらない」でクローズされるような相談事も受け付けている。

その他

  • 知恵袋はオールジャンルなので案外質問されている。プログラミングカテゴリもある。ただしコードのシンタックスハイライトがないので読みづらく、回答がない質問は一定期間で消えるので実態がいまいち分からない。学生がC言語の宿題をそのまま質問にしている傾向があるような…。
  • OKWaveも技術者向けカテゴリがある。こちらもシンタックスハイライトがないので読みづらい。回答率はあまり高くなさそう。
  • 人力検索はてなはなぜか「ウェブ制作」限定になっている。なんで?はてな記法が使えるのでコードは読みやすいですよ。
  • 2chに初心者向けのプログラミング質問系のスレがあったりする。プログラム板で一番勢いがあるのは、C++相談室。

iOS9広告ブロック騒動雑感

煎じすぎて味がしないかもですが、開発者視点の話ってあんま見ないので。

Appleは日本の広告業界なんて見ていない

「悪い日本の広告が駆逐されるのは良いことだ」「これをいい機会に広告産業を見直すべきだ」的な論があるのだけれど、Appleは特に日本の広告業界について不満があるわけでもなんでもなく、ウェブサイトのネイティブアプリ化、あわよくば自社のiAdを盛り上げたいという意図しかないのでは?と考えています。

ウェブサイトのネイティブシフトについては、iOS8からSafariのログイン情報を使ってシームレスにアプリにログインする機能が追加されています。OSXではSafariでログインし、その認証情報をiCloudキーチェーンでiOSに連携、ネイティブアプリを使う、という囲い込みがAppleの考えているゴールと考えています。

情報を囲われるということは、広告産業にとって致命的です。リスティング広告ビッグデータ機械学習が輝いている分野の一つだと考えていますが、それを支えてきたのはオープンウェブの存在です。Appleがユーザーの行動情報を寡占すれば、それらの価値は大きく毀損し、同時にiAdの真価を発揮させていくのだと思います。たぶん。

こっから後はどうでもいい話です。

広告産業とアプリ開発

iPhoneアプリが流行っていた初期って、CoreMotionを使ったどうでもいいようなアプリにも100円で払える雰囲気があったと思うんですよ。連日ストアの上位を覗くのが楽しかったころです。

そういう時代には、アプリの売り上げで一発当てることも可能でした。その頃の人たちはもうコモディティ化したiPhoneには興味をなくして、ドローンでも飛ばして遊んでたりするんでしょうか。

ともあれ、そこへ広告産業が流入してきました。

アプリにお金を払う敷居ってやっぱり低くはないので、評判の良いアプリが一点集中的に売れる傾向にあったと思います。売れないアプリはさっぱり売れません。

ですので、売れないアプリのLite版を出して、広告収益に切り替えるところが増えていきました。

こうなると買い切り型のアプリはデメリットばかりが浮き彫りとなっていきました。iOSはバージョンアップで劇的にAPIが変わるので、それに追従する工数を捻出しなければならないのですが、既存のユーザーに負担を強いることはできないので、赤字覚悟で新バージョンに追従するか、買ってくれたユーザーを切り捨てるかの辛い二択になります。もはや市場には無料化したアプリが溢れているので新規開拓の望みも薄いのです。

この辺りで、「アプリを買う」という文化は廃れました。焼畑に広がるのは「OSをアップデートしたら起動しません。★1です」の怨嗟のレビューばかりです。

広告収益を上げるには、まずダウンロードされ、繰り返し起動されることが重要です。お小遣い稼ぎやアダルト系、2chまとめ系アプリのような、ウェブでも悪貨と言われる側が圧倒的に有利です。アプリのプロダクトデザインもユーザーゴールの実現よりアプリの消費期限の延命を、利便性よりもインプレッションを増やすための通知を送るよう、全てが改悪されていきます。追い打ちをかけるように、アプリ内課金型のソーシャルゲームがネイティブに流れてきました。

個人開発アプリの行方

もはや面白いアイデアを実現するアプリを作る開発者を応援することにジュース1本分のお金を払う価値は、出るか出ないかわからないSSSレアのために1万円払うことより低いわけです。

そういうわけで個人開発アプリのほとんどは、広告に頼っています。大きく当たることはないと思いますが、お小遣い程度の額にはなるんだと思います。その原資はどこから来ていたのかといえば、皆様の嫌いな「悪い日本の広告」です。

アプリ広告はウェブの広告と無関係でしょうか。そうは思いません。少なくないアプリ広告は起動時にブラウザを開き、サードパーティCookieを利用してユーザーを特定する仕組みを持っています。インストールしているアプリやウェブサイト閲覧などの傾向を蓄積し、ウェブブラウザの広告を最適化していると考えるのはそれほど突飛ではありません。どんな検索ワードに価値があるのか、どんな属性のユーザーがこの広告に食いつくのか、そういった情報はそのままお金になるのです。

よって、仮にスマホへの広告出稿が減るとすれば、そのダメージはGoogleやウェブ界隈だけではなく、個人開発アプリへも及ぶと考えます。

最新のMacBookを購入し、AppleWatchを肌身離さず、WWDCのために渡米し、iPhone6Sの予約を済ませあとは発送を待つばかり、開発者ライセンスを毎年更新し、Swiftコンパイラが吐くバイトコードと対話できるような、典型的なAppleユーザーですら広告ブロックの鉄槌を逃れられないのです。

いま、Swiftの登場によってアプリ開発の敷居は著しく下がり、XCodeも無償で実機で試せるようになり、過去最高に個人開発の機運が高まり、ここ数年で最も独創的なアプリが誕生すると言われていますが、果たして…。