Apple IDの2要素認証をいつまでも求められる場合の対処

ちょっと話題的には古いのかもしれませんが、Apple DeveloperのApple IDは2要素認証が必須になり、そのような警告メッセージが出ました。で、ある時から、2要素認証を「しないといけない」状況になりました。

そこで、appleid.apple.comで、2要素認証をオンにしてみたのですが、XcodeやDeveloperサイトでの状況は変わりませんでした。Xcodeでは署名のたびにパスワードを求められるのですが、それでも認証が通らなくなってきてなんとかしないといけなくなりました。appleid.apple.comで設定すればいいだけではないというのがポイントです。

で、どうすればいいかといえば、macOSのシステム環境設定にあるiCloudで、そのApple IDを入力します。しばらくすると、警告アイコンが出てきて、更新するみたいな内容のボタンが出てくるので、それをクリックしてしばらくすると、アカウントの情報が更新された模様です。そこまでくると、Xcodeでの認証が通るようになります。

多分、何かの具合で、appleid.apple.comでは更新ができない状態なのでしょうけど、iCloudで普段使っていないアカウントの場合、入力し直しになり、データが消えるみたいなメッセージに対処しないといけないわけですが、データはクラウドにあるので原則問題ないと思います。しかしながら、システム環境設定を変えるのは色々面倒と後の影響がきになるところですが、appleid.apple.comでダメだったら仕方ないので、iCloudでセットアップして、更新処理をmacOS側からやってください。

FileMaker Server 16のREST APIをmacOS上で試用する場合

FileMaker Server 16をまずは手元のMacにインストールして使ってみようという方も多いだろう。ここで、FileMaker Data API(REST API)を使用する場合にPHPのcurlライブラリを使うと、「SSL: CA certificate set, but certificate verification is disabled」というエラーメッセージが返ってくる。httpsでの通信が推奨され、証明書が検証できないと通信をしないということが当然となって来ているが、とはいえ、試用なので自己署名証明書で運用できないかとPHPの様々なパラメータを触ってみたところで、上記のエラーないしは別のエラーが出る。ということで、色々調べてみると、macOSに入っているopensslのバグで、サーバー検証をしないという設定ができないということらしい。homebrewから入れ直す方法など、色々紹介されているが、何をとっても大事になってしまう。

というところで、早速、FileMakerホスティングで有名なエミックの松尾さんが、ルータとなるスクリプトを公開された(一部、修正したい箇所があったので、筆者がフォークしたものもある)。FileMaker ServerのREST APIは、httpで接続してもhttpsにリダイレクトされるが、その背後のNode.jsが開いている3000ポートを目指して接続することで、httpでも処理が進められる。ただ、ヘッダをうまく使って同じホストからの接続しかできないような仕掛けがあるため、「直接3000番ポートに接続する」よりも、ルータスクリプトを使う方が楽である。

ルータは、WebアプリケーションとFileMaker Serverの中間に位置しており、WebアプリケーションがFileMaker ServerへのRESTコールを行う代わりに、ルーターに対して呼び出しを行う。ルーターはhttpsを使わずにFileMaker Serverを呼び出し結果を得て、それをWebアプリケーションに返す。動作としては「プロキシ」であるが、名前がrouterなので、ルーターと呼ぶことにする。

ルーターを利用できるようにするには、ダウンロードしたファイルrouter.phpを、以下のようなコマンドで起動する。ファイルがある場所をカレントディレクトリにして、コマンドを入力する。

php -S 127.0.0.1:8090 router.php

ここで、127.0.0.1は自分自身に接続するIPアドレスであり、コロンに続いてサーバーが利用するポート番号を指定する。他のプロセスが使っていない番号を使う必要がある。なお、使用を止めたい場合は、Terminalの画面でcontrol+Cキーを押す。

筆者が作ったAPIを利用するためのクラスFMDataAPIでルーターを利用したい場合、利用方法のサンプルを示したFMDataAPI_Sample.phpであれば、最初の方にあるインスタンス化している部分を記載のように修正する。パラメータを2つ追加して、8090ポートを使うことと、httpプロトコルを使うように設定すれば良い。他は変更が必要ない。もちろん、8090はルーターを起動する時に指定したポート番号を使う。IPアドレスも、基本的に起動時に設定したものを指定する。

修正前:
$fmdb = new FMDataAPI("TestDB", "web", "password", "127.0.0.1");

修正後:
$fmdb = new FMDataAPI("TestDB", "web", "password", "127.0.0.1", 8090, "http");

一方、FMDataAPITrialにあるスクリプトは、まず、lib.phpファイルにある以下の関数の返り値に、ルータ側のポート番号を指定する。

function targetHost()
{
    return "127.0.0.1:8090";
}

そして、Test2.php以降、プログラムの中のhttpsの記述をhttpに切り替える。callAPI関数の最初の引数が全てhttpsになっているので、httpに書き換える。

$host = targetHost();
$result = callAPI(
    "http://{$host}/fmi/rest/api/auth/TestDB",
    null,
    json_encode(array(
        "user" => "web",
        "password" => "password",
        "layout" => "person_layout",
    )),
    "POST");
resultOutput($result);

なお、phpコマンドで、router.phpを起動する時、127.0.0.1ではなく、localhostで指定した場合、上記の修正箇所のIPアドレスは、全てlocalhostで記述する。127.0.0.1では稼働しないので注意する必要がある。phpコマンドで起動する時に127.0.0.1を指定すると、Webアプリケーション側の設定は、127.0.0.1でもlocalhostでもどちらでも良いようだ。FileMaker Serverはかなり以前からIPv6ベースで動いているのだが、localhostとした場合、どこかで127.0.0.1ではないIPv6のアドレスとして識別しているのではないかと想像できるのだが、詳細は分からない。

ちなみに、FMDataAPIを使ったシステムを運用する時、原則としては正しい証明書でhttpsで運用することが原則だ。もちろん、盗聴されないためというのが大きな理由であるが、ホストを偽ってデータを収集されたり嘘の情報を流されることも防ぐ必要がある。httpsでの運用は、Let’s Encryptで無償で利用できるなど、年々手軽になってきているので、面倒がらずに対応すべきだというのが1つの結論である。

では、PHPを稼働させるWebサーバーとFileMaker Serverが同一のホストであれば、盗聴の心配はないのではないかと言うことになる。Firewallでポートを塞げば、それでいいのではないかと言う話もなくはない。ただ、FileMaker Data APIだけにWebサーバーを使うと言うことはあまりないと思われるので、Firewallの設定はちょっと面倒ではないだろうか。絶対に外部からFileMaker Data APIは利用しないで、サーバー内部に限るのであれば、httpでの運用でも問題はないかもしれないが、必ずサーバーの内部からの利用しかしないと言うことを管理者が人手で保証しないといけなくなる。通常、httpsで運用する手順を踏む方が楽なのではないだろうか。

自己署名証明書の問題も同様だ。サーバー内部に限るのなら、詐称のしようもないと考えることができる。これも、「絶対にサーバーの内部からの利用しかしないと言うことを管理者が人手で保証する」と言うモデルであり、むしろ、そういった人的な縛りよりもhttpsでの運用をする方が問題が発生しにく状況であると言えなくないだろうか?

そう言うことで、httpや自己署名証明書での運用は自己責任が大きいので、FileMaker Data APIでアプリケーションを開発したら、本番はhttpsでの運用を目指すようにすべきである。

なお、筆者の作ったFMDataAPIクラスは、デフォルトは証明書の検証をしない状態で稼働するので、そちらは開発中での利用を想定している。実際に正しい証明書が用意できれば、以下のメソッドを呼び出す。そうすれば、証明書の検証を行うようになる。自己署名でないこと、期限、アクセスしているサーバ名と同一なのかが検証される。

$fm->setCertValidating(true);

すなわち、FMDataAPIクラスは、証明書の検証もメソッドでコントロールできるようになっている。

Xcodeのビルド/バージョン番号をagvtoolで管理

Xcode 8にプロジェクトを変えたところで、あるプロジェクトで、PlistBuddyでバージョン番号をアップしていたのがあるのですが、スクリプトが引き継がれなくなったので、今風のやり方はないかと思って検索したら、agvtoolがあるなということで、使い方を考えてみました。Apple Generic Versioning Toolの略だそうです。ただ、いろいろ調べたり、結構詳しいmanを見て考えました。一番素直な使い方はどんなのだろうか?Xcodeのプロジェクト1つだけの場合と、別のプロジェクトを組み込んだ場合で考えて見ました。

使い方の前に、ここでいうVersioningとは、XcodeのプロジェクトのGeneralの設定にある「バージョン」と「ビルド」です。英語版しか今はないので画面通りに記述すると、IndentityのカテゴリにあるVersionとBuildです。

shot3250x

このうち、Finderで表示されるいわゆるバージョンはここのVersionですが、Buildは一般には参照できません。Finderのバージョン表記に出てくる場合もありますが、それはさらにこれらとは別のバージョン表記です。バージョンに関する情報はアプリケーションバンドルのInfo.plistに記述されますが、次のような関係にあります。

短いバージョン 長いバージョン ビルド番号
Finderの情報ウインドウ バージョン バージョン 非表示
Finderウインドウ内 バージョン 非表示 非表示
WebのApp Store
iOSでのApp Storeアプリ
バージョン  非表示  非表示
Xcodeでの設定 Version Info.plistに設定する Build
Info.plistのキー CFBundleShortVersionString CFBundleGetInfoString CFBundleVersion

Finderの情報ウインドウに見えるバージョンには、Info.plistに長いバージョンつまり、CFBundleGetInfoStringがあればそれを表示しなければ短いバージョンつまり、CFBundleShortVersionStringを表示します。Finderウインドウをカラム表示にした時に右端に表示されるアイコン下の属性は、常に短いバージョンつまりCFBundleShortVersionStringが表示されます。

一方、iOSについては、Web上のApp StoreやiOSでのApp Storeアプリで、短いバージョンすなわちCFBundleShortVersionStringが見えていますが、iTunesでは見えていません。

表の最初の3行は一般ユーザーの目に触れるところで、最後の2行はXcodeやInfo.plistの世界です。つまり、ここではCFBundleShortVersionStringがバージョンで、CFBundleVersionがビルドであるということを認識してください。どっちもキーのキーワードにVersionとあり、一見するとCFBundleVersionがバージョン番号のように思えてしまうところが引っかかるところかと思います。以下、「バージョン」と「ビルド」としてそれぞれの設定を参照します。

agvtoolを使う基本的な設定

プロジェクトのビルド設定にあるBuild Settingsには多数の設定項目があります。そこのVersioningのカテゴリを見てください。Versioning Systemの右側の値が入っているところをクリックすると、ポップアップニュが出てくるので、「Apple Generic」を選択します。まず、この設定を行います。

shot3249

それから、上の図にはありませんが、同じVersioningのカテゴリにあるCurrent Project Versionに何か数字を入れてください。この数字はビルド番号の初期値となります。キー名に「Version」とありますが、このキーはビルドの値をおぼえておくために使われています。なお、その他のキーを見るとPrefixやSuffixなどがあり、ビルド番号を構築する時にこれらの文字列を前後につけることができるようになっています。

他のプロジェクトを読み込んでいないプロジェクト

まず、単純なプロジェクト、つまり、他のプロジェクトを読み込んでいないようなプロジェクトを考えます。この場合、ターゲットは通常は1つであるので、アプリケーションのバージョンはそのターゲットを1箇所変えるだけです。そのために、わざわざコマンドを動かすよりかは、Xcodeの画面上で手作業で変えた方がいいでしょう。ということで、バージョンについては自動化の必要はないと考えました。

一方、ビルドの方は、本当にビルドをした時、あるいはProductメニューからArchiveを選んでアーカイブを作った時など、ある程度自動的に設定をしたいと考えるでしょう。そのためには、タイツルバーのプロジェクト名の見えている部分をクリックして、Edit Schemeを選択します。そして、左側のBuildやArchiveの設定に、以下のようなスクリプトを追加します。Edit Schemeを選択してシートが出てきます。BuildあるいはArchiveのPre-actionsないしはPost-actionsを選択します。おそらく、最初は何もないと思われます。シートの下にある「+」をクリックして、New Run Script Actionを選択すると、以下のような画面になります。ここで、2行のシェルスクリプトを入れると同時に、Provide build settings fromのところで、アプリケーションのターゲットを選択しておきます。

shot3259

cd "${PROJECT_DIR}"
agvtool next-version -all

これでCloseをクリックします。上記の場合だと、command+Bでビルドすると、ビルドの番号がアップするはずです。現在の番号は、Build SettingsにあるCurrent Project Versionの値を取り出してアップして、「ビルド」の枠に設定します。現在、ビルドの枠に見えている数字がアップするのではありません。ちなみに、このagvtoolは、プロジェクトのルートをカレントにして動くようになっており、引数でプロジェクトのディレクトリを設定するようになっていません。そこで、ビルド時に作成される環境変数のPROJECT_DIRを利用してカレントディレクトリを移動し、コマンドを入力しています。agvtoolの引数はプロジェクトや値に関わらず、常にこの通りです。

プロジェクトを読み込んでいるプロジェクト

プロジェクトを分けていて、あるプロジェクトで統合している場合、まず、ビルド番号は前に示したような方法で、各プロジェクトに設定するしかないと思われます。ビルドの数はプロジェクトごとに違うでしょうから、それはそれでいいかと思います。

一方、バージョンの方は、以下のように一気にまとめて設定ができるようになっています。統合したプロジェクトのディレクトリをカレントにして、「agvtool new-marketing-version」に続いてバージョン番号を指定します。このマーケティングバージョンというのが、「バージョン」になるということです。

 

$ agvtool new-marketing-version 5.1
Setting CFBundleShortVersionString of project DLS to: 
    5.1.

Updating CFBundleShortVersionString in Info.plist(s)...

Updated CFBundleShortVersionString in "DLS.xcodeproj/../DLSTests/Info.plist" to 5.1
Updated CFBundleShortVersionString in "DLS.xcodeproj/../DLSUITests/Info.plist" to 5.1
Updated CFBundleShortVersionString in "DLS.xcodeproj/../DLS/Info.plist" to 5.1
Updated CFBundleShortVersionString in "DLS.xcodeproj/../Installer/Info.plist" to 5.1
Updated CFBundleShortVersionString in "DLS.xcodeproj/../Utility/Info.plist" to 5.1

ここで、読み込んでいるプロジェクトのInfo.plistも設定されることをメッセージで確認しましょう。

ただ、いくつか試してみた限りでは、読み込んだプロジェクト側にバージョン番号が反映される場合と反映されない場合がありました。以前から読み込みをしてあったプロジェクトは全てに反映されるのに、新たに作ったプロジェクトはだめでした。また、agvtool next-version -allの呼び出しをPre-actionsに設定すると、読み込み側のプロジェクトのビルドがキャンセルされることも見られたので、その場合はPost-actions側に設定することでビルドは全部流れるようになりました。

この辺りは、理由が分かれば追記しようと思います。

何れにしても、agvtoolのパラーメータは多数ありますが、ここで紹介したように、next-versionのものをプロジェクトにスクリプトとして仕組むことと、バージョンの設定のためにnew-marcketing-versionを使うことで概ねのことは賄えると思います。まずはこの線から始めてみてはどうでしょうか?

プロジェクトをXcode 8/Swift 3に変換した時に自動変換できなかった結果から変更点をチェックする

Xcode 7/iOS 9.3のプロジェクトをXcode 8で読み込んでSwift 3を利用するように変換した時の自動変換結果については、こちらに記述しました。そして、ターゲットをiOS 10にして、一部、赤いエラーが出たところなどを直すことになります。それらについてまとめておきます。

署名ができない!

実は、この問題に最後まで苦しみました。署名に関するプロジェクトの設定をあれこれいじってもエラーが出て完了しませんでした。エラーのログをよく見ると、こんなメッセージが見えました。

xxxx.app: resource fork, finder information, or similar detritus not allowed
Command /usr/bin/codesign failed with exit code 1

しかし、リソースフォークをいじった記憶もありません。そこで検索すると、stack overflowのサイトに答えがありました。どうやら、画像なんかにはアプリケーションが勝手にリソースをつけていたりすることがあるので、それを消せばいいということでした。リソース等は署名対象外にしないといけないというのは、最近ルールとして増えたのかもしれません。プロジェクトのルートをカレントディレクトリにして「xattr -rc」、これでエラーがなくなりました。このコマンドは、リソースフォークやFinder情報などのファイルの付加情報を消します。-cで消す、-rでサブディレクトリをすべてさらいます。もちろん、ファイルそのものは消しません。拡張情報の処理コマンドです。

変換されない箇所が残るパターン

NSURLをURLは自動的に変換してくれると思ったのですが、以下のようなパターンの場合は、自動変換してくれず、手動で書き換えました。NSを消すだけですが、AnyObjectはAnyにするなどを知っておかないと作業は面倒になります。完了時のクロージャーはもしかすると、キーワードなしで、閉じかっこの次に記述しておけば変換してくれたのかもしれませんが、これは想像です。

  • variable as Dictionary<String, NSURL>?
  • self.webView.evaluateJavaScript(script, completionHandler: {(obj: AnyObject?, error: NSError?) -> Void in ….

返り値があるのに代入していない場合

例えば、以下のようなプログラムがあるとします。いずれも、返り値のあるメソッド呼び出しであり、インスタンス生成です。これらは、「Result of call to ‘lengthOfBytes(using:)’ is unused」などというメッセージで警告となります。返り値が使われていないというのです。以下のサンプルはさておき、返り値の不要な場合は時々あります。いちいち変数を定義すると、今度はその変数が使われていないと言った警告が出ます。(ちなみに、おなじみの定数「NSUTF8StringEncoding」は、「String.Encoding.utf8」のように、よりスッキリした記述に変わっています。)

"test".lengthOfBytes(using: String.Encoding.utf8)
String("")

この場合、以下のように、_ = に続いてステートメントを記述します。つまり、空代入のようなことをするということです。これも、返り値があるのに忘れているようなミスを防ぐためということになっていますが、ちょっとやりすぎな気もしますね。

_ = "test".lengthOfBytes(using: String.Encoding.utf8)
_ = String("")

#if等でコンパイル時の分岐がある場合

Xcode 8でのコンバートをする時に、定義定数がない状態でのコードが変更されます。したがって、#if等で変数が定義されているかどうかを確認するような場合、ELSE側だけが変換されました。変換されない方は手作業で変更するしかありません。

アプリケーションのバッジを稼働させる方法

バッジ等のユーザー通知をアクティブにするには、iOS 9まではAppDelegateクラスのapplication(_:didFinishLaunchingWithOptions:)メソッドに、例えば以下のようなプログラムを書いていました。変数applicationはメソッドの第1引数です。

let settings = UIUserNotificationSettings(types: UIUserNotificationType.badge, 
                      categories: nil)
application.registerUserNotificationSettings(settings)

記述方法が以下のように大きく変わります。UNUserNotificationCenter(User NotificationフレームワークのUNが頭文字)クラスのインスタンスをcurrentメソッドで得て、requestAuthorizationメソッドで機能の利用を要求します。引数は定数(.badgeがホーム画面のアプリケーションのアイコンにつけるバッヂの許可で、他に.soundなどの定数がある)の配列を指定します。そして、処理完了時に実行されるクロージャーが続きます。許可の可否に応じて処理をすることができますが、何も書かなくても動作はします。

let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.badge]) { (granted, error) in}

ちなみにrequestAuthorizationは2つの引数を持つので、次のような記述が可能です。むしろ、こちらは定義通りです。しかしながら、「最後の引数のクロージャー」については、引数の最後を示す閉じかっこの後に記述できる(Trailing Closure)ので、上記の書き方となります。

center.requestAuthorization(options: [.badge], completionHandler: {(granted, error) in})

なお、処理完了時のクロージャーに何も処理がない場合の一番短い書き方は、これではないかと思われます。

center.requestAuthorization(options: [.badge]){_,_ in}

openURLがdeprecatedになった

UPApplicationクラスのopenURLメソッドがdeprecatedになり、openを使うようにと警告が出てきます。ただし、引数が増えます。最初の引数にURLを指定するのは同じですが、optionsと完了時のクロージャーも記述します。それまで、openURLを使っていたのなら、おそらく当初は増えた引数は何も指定しないでOKでしょう。options:の後にnilを指定したらエラーになります。なので、中身が空のDictionaryを指定するのが手軽だと思います。そして、最後のクロージャーは閉じかっこの後に記述します。何も処理がない場合でも、引数並びは評価されるためnilは指定できません。いかが定義通りではありますが、もっと短くしたいのなら、{_ in} という記述でもいいでしょう。

// iOS 9
UIApplication.shared.openURL(navigationAction.request.url!)
// iOS 10
UIApplication.shared.open(navigationAction.request.url!, options:[:]) {(Bool) -> Void in}

NSRangeとRangeの変換は相変わらずできない

これ不便ですよね。ただ、文字列処理だけしていればRangeだけしか出ないのかもしれませんが、UITextFieldDelegateのメソッドなんかは文字列の変更箇所がNSRangeでやってくるので、Stringに対しての処理をしたい場合は、それをRangeに変更したいわけです。もちろん、NSStringで処理ということもありますが、せっかくSwiftなのだからStringで処理をしたいわけです。iOS 9の時には、自分で変換メソッドを作っていました。そこではadvancedBy(_:limit:)を利用していたのですが、Swift 3にはないらしく、index関数を利用して改めて作り直しました。extensiondでStringクラスにメソッドを追加して変換しています。以前のメソッドよりもだいぶんシンプルに短く書いていますが、それは自分自身のSwiftの知識が増えたからかもしれません。

extension String {
    func rangeFromNSRange(_ nsRange : NSRange) -> Range {
        let startIndex = self.startIndex
        let fromIndex = index(startIndex, offsetBy: nsRange.location)
        let toIndex = index(fromIndex, offsetBy: nsRange.length)
        return fromIndex ..< toIndex
    }
}

Any has no subscript members

AnyObjectで定義されたプロパティに対して、x[“test”]のようなさぶスクリプトが、iOS 9.3の時(あるいはSwift 2.3の時)には機能していたと思うのですが、Anyに対してはサブスクリプトが機能しないようなので、as! Dictionary<String, Int> といった明示的なキャストをする必要があります。

プロジェクトをXcode 8/Swift 3に変換した時の自動変換結果から変更点をチェックする

Xcode 8.0が正式に出ました。あるアプリケーションのプロジェクトをコンバートした時に自動的に書き換えられた結果をもとにどのような変更があったのかを記録しておきます。なお、このプロジェクトは、Swift 2.3の時に、Swift 3でdeprecatedになるという警告の箇所は、警告が出ないように変更した結果です。それ以前のバージョンのSwiftで作られたソースでは、もっと様々な変更の必要があると思います。まず、ターゲットは、iOS 9.3のままの状態でコンパイルが通るようにしてみました。

クラスそのものが変更されたもの

以下のクラスについて、名前が変更されています。矢印の左側が、Xcode 7.x(iOS 9.3)の場合の記述で、矢印の右側の記述に自動的に変換されたことを示しています。NSの付いたクラス名が付いていないものに変わってきています。おなじみのNSURLやNSErrorが、配列や文字列ど同様NSのないクラス名に変更されています。クラスの機能については大きくは変わっていないようです。NSURLSession及びその名前で始まるNSURLSessionTaskも含めて、頭の「NS」はなくなっていますが、今日現在、ドキュメンテーション中に「NSURLSession」が残っていたりします。最後のURLSession.AuthChallengeDispositionは列挙型です。

  • NSUserDefaults→UserDefaults
  • NSError→Error
  • NSURL→URL
  • NSBundle→Bundle
  • NSURLSession→URLSession
  • NSURLCredential→URLCredential
  • NSURLAuthenticationChallenge→URLAuthenticationChallenge
  • NSURLSessionAuthChallengeDisposition→URLSession.AuthChallengeDisposition
  • AnyObject→Any

インスタンスを得るメソッドがシンプルなプロパティに

「UIApplication.sharedApplication()」などは頻繁に記述していたのですが、Applicationという単語の重なりがなんか重い感じがしていましたが、「UIApplication.shared」のように、長いスタティックメソッドが短いプロパティに変更されました。コードがかなりスッキリします。以下のリストの最後から2つ目にあるにあるDipatchQueueクラスは、iOS 10から搭載されたクラスで、dispatchで始まるGrand Central Dispatch関連のAPIをクラスにラップしたものです。このクラスのドキュメントはまだ完全に埋まっていませんが、クラスはiOS 10からなのにメソッドのsync(execute:)のようにiOS 4から関数としてサポートしているものもあり、要するにやっとクラスとして使えるようにAPIを整えたという状況のようです。最後のUIColorクラスは、いくつかの色のオブジェクトを得るスタティックメソッドが用意されていましたが、これもColorの重複があったものの、redやgreen、blackといったプロパティで得られるようになりました。

  • UIApplication.sharedApplication()→UIApplication.shared
  • NSBundle.mainBundle()→Bundle.main
  • standardUserDefaults()→standard
  • UIScreen.mainScreen()→UIScreen.main
  • dispatch_get_main_queue()→DipatchQueue.main
  • UIColor.redColor()→UIColor.red

アクセス修飾子

従来の3種類から、open、public、internal、fileprivate、privateの5段階に変更されました。openとfileprivateがSwift 3より導入されたものです。従来のprivateは、全部fileprivateに変更されてしまいます。省略時にinternalになるのは変更ありません。
Swift 2.xではprivateによるアクセス制限はクラス単位ではなくファイル単位でした。したがって、1ファイルに2つのクラスを定義した場合、privateなプロパティでも別のクラスからアクセスができました。Swift 3ではファイル単位でのfileprivateと、クラス単位のprivateに分離しました。従来のprivateが現在のfileprivateになったということで、こうした変換を行なったのでしょう。
一方、publicとopenについては、元のpublicは現在はopenに対応します。いずれも、モジュール外からアクセス可能にするための修飾子ですが、Swift 3ではpublicはサブクラス化やoverrideできないという制約が付きます。

オプショナルバインディングの書き方

Swift 2位の時に、letの後にカンマで区切って複数の代入文が書けるようになり、ifを多重にネストしなくてもよくなったのですが、Swift 3ではカンマで区切ったそれぞれの代入文にletが必要になりました。以下の、let b = yのletは今までは不要だったのですが、これが入るようになります。

var x: String?
var y: String?
if let a = x, let b = y {
   print(a,b)
}

UIKitのAPIの変更

メソッド名やプロパティ名などがあちらこちらで変わります。ただし、総じて読みやすくはなります。例えば、UIViewControllerのshowViewController(_:sender:)、dismissViewControllerAnimated(_:completion:)、presentViewController(_:animated:completion:)の各メソッドが、show(_:sender:)、dismiss(animated:completion:)present(_:animated:completion:)へと極めて短くなりました。また、最初の引数にはキーワードをつけないということで統一されていて、最初のキーワードは関数名に組み込まれていましたが、状況によっては引数のキーワードとして使うようになりました。なので、dismissのように通常のメソッドでも、最初の引数にキーワードが付くことがあります。一方、UINavigationControllerクラスではpopViewControllerAnimated()がpopViewController(animated:)にはなるなど変化はあるものの、大きな変化のないクラスもあります。

UIViewクラスのhidden、userInteractionEnabledプロパティはisHidden、isUserInteractionEnableとなりました。Boolean型のプロパティのインタフェースを「is+属性名」としたということで、この部分はJavaなどのルールに即したということでしょう。また、プロパティ名にあった「URL」はほぼ「url」で置き換えられており、URLで始まるプロパティもurlで始まるようになっています。定数についても、以前は.Badgeのように頭文字は大文字でしたが、.bridgeのようにドットの次は小文字になりました。たまたま、Objective-Cで作ったクラスを入れていたのですが、そのクラスの定数も同様に頭文字が小文字になったので、この処理はブリッジ部分での変更であることが確かです。結果的に、クラス以外の頭文字は小文字というルールが浸透した感じです。

さらに、メソッドだったものもプロパティとして扱えるものは、プロパティになりました。UIViewControllerクラスでは、supportedInterfaceOrientationsメソッドがあり、それをオーバーライドして返り値をプログラムで記述することで、自分で作っているビューコントローラでの挙動を変更できました。これが、supportedInterfaceOrientationsプロパティに変わります。ただし、その後は、{…} で値を返すように記述することで、プログラムがゲッターとして機能します。つまり、var プロパティ名 : クラス に続いて { get { } set { } } と記述するのがセッターやゲッターの基本ですが、ゲッターだけなら、{ …..; return xx; } のように引数指定なしのクロージャのように記述するだけで変わりません。従って、メソッドからプロパティに変わっても、{ } 内は同じです。

func supportedInterfaceOrientations()->UIInterfaceOrientationMask // Swift 2.x
var supportedInterfaceOrientations:UIInterfaceOrientationMask // Swift 3

関数を定義する時、最初の引数にキーワードがないとき、単に記述しないで済みましたが、Swift 3では _ の記述が必要になりました。

@IBAction func tapRegistering(sender: UIButton) { // Swift 2.x
@IBAction func tapRegistering(_ sender: UIButton) { // Swift 3

@escapingが追加されるクロージャーの引数定義

WKWebViewのデリゲートメソッドの部分では、引数のクロージャーを持つものがありますが、メソッド定義の記述に@escapingが加わりました。Swift 2.3(iOS 9)ではこの@escapingはない状態で定義されていました。このアノテーションを追加することで、引数に設定されたクロージャーがメソッド実行後に消えてなくなってしないように保持をします。このクロージャー自体をlazyで指定したプロパティで使う場合や、あるいは配列にlazyプロパティを指定してさらにmapメソッドを適用することで、mapメソッドのクロージャーの適用を配列の要素を取得する時点で行うことができるのですが、そのmapメソッドの引数にクロージャーを指定するような場合に、@escapingで保持を指定するということになっています。

func webView(_ webView: WKWebView,
 decidePolicyFor navigationResponse: WKNavigationResponse,
 decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {

ちなみに、iOS 10/Xcode 8とはあまり関係ありませんが、配列に対するlazyプロパティについてはブログ等でほとんど見られないので、ちょっと試して見ました。サンプルプログラムを見てください。配列mに対して、mapメソッドを直接ではなく、lazy.map()で適用します。nはprintすると、「LazyMapRandomAccessCollection<Array<Int>, Int>(_base: [1, 2, 3], _transform: (Function))」と出てくるように、特殊なコレクションです。このnをsubscriptによりアクセスすると、そのアクセスした時にmapメソッドを実行して結果を返します。kの値は最初は100ですが、lazy.map()の後に、kの値を変えて配列の要素にアクセスすると、その時のkの値が適用されて、201 202という値が得られます。まさに、メソッドの適用をlazyにするという仕組みと言えるでしょう。

var m = [1,2,3]
var k = 100
let n = m.lazy.map({s in return k + s})
print(n[0]) // 101 と出力
k = 200
print(n[0], n[1]) // 201 202 と出力

このプロジェクトはすでにSwift 2.3の段階で警告も含めてクリアしているので、++演算やそれを使ったfotなどの警告となっていた記述は一切ない状態だったので、それほど時間がかからずに変更はできました。こうして違いが出たところは概ね自動的に変更してくれるので、それはそれでいいのですが、仔細に見ることで色々と新しい機能も理解できるようになります。

Xcode 7.3で出るSwitf 3でなくなる機能

iPhone SEがラインナップに加わり、Xcode 7.3がリリースされました。Swift 2.2になったのですが、Swift 3でなくなる機能が、deprecatedとして出てくるようになりました。以前のように、いきなりコードに赤いエラーが出て動かなくなるより、はるかに気持ちの良い対応ですね。手元のいくつかのプロジェクトを早速開いてみました。

まずは、x++やx–演算子の廃止です。こちらに詳細がありますが、まとめると初心者にとってわかりにくく、トリッキーなプログラムになりがちということで、廃止すべきという意見が通ったということです。x++と++xの違いとか、C言語のクイズとしては面白いかもしれませんが、読みやすいコードにするには、解釈しづらい言語仕様の排除というところでしょうか。x++は、x += 1 に変換しろということになっていますが、++の前後の位置を利用している場合や、x++の返り値を利用している場合には、単に置き換えで済まないかもしれません。

shot3198

__FUNCTION__が使えなくなり、#functionに置き換えるように出てきています。プロポーザルはこちらですが、アンダーラインを使うC言語由来の方法ではなく、#をコンパイラによる置き換えを記述できるようにするということでの変化ということになります。

shot3199

タイマー等で使っていたセレクタ型はSelector(“メソッド名:”)みたいに書いていましたが、#selector(クラス名.メソッド名(引数))のように記述するようになります。メソッド名を文字列で記述するとなると、間違ったメソッド名かどうかの判断ができないので、記述を見直すのは確かに必要です。こちらにプロポーザルがあります。

shot3200

オープンソースとなったSwiftですが、早速多数のプロポーザルが行われ、それが言語仕様となり、Xcodeに反映されるというダイナミズムがこのようにすでに動き始めています。Swiftは過去に例を見ない速度で進化し始めているといえるでしょう。

El Capitanのイチオシ機能はSplit View

年中行事となったOS Xのアップグレードですが、それだけに「大した違いはない」という感想を持ってしまいがちです。しかしながら、2015年10月リリースのEl Capitan(OS X 10.11)のSplit Viewはとても注目できる機能です。一部のiPadでiOS 9により実現していた機能なので、iOSの方が少し先になりましたが、複数のアプリケーションを使い分けるMacでこそ真価が発揮できそうな注目の機能です。

要するにこんなことができます。画面全体に表示されている感じを示すために、画面ショットに外枠も追加してみました。

右は編集中の原稿をJeditで見ているところです。左側はそれをブラウザのChromeで見ているところです。画面にはこの2つのアプリケーションの画面があるだけで、他のアプリケーションはありません。右で入力や変更をして保存、左側をクリックして更新とある程度の操作は必要ですが、ウインドウの大きさは固定であり、スクリールをするだけで、位置を調整したりということしなくても、2つのアプリケーションを並行して快適に利用できます。

shot0001f

こちらは、左がで開発ツールのPHPStromを使い、右側のブラウザ(Chrome)で実行しながらデバッグするという状況です。どちらのアプリケーションもさらにペインに分割されて多数のペインになっていますが、こういう作業でウインドウの前後関係を気にしないで作業できるのは非常に快適です。

shot0002f

使用方法は次の通りです。まず、利用したいウインドウを2つあらかじめ用意しておきます。そして、3本指でトラックパッドを画面方向にスワイプするMission Controlを利用します。すべてのウインドウが画面いっぱいに重ならないように表示されます。そして、1つのウインドウを画面上の「デスクトップ1」などと見えているデスクトップが並ぶ箇所(背景画像がボケて見える箇所)にドラッグします。何もないところにドラッグします。以下の図ではグラフィックス表示していますが、最初は文字のみです。マウスポインタを画面の上の方に移動させれば、グラフィックスも表示されます。

shot2695

デスクトップと同じ大きさのグレーのボックスが表示されます。これが出ることを確認してからトラックパッドの押し込みを放すなどして、ドラッグを終了します。

shot2696

すると次の図のように、もう1つデスクトップが確保されますが、アプリケーション(この場合PHPStorm)が最大化された状態で追加されます。

shot2697

続いて別のウインドウを先ほど新たに作ったデスクトップの上の右半分(あるいは左半分)のあたりにドラッグして追加します。追加される場所が濃いグレーで応答しますので、ドラッグを終了する前に、画面の状況を確認しながら作業をしましょう。

shot2698

こうすれば、2画面に分割したデスクトップが1つ確保されます。Mission Controlの画面でクリックして切り替えてもいいですが、2本指で左右にスワイプして、デスクトップを切り替える方が、快適に切り替わるでしょう。アプリケーションの境界線をドラッグして、左右に移動することもできます。ディスプレイが複数台あれば、それぞれのディスプレイで、内容の異なるSplit Viewを構成することもできます。ちなみに3つのウインドウまではできません。同一アプリケーションの2つのウインドウであれば、Split Viewで表示できます。

Macだと複数のアプリケーションを行き来することはよく行います。マルチウインドウシステムはそうした作業に適合するとはいえ、たくさんのウインドウが折り重なるデスクトップは混乱の元です。切り替わるべきウインドが前面に来るはずが、全然違うウインドウが出てきてがっかりすることも多々あります。作業の上で「それぞれのウインドウを見る」となると、今まではウインドウの位置や大きさをユーザーが調整していたのですが、そういう使い方ではマルチウインドウよりスプリット形式に表示する方が見やすく効率がいいのは、多くのアプリケーションで実証されています。そして、その仕組みをOSがサポートすることで、アプリケーションを超えて1つの画面に集約できる機能がやっと使えるようになったということです。Split ViewはiOSの系譜があるとはいえ、Macにとっては新しいインタフェースを加えるものであり注目できる機能と言えるでしょう。

今現在、USB-C搭載の新しいMacBookはどう使うか?

USB-Cとどう過ごすのかということで、いくつかの記事をここに投稿しました。まず、分かりきった結論かもしれませんが、29WのApple純正のACアダプタが安定して利用するということです。12Wだと、電源供給の認識はしますが、ディスプレイアダプタが間に入ると、おそらくそのディスプレイアダプタやそこから接続されている機器に配給する電力が足りなくなるのだと思いますが、予想外なことが起こります。従って、ディスプレイアダプタを使うときには、29WのACアダプタを使うべきです。この容量のもので、USB出力のものはあるかもしれませんが、結局Apple純正品がいちばんコンパクトで持ち運びしやすいということになりますね。ただ、2mのUSB-C両端ケーブルはちょっと長いので、短いものが欲しくなります。

仮にiPadのアダプタが役に立つとしたら、外出時、それほどの時間は使わないけど、念のためアダプタを持っておきたいとか、ディスプレイアダプタを使わない場合ということに限られるかと思います。それでも、以前に書いたように場所によっては給電状態にならないのであれば、やはり持ち歩くだけ無駄なのかもしれません。そういうわけで、結局、確実に電源にするには、Apple純正のACアダプタということになります。

ディスプレイは、DELLの4Kの27インチでMac純正ではありませんし、HDMIでの接続です。外付けディスプレイの接続が安定しないのがちょっと困り者です。いちばん困るのは、「ディスプレイ」環境設定での設定が記録されていないのか、何かある時点の記録なのか、変更しても戻るということです。しかし、これは、毎回そうでもないです。シャットダウンしてもそうなるときは続けてなるのだけど、なぜか突然、「直前と同じ状態」になったりします。

そして、ディスプレイがフリーズすることが稀にありあす。ディスプレイの電源ボタンを長押ししても、電源が消えず、画面も真っ暗なので、ディスプレイの電源の抜き差しが必要です。ディスプレイアダプタがフリーズしたかのようなときもあります。DELLのディスプレイはスリープと認識しますが、ディスプレイアダプタのすべてのコネクタを抜いて、挿し直すと直ります。その間、Mac側の操作で復活はどうやっても出来ない状態になります。これらは、純正のACアダプタを使っていてもごくごく稀に発生します。もしかしたら、ディスプレイアダプタには常に電源が入っているわけですから、コンピュータのリセットと連動していないということが原因の1つではないのだろうかと考えますが、それは単に推測です。

そういうわけで、Mini Display Portのアダプタが、次の期待ということになります。

USB-CのVGAアダプタ

新しいMacBookのための、「USB-C VGA Multiportアダプタ」が到着しました。「USB-C Digital AV Multiportアダプタ」については以前に書いた通り[こちら][こちら]です。VGAの方は、どんな風にOS側から見えるかを書いておきましょう。VGAなので「従来通り」です。なお、HDMIに接続しているDELL P2715QはVGA入力がありませんので、以前に購入したBenQ GL2450H、つまり、HD解像度の24インチのものに接続しました。「このMacについて」は次の通りです。

shot2026

「システム情報」だと以下の通りです。USB-CのVGAアダプタが接続されていることがここで見えています。4K解像度だと、「このMacについて」と「システム情報」の解像度が2倍違うのですが、HDの解像度だと同一になっています。

shot2027

システム環境設定のディスプレイは、Retinaではなくドット数を選択する方式が見えています。

shot2028

あまりきれいな写真ではありませんが、HDMIのアダプタと、VGAのアダプタを並べて写真に撮ってみました。VGAの方も、USB、USB-Cがいずれもあるので、幅が広くなるのは仕方ないとして、なぜか、ケーブルも長いです。しかしながら、ディスプレイ端子の位置から、Macに差し込むコネクタまでの長さは同一です。ボックス部分が長方形になっている分、ケーブルが長くなっています。しかし、このVGAアダプタは、微妙な大きさですね。3端子ないとやっぱり不便だけど、かさばる感じがしますね。厚みは、VGAの方が少し多いです。

IMG_0391

Swift: アウトレットを含めたプロパティで Optional “?” を積極的に使う

Swiftの特徴として、クラス名のみで示す型では、nil値を許さないことです。一方、?や!でnil値を許す変数の定義などができます。それぞれ、OptionalとUnwrapped Optionalと呼ばれています。

var myLabel1: UILabel = UILabel(frame: ...) // nilを許さないので何か入力しないといけない
var myLabel2: UILabel?
var myLabel3: UILabel!

ここで、アウトレットについて考えてみます。アウトレットは、Interface Builderで線を引くなどして参照先のオブジェクトが指定されていれば、ストーリーボードのロード時などに自動的に参照先が設定されます。しかしながら、オブジェクト生成直後では値が確定しないので、OptionalかUnwrapped Optionalのどちらかにしないといけません。

ここで、Appleのドキュメントでは、Unwrapped Optionalにすべきと書かれており、Xcodeでアシスタントエディタで、Interface Builderのオブジェクトからctrl+ドラッグでコード上にドロップしたときに作られるプロパティは、自動的にUnwrapped Optionalになります。もし、アウトレットをOptionalで定義すると、Optional Binding、つまり、

if let aLabel = myLabel2 {
    x = aLabel.text
}

のように、ifで囲う必要があります。そうなると、Objective-Cで作られたプログラムを移植するようなときに、流れが変わってしまい、作業効率が悪くなるということもあると思います。こうした点を総合して、AppleはUnwrap Optionalをアウトレットの推奨形態にしていると思われます。

しかしながら、Optionalか、Unwrap Optionalかは、仕組みの上ではどちらでも構わないのではないでしょうか。つまり、nil値を許せばいいのです。また、nil値になる状態での不要な処理を避けるという意味では、明示的にOptional Bindingを記述することになるので、不都合はなく、むしろ好都合かもしれません。余分に記述するのが目障りということもあるかもしれませんが、予防的な措置であれば、むしろ歓迎すべきです。これらの点は、個人的な嗜好ではありますが、むしろ評価すべきです。そもそも、Swfitは安全な言語ということが謳い文句だったはずです。であれば、Optionalをもっと積極的に使うべきではないでしょうか。

Optionalにしたら、面倒と思うかもしれません。たとえば、以下はいずれも、エラーになります。tagというプロパティはないとエラーでは記述されています。

myLabel2.tag = 3
let m = myLabel2.tag

しかしながら、若干の誤差を許していただければ、基本的には非常に緩いルールでこれらのエラーは逃れられます。それは、Optionalなプロパティが左辺にあるときには、単に、変数名に?をつけるだけでいいのです。右辺で利用するときには原則としてバインディングを行います。

myLabel2?.text = "aaa";
if let label = myLabel2 {
     let m = label.text
}

これだけのことでいいのです。これだけのことで、より安全になるのであれば、手間をかける価値はあるのではないでしょうか? アウトレットを含めたプロパティは、Optionalで定義する〜このルールでも構わないと考えます。