騒音のない世界 BLOG

コンピュータの話と、音楽の話

楽曲をダウンロードする方法について

楽曲をダウンロードする方法がわからないと伺ったので解説します。

今回は「騒音のない世界のサイトの埋め込みプレイヤーからダウンロードする方法」と、SoundCloudのページから直接ダウンロードする方法」の2つをご紹介します。

なお、どちらもPC環境を想定しており、スマホタブレット等はOSや端末によって仕様や操作方法が多岐にわたるため想定しておりません。ご了承ください。また、スクリーンショットは2017年6月現在のものですので変更される可能性があります。

騒音のない世界のサイトから

騒音のない世界noiselessworld.net

サイトの埋め込みプレイヤーからダウンロードする場合です。 まずプレイヤーで曲を選択してください。 曲名の横に下矢印⬇のボタンが出てくると思います。

f:id:a_beco:20170618163524p:plain

このボタンからダウンロードできます。ボタンが出てこない場合、 僕の方の設定の問題の可能性があるのでTwitterなどで知らせて頂けるとありがたいです。

SoundCloudページから

beco | Free Listening on SoundCloudsoundcloud.com

SoundCloudのページからダウンロードする場合です。トップページと個別の楽曲ページの2箇所からダウンロードできます。

トップページから

プレイリストからは直接ダウンロードできないようなので、1曲ごとになっている方からダウンロードしてください。 下図のように「More」ボタンをクリックしてでくるメニューからダウンロードできます。

f:id:a_beco:20170618164531p:plain

個別の楽曲ページから

楽曲タイトルをクリックすると個別の楽曲ページに移動します。こちらもトップページからダウンロードするときと同様に 「More」ボタンをクリックしてでてくるメニューからダウンロードできます。

f:id:a_beco:20170618165048p:plain

おわりに

いかがでしたでしょうか。不明点などありましたらお気軽にメッセージください。

iOSエンジニアがUnity使って1ヶ月でアプリ作った話

先日、Unity製のカジュアル2DゲームをiOS/Androidでリリースしました。

iOS開発やcocos2d-xでのゲーム開発の経験はあったのですが、Unityはまったく初めてでした。というわけで、Unity使う中で感じたことをまとめたいと思います。これからUnity入門する方やUnityに興味ある方の参考になれば幸いです。

学習

最初にとりあえずUnityの学習期間を設けました。勉強だけしてたのはだいたい2、3日ぐらいだったと思います。インストールして動かすまではすんなりいけました。

勉強方法

最初に驚いたのが公式サイトが日本語に対応していたことです。全てではありませんがドキュメントの類までかなり対応されていました。少し前にAndroid開発のチュートリアルか何かを読んだ時は最初の数ページだけ日本語で後半ほとんど英語だったのですが、そんなこともなくしっかり対応されていました。すばらしい。

Unity - マニュアル: Unity マニュアル

公式のチュートリアルも充実してます。コースは動画になっており、実際にゲームを作りながら基本的な要素を学んでいく形のものになっています。こちらは日本語字幕があるものとないものがありますが、おそらくネイティブスピーカー以外の人が聞くことも想定しており比較的平易な英語ではっきりと発音してくれているので聞き取りやすいです。僕は英語のリスニングはそこまで得意ではないですが、英語字幕もあるので簡単に進められました。

Unity - Learn - Modules

今回作るゲームは2Dだったので2Dの初級と中級をやりつつ色々試したりしてました。それだけであとは調べながら作れそうという状態になれたので、実際のプロジェクトを進めていくことに。

本も読んでいたのですがそちらはあとから知識の穴を埋めるイメージで隙間時間に読んだりしてました。3Dだったら地形とかマテリアル、ライティング周りなども勉強しないと厳しいかもですね。

大変だったこと

Unity開発の中でハマったこと・手間取ったことなどをざーっと書いてみます。最初からこの辺気をつけられるともっと開発効率上がったかなと思います。

エディタを覚える

まず、縦画面にする方法がわかりませんでした。プロジェクトの初期状態は横画面で、チュートリアルも横画面だったので。「いやこんなもんすぐできる」と思ってあれこれやってたんですが結局自力では見つけられませんでした。まあ調べろって話なんですが、「簡単そうなことでも自力で見つけられない」っていう洗礼をいきない浴びた感じでしたね。当たり前かもですが、調べながら覚えていく感じだな〜と。

よく使う処理のショートカットとかも覚えると便利になりますね。公式チュートリアルでは出てきませんが、Q, W, E, Rで移動とか回転とかのツールを切り替えられるのは最初に知っておくと良いです。

複数の座標系が混在する

ゲームによって違うかと思いますが、複数の座標系について考える必要があり少し混乱しました。説明が難しいのでちょっとわかりにくい話になります。もっと良い方法もあるかもです。

ゲーム内のオブジェクトは基本的にワールド座標系で設定します。これは3次元の座標系ですが、2Dゲームでも同じ座標系を用います。ワールド座標系の1単位の長さは例えばカメラに写っている領域の高さを10とする、みたいな感じで自由に決められます。

さらにUnityにはGUIを作るuGUIという仕組みがあって、こちらはスクリーン座標系っていうんですかね、2次元の座標系で設定します。これはワールド座標系とは別の座標系です。デザインをiPhone5サイズでお願いしていたので、今回は横幅が640になるように設定しました(デバイスの横幅/640が1単位になる)。

さらに広告やWebviewなどのネイティブUIを出す必要がありました。例えば画面下部に出す広告の高さは50dipで固定です。dip密度非依存ピクセルというやつで、Androidなどのネイティブアプリでよく使われる単位です。Unityからデバイスのサイズを取るとピクセル値で取れるので、dipにcontent-scaleを掛けた座標系についても考える必要があります。(content-scaleはUnityからは直接取れなさそう?だったので、DPIから計算しました。)

要は色んな長さの「1」があってどれがどれなんだっけとなるっていう話なんですが、結局どうしたかというとそれぞれの座標系を相互に変換するような関数を作りました。それでデザインで指定された座標をワールド座標に変換して配置、とか、画面下から50dip空けて30ワールド座標の位置に配置、みたいなことやってました。

dip座標系 <-> デバイス座標系 <-> スクリーン座標系 <-> ワールド座標系

という感じです。

画面のレイアウトはめちゃくちゃやらないといけないので、最初に仕組みを作るのに時間をかけて細かい計算は隠蔽し、できるだけシンプルに考えられるようにしました。

ついでにですが、マルチ解像度対応は横方向は横幅に合わせて拡縮、そのスケールで縦方向は画面いっぱいに伸ばすという感じにしました。小さめの画面を基準にしているので基本引き伸ばされて多少ガビっちゃいますが、大きめの端末で見ても問題なさそうだったので許容としてます。

transform.positionとtransform.localPosition

Unityエディタ上でGameObjectに設定したpositionとかrotateみたいなプロパティは、スクリプトから動的にいじれたりします。それで、なんとなくエディタ上の名前とプロパティ名って必ず一致してると思ってました。実際だいたい一致してるんですが、transform.position = new Vector2(x, y)みたいにすると全然思った場所にレイアウトされなくてなんでや!ってなりました。

調べたところ、エディタからだと親GameObject基点の相対positionを指定するんですが、スクリプトのpositionは画面中心基点のグローバルなポジションになってしまうみたいです。なのでしばらく親GameObjectのpositionに足しあわせる感じで相対ポジションを書いてて、めんどくさいなーと思ってました。

しばらくしてドキュメント眺めてて気づいたんですが、transformにはlocalPosiitonというプロパティがあって、エディタ上のpositionはスクリプトではlocalPositionと対応しているみたいです。エディタだと表記がpositionなので結構長いこと勘違いしてました。基本スクリプトでセットする時はlocalPosition使うことが多くなるんじゃないでしょうか。

タッチイベントの取得

オブジェクトをタッチして云々ということがやりたいとき、uGUIを用いる場合はButtonコンポーネントを使えばタッチは簡単にとれますが、そうでない場合は自分で当たり判定を書く必要があります。毎回書くと結構大変なので、使いまわせるようなコンポーネントを作るなど考えたほうが良さそうです。

ArrayListとList (C#)

C#も初めてだったので少し戸惑ったことなのですが、「ListのほうがArrayListより新しい」って名前から初見で気づけなくないですか。。なんとなくJavaに近い感覚で書いてたので最初は名前的にArrayList使ってましたが、型情報が失われるのでキャストが多くなってなんか嫌だなと思っていました。あとからListの存在を知って、Listみたいに型指定できるって知ったのでこれだ。。と。普通の配列への変換もこっちのほうがやりやすいですね。

ビルド設定

Unityはビルド周りの機能が結構弱い印象です。普通のネイティブアプリだとdebug, releaseで分けたりとか、繋ぐサーバの環境ごとにビルド設定を分け、サーバの接続先を変えたり、別アプリにしたり、マクロで処理を分岐したりとかできると思いますが、Unityだと難しいです。

一応developmentビルドというものはありますが、bundle-idやアプリ名を切り替えるなど細やかな設定を簡単に実現する方法は標準では用意されてないという感じでした。(今回は、幸い社内ツールでPlayer設定を保存して切り替えられるようなものがありそちらを利用させてもらってだいたいなんとかなりました。)

ビルド工程についてはスクリプトを書いてある程度自動化することはできるようになっていて、今回で言うと広告のUnity SDKを入れるときにキーをinfo.plistに設定してあれこれみたいな手順があったのですが、毎回やってたらヤバイ量だったのでスクリプトを書いて自動化しました。

リソースの管理

色や文字列などもリソースとして一元管理したかったのですが、いまいちベストな方法を確立できませんでした。

最初はスクリプトで定数として管理し、必ずスクリプトからセットするルールにしていたのですが、Unityはエディタが強力で、エディタで完結させたほうがはるかに速く作れるので「必ずスクリプトからセットする」というルールはスピード感がなく諦めました。エディタでの見え方と実行時の見え方が変わるとややこしかったというのもあります。

結局エディタで直接指定するようにしたので一元管理はできていません。なのでUIの色をいっぺんに変えたりとかはできないです。ローカライズとかも大変なのでは。。ちょろっと調べたんですけど簡単にやるデファクトな方法って無さそう?ですかね。

プラグインの管理

Unityプラグインは普通に入れるとプロジェクトの色んなフォルダにガッとインポートされてファイルが散在してしまうこともあり、どれがどのパッケージなのかわからなくなったりします。

外すためのプラグインとしてPackage Uninstallerというのがサードパーティ製で一応ありましたが、パッケージに含まれるファイル全部消してしまいそうな感じだったので、複数のパッケージが同じファイルに依存していたりすると死ぬんじゃないかな。

https://www.assetstore.unity3d.com/jp/#!/content/35439

実際そういうのがあって、複数のプラグインが全く同じGoogleのなにかのライブラリに依存してました。(プラグインに含まれているバージョンが違っていて、古い方で上書きしちゃうとビルドできなくなるという問題もありました。)

あとプラグイン間でAndroidのActivity奪い合っちゃう問題というのもありました。Activityのライフサイクルきっかけで処理したくてActivityを独自のものに置き換えるプラグインがあるんですけど、Activityのイスって1つしか無いので2つ以上そういうプラグインを入れると動かなくなるんですよね。手動でActivityをマージして置き換えないといけないという。

いろいろ書きましたが、プラグインの管理は特にプロジェクト規模が大きくなっていくと結構たいへんになりそうだなーという印象でした。今回はそこまで問題になりませんでしたが、プラグインごとにフォルダがあって独立に管理できるようにしておくと良さそうという感じ。

設計について

ゲームの設計って前々から悩んでますが、難しいですね。

ゲーム内容にもよりますが、ゲームロジックとビューが密接に絡むことが多いので、そこを完全に分離するのは難しいと思うんですよね。見た目に依存するロジック層と見た目に依存しないロジック層で分けるとかなんですかね?MVVM的なアーキテクチャは可能かもしれませんね。VMには見た目に絡んだロジックを書くイメージ。

あとUnityはComponent-Based Architectureというやつだと思うので、それにメンタルモデルを切り替えて乗っかる必要があるなという感じがしています。たまたま昔書いていた記事を貼っておきます。

ゲーム設計: Component-Based Architecture - Qiita

スクリプトを書く時には小さいコンポーネントに分ける意識で、コンポーネントの再利用性をいかに上げられるかを考えるって感じですかね。

あとグローバルにデータを持つのってバッドノウハウなイメージあったのですがゲームだと結構普通にやるのかなって感じがしてます。Scene間のデータ受け渡しをやろうとするとどうしてもそうなりますし。そもそもUnityのGameObjectってFindとかするとどこからでも到達できてしまうし。。

ツール系よりもゲームのほうが変更の振れ幅が大きくて、堅牢性よりフレキシビリティを重視するんですかね。動かせる状態になってからの調整とか多そうですし。その中で保守性や可読性を担保していくのはなかなか骨が折れそうですね。

おわりに

ちょっとダラダラ書いてしまいましたが今回は以上です。何か参考になれば幸いです。

Null安全がすごい

先日社内で事業部のエンジニア全体向けに発表する機会があり、テーマが割と何でもありだったのでNull安全について話しました。発表の中で話せなかったことなども踏まえてブログにもまとめてみようと思います。

まずこのテーマを選んだ理由ですが、背景として

  • 自分がちょうどSwiftでiOSアプリを開発しており、Null安全な言語を利用していた
  • 事業部でNull安全な言語を使っているプロジェクトが他になかった
  • 比較的近年になって広がりを見せている新しい概念である
  • 自分がSwiftを書き始めて一番感動した部分だった
  • 将来的に言語選定の際の判断軸になるなど、具体的なアクションにつながりそう

といったところがあり、共有する価値があるのではと考えました。そういうわけなのでできるだけ特定の言語に依存せず、まったく知らない人にその概念を理解してもらい、良さを伝えるということを念頭に置いています。

Null安全とは

Null安全とは、一言で言うと「Nullチェックをコンパイラが強制する仕組み」のことです。Nullチェックをしないとコンパイルが通らない状態にすることで、実行時にNullへの不正なアクセスを防ぐ、ということをゴールとしています。

概念の説明としてはこれだけなのですが、Null安全が導入された背景を理解するため、NullとNull安全を取り巻く歴史を紐解きつつ、さらに深掘りしていこうと思います。

Nullの発祥

Nullという概念は1965年にTony HoareがALGOL Wという言語に導入したのが始まりとされています。その後様々な言語でNullが導入されていますが、2009年のカンファレンス(QCon)でTony Hoare自身が「Nullは10億ドルに相当する誤りだった」という旨の発言をしています。さらに、当時からコンパイラが参照の安全性をチェックするという発想はありながらも、実装の簡易さから単にNullを導入するという選択をしてしまい、多くのクラッシュにつながってしまった、とも話しています。

気をつけてNullチェックをする、みたいなことは当たり前のようになってしまいがちですが、人間が気をつけないとバグにつながるようなことはできるだけ機械に任せたほうが良いでしょう。考案者本人が認めているように、Nullチェックも本来はもっと仕組みで自動化されるべき存在だということではないでしょうか。コンパイラによって自動化するというのはその1つのアプローチといえます。

Null安全の歴史

Null安全という言葉は2005年のカンファレンス(ECOOP)でBertrand Meyerによって「Void Safety」という名前で紹介されたのが最初のようです。その後本人が開発するプログラミング言語Eiffelに導入されています。実装レベルではそれ以前にも似たような仕組みを実験的に導入している言語はいくつかあったみたいです。

それなりにメジャーな言語に普及していったのはここ数年のことで、だいたい2010年ごろからです。2010年に登場したRustをはじめ、Kotlin(2011~), Swift(2014~), TypeScript 2.0(2016~)など様々な言語に導入されました。

コンピュータやプログラミング言語の歴史から考えれば比較的近年になって広まってきた考え方であるといえるでしょう。

Null安全の仕組み

もう少し具体的な仕組みに踏み込んで見たいと思います。Null安全は実際にどういった形で実装されているのでしょうか。いろいろなバリエーションはあるかと思いますが、個人的に一番慣れ親しんでいるSwiftを例に取りつつ説明していきます。

Null安全はその性質上、言語機能として実装されることが多いです。やや雑な説明かもしれませんが、以下の2つの条件が満たせればNull安全な言語であるといえるでしょう。

  1. Optional型が存在すること
  2. Optionalでない型にはNullを代入できないこと

Optionalという言い方は言語によって異なるかと思いますが、わりとよく使われている用語のようです。概念としては「値もしくはNullを代入できるが、その中身には直接アクセスできない」ような型だと考えていただければよいかと思います。Optional型は明示的にNullチェックをすることによって中身の値にアクセスできるようになります。この型を使っていれば、Nullへの不正なアクセスは起こり得ません。

また、Optionalでない型にNullを代入できてしまうと結局そちらでNullへの不正なアクセスが起きうるため、2つめの条件も保証されている必要があります。

コード例を見る

Swiftでのコード例を見てみましょう。Swift未経験の方でもわかるようにコメントを含めていますのでなんとなく理解してもらえればと思います。まずOptional型です。

var opt: String? // StringのOptional型を定義する
opt = nil // Optionalなのでnilを代入できる
opt.lowercased() // Error! NullチェックをしていないのでStringのメソッドは呼べない

if let non_opt = opt { // Nullチェック
  non_opt.lowercased() // NullチェックをしたのでStringのメソッドを呼べる
}

Swiftの場合、型名の後ろに?を付けることでOptionalにすることができます。また、nilがいわゆるNullです。Optionalにはnilを代入でき、チェックなしには値にアクセスしたりメソッドを読んだりすることはできません。

次に、Optionalでない型です。

var non_opt: String = "TEST"
non_opt = nil // Error! Optionalでないのでnilは代入できない
non_opt.lowercased() // Stringのメソッドを呼べる

Optionalでない型にはnilは代入できません。つまりnilにはなりえないのでチェックなしにメソッドを呼ぶことができます。Non-null typeと呼ぶこともあります。

Javaなど従来の多くの言語では参照型のみNull代入可能であることが多いですが、Swiftでは値型か参照型か、ということとNull代入できるかどうかということは関係ありません。IntやDoubleといった値型でもOptionalならNull代入できますし、クラスなどの参照型でもOptionalでなければNullは代入できません。

これによって、型が値型か参照型かは意識せず、「存在しない」という状態をNullで表せるようになります。

Null安全のメリット

Null安全についてイメージが深まってきたでしょうか。次にNull安全のメリットについて見ていこうと思います。メリットとしては以下の様なものがあると思います。

うっかりNullチェックを忘れてしまうことがなくなる

コンパイラがNullチェックを強制するためです。Nullチェックをするか否かの判断軸も明確になるため、全体的にNullについての悩みが減ります。

生産性が高まる

開発のイテレーションにおいてNullチェック忘れというありがちなミスを早い段階で見つけられるようになるため、実装, コンパイル, 実行, デバッグ, というイテレーションを早めることができ、生産性が高まることが期待できます。

関数のインタフェースが表現豊かになる

これは直感的には理解しづらいかもしれませんが、例えば関数が失敗した場合の簡単なエラーの返し方としてOptional型を使うことができます。

エラーの表現として-1や空文字を返すようなパターンは古くから存在しますが、-1や空文字を失敗として扱うと理解しづらくなりますし、失敗しうるのかどうかがドキュメントを読まない限りわかりません。Nullを返すパターンは多少ましですが、結局失敗しうるのかどうかは関数のシグニチャからはわかりません。エラーをthrowする書き方は良いのですが、「書くコスト」は無視できません。チームで開発する場合、メンバーのスキル感によってはコストの高い書き方に対してリターンを理解してもらうための学習コストもかかります。それらを加味して、Optionalを返すというエラーの返し方は手軽でありながらエラーを返す可能性がある関数なのかどうかがわかりやすいため可読性も高いのが良いと感じています。

また、引数がOptionalであれば文字通りオプショナルな引数と判断することもできます。このように、関数のインタフェースが表現豊かになるのは大きなメリットといえます。

Null安全の悩ましい点

メリットの多いNull安全ですが、悩ましい点もあります。

抜け道の存在

現実的に、Optional型でも処理的にNullにならないケースもあります。そのような場合に冗長性を廃するため、Null安全な言語でありながらNullチェックを回避する仕組みが多くの言語に存在しています。

例えばSwiftにはForced Unwrappingと呼ばれる仕組みがあります。これは正しく使えば冗長なチェックを減らすことができるのですが、間違って使ってしまうと結局実行時にNullにアクセスしてしまい、クラッシュにつながってしまいます。Forced Unwrappingするには、変数の後ろに!を付けます。

var opt: String? // StringのOptional型
opt = nil // Optional型なのでnil代入できる
var non_op = opt! // Forced Unwrapping。中身はnilなので実行時にクラッシュする。

正しく使えれば便利なこともあるのですが、これによって結局Nullによるクラッシュを100%なくすには至っていません。これはNull安全の仕組みを採用しないほどのデメリットとは考えていませんが、もう少し良い仕組みにもできそうな気がしています。

Null安全を導入する悩ましさ

これは使う上での話というよりはそもそも導入する上での障壁の話です。

Null安全な言語であることの条件の1つとして「Optionalでない型にはNullを代入できないこと」というものを上げました。これは、例えばJavaにOptionalを導入しても、通常のNull代入可能な参照型があることによってNull安全の恩恵を受けにくくなってしまうということです。

このような背景があるため、既存のNull安全でない言語にNull安全を導入するのは難しいところがあります。Null安全なしくみを利用したければ、そのようなしくみのある言語を採用する必要があるでしょう。そのためプロジェクトの初期に決定する必要があり、すでに動いているプロジェクトに導入するのは難しいと言えます。

ちなみに、AltJSの1つであるTypeScriptはそのへんをうまくやっていて、バージョン2.0からデフォルトでは後方互換性を保ちつつ、フラグをたてることで後方互換性を捨てる代わりにNull安全の機能を導入できるようになっているみたいです。とはいえ、後方互換性をもたせることの重要性が高い言語ではそのようなアプローチも難しいのかもしれないと思っています。

Non-Null Typeについて

Null安全について説明するとOptional型に注目してしまいがちなのですが、本当に強力なのはそれと対をなすOptionalでない型、つまりNon-Nullな型の存在です。

Optionalな型が生まれたときに素早くチェックし、登場する変数をOptionalでない型のみにできれば、そこはNullが存在しない世界、ロジックに集中できる世界です。Nullにまつわる煩わしい問題をプログラムの端に追いやって、ロジックの本質的な領域を増やすことができれば見通しの良いプログラムを組むことにつながるでしょう。

おわりに

近年のプログラミング言語における新しい概念としてNull安全について解説しました。

プログラミング言語は人間に優しくなるように進化しています。構造化プログラミング、オブジェクト指向、型安全など、どれも可能なこと自体は大きく変わっていませんが、人間にとって読みやすく、書きやすく、管理しやすくするためのアイディアです。Null安全もその一つと言えるのではないでしょうか。

Null安全は、Nullがもたらす煩わしさを低減し、コアなロジックに集中できるようにする強力なツールであると感じています。正しく活用できれば多くのメリットが得られるでしょう。

Swift界隈では色々と議論され、「良い」という認識が広まっているテーマな割に、コミュニティによってはまだ全然浸透していないと感じています。Null安全という考え方に興味を持ってもらうきっかけになれば幸いです。

参考リンク

新曲「裸の王様」出しました

ちょっと出してから時間立っちゃいましたが、新曲出しました。

ドラムもギターもノリノリの曲です。ぜひどうぞ。

過去の曲もぜひ。

1~2ヶ月に1曲ぐらいのペースで上げてます。 更新情報など気になったら各種SNSのフォローお願いします。

新曲「終わりのない物語」出しました

クリーントーンのギターが淡々とつぶやくようにシンプルなメロディを弾きながら広い世界を旅していくような感じで、素朴で暖かい曲になったと思います。

ぜひ聴いてください。

過去の曲もぜひ。

1~2ヶ月に1曲ぐらいのペースで上げてます。 更新情報など気になったら各種SNSのフォローお願いします。

ポリモーフィズムをもっと理解する

ポリモーフィズムオブジェクト指向の3本柱として「継承」「カプセル化」と共に語られることが多いですが、サブクラス化してオーバーライドして…というのはポリモーフィズムの一面にすぎません。この記事ではポリモーフィズムとは何なのかを説明すると共に3種類のポリモーフィズムを取り上げ、「ポリモーフィズム」という言葉を様々な角度から見ていきたいと思います。対象読者としては全くの初学者というよりも何となくポリモーフィズムは知ってて使ったりもするけどちゃんと説明できないとか、そういう人を考えています。

ポリモーフィズムを一言で説明する

ポリモーフィズムとは、一言で説明するとどういう概念なのでしょうか。日本語訳としては、オブジェクト指向の文脈だと「多態性」、関数型の文脈だと「多相性」が多い印象ですが、どちらもピンとくる言葉ではありません。そこでまずは言葉を分解して読み解いてみたいと思います。

ポリモーフィズム(Polymorphism)という言葉はポリ(Poly)とモーフィズム(Morphism)にわけられます。ポリは「複数の」、モーフィズムは「形状」と置き換えられそうです。つまりポリモーフィズムとは「複数の形状」と読み取れます。

まだ抽象的で掴みどころがないですね。実際ポリモーフィズムという言葉自体は結構抽象的で、後ほどご紹介しますが生物学などでも用いられる幅広い言葉のようです。そこで、もう少しプログラミング的な意味に注目していこうかと思います。Wikipediaの冒頭の説明がとてもわかりやすいです。

polymorphism is the provision of a single interface to entities of different types. Polymorphism (computer science) - Wikipedia

短くすると「Single interface for different types」とでも言うのでしょうか。つまりプログラミングにおけるポリモーフィズムとは、複数の異なる型に対して共通のインタフェースを提供することです。

3種類のポリモーフィズム

ポリモーフィズムには大きく分けて3種類あります。それぞれ見ていきます。ポリモーフィックな部分だけサンプルを軽く書いておきますので雰囲気だけ感じて頂ければと思います。

Ad hoc polymorphism

いくつかの限定された型に対して共通のインタフェースを提供するポリモーフィズムです。実装上は関数のオーバーロードによって実現されます。同じ名前の関数に対して、引数や戻り値の型を変更したものを用意します。

関数オーバーロードポリモーフィズムであるというのは少し意外に感じられる方もおられるのではないでしょうか。しかしこれも確かに「複数の異なる型に対して共通のインタフェースを提供」しています。

// DogとCatに対してtouchという共通インタフェースを提供する
class Breeder {
    func touch(_ dog: Dog) {
        dog.say()
    }
    func touch(_ cat: Cat) {
        cat.say()
    }
}

let breeder = Breeder()
breeder.touch(Dog()) // bowwow
breeder.touch(Cat()) // meow

Parametric polymorphism

様々な型に適用できるようなインタフェースを提供するポリモーフィズムです。実装上はいわゆるGenericsやTemplateと呼ばれる言語機能により実現されます。型を引数として渡すことで処理を様々な型に適用することを可能にします。

関数型言語において単にポリモーフィズムと言う場合、こちらを指します。

// Animalに適合するあらゆる型に対してtouchという共通インタフェースを提供する
func touch<A: Animal>(_ animal: A) {
    animal.say()
}

touch(Dog()) // bowwow
touch(Cat()) // meow

Subtyping

実行時にサブクラスのポインタをベースクラスのポインタとして扱うことにより、ベースクラスの共通なインタフェースから各サブクラスの異なる実装を呼べるようにするポリモーフィズムです。

オブジェクト指向言語において単にポリモーフィズムという場合、こちらを指します。

// AnimalとAnimalのサブクラスにtouchという共通インタフェースを提供する
func touch(_ animal: Animal) {
    animal.say()
}

touch(Dog()) // bowwow
touch(Cat()) // meow

なぜポリモーフィズムか?

なぜそのような言語機能があると嬉しいのか?という話ですが、これは僕の個人的な考えを書かせていただきます。

ポリモーフィズムは「複数の異なる型に対して共通のインタフェースを提供すること」であるというのは前述しましたが、その本質は「共通のインタフェースを用意する」という部分であると考えています。

「共通のインタフェースを用意する」という考え方は身の回りの道具のデザインにもあふれています。PCを見ればUSBやイヤホンジャックなど、共通化されたインタフェースばかりです。これがもたらすメリットは、インタフェースに適合している機器であれば何でも取り付けられるということです。言い換えれば、共通のインタフェースは柔軟な拡張性をもたらすものであり、それが良いインタフェースデザインであれば「疎結合」も促進します。

また、共通のインタフェースにはヒューマンインタフェース上のメリットもあります。ボタンやスイッチなどの使い方が共通であれば使い方を新たに覚える必要もありませんし、ミスの割合が減ることも期待できます。

プログラミング言語も人間が使う道具なので、同様のメリットがあると考えられます。つまり柔軟な拡張性と、ミスの低減です。新しい機能を追加するときに現状のコードにほとんど変更を加えることなく自然な形で追加できたり(柔軟な拡張性)、ifやswitchで分岐することなく共通インタフェースを呼び出すようにすることで、新たな機能を追加したときに書き加える箇所を減らすことができたり、連結や分解、探索、ソートなど汎用的な処理に対して呼び出すべき関数がすぐにわかったり(ミスの低減)します。

まとめると、なぜポリモーフィズムが嬉しいのか?に対する答えは「良いインタフェースを設計するのに便利だから」です。これが僕が考えるポリモーフィズムのメリットです。

その他の分野でのポリモーフィズム

ポリモーフィズムという言葉をより深く理解するため、他分野においてどのように使われているかもさらっと見てみましたのでご参考までにどうぞ。疎い分野なので間違いなどあったらすいません。

生物学

同じ種の生物の中で形や構造などが異なることをポリモーフィズムと言います。人間であれば男女の性差、目の色や肌の色、血液型などを指し、そのうち遺伝子に基づくものを遺伝的多型(Genetic polymorphism)といいます。

物質科学

同じ化学組成だが複数の異なる結晶形をとることをポリモーフィズムと言います。温度や圧力など、結晶が生成するときの条件によって結晶系が決まるようです。例えば、グラファイトとダイヤモンドはどちらもC(炭素)からなります。

おわりに

いかがでしたでしょうか。ポリモーフィズムについて様々な角度から見ていきつつ、その概念やメリットについて考察してみました。何か新たな発見があれば幸いです。

参考

結局ほとんど英語のWikipediaを参考にしました。

UIPasteboardをもっと理解する (後編)

「UIPasteboardをもっと理解する」の後編です。前編はこちらになります。

UIPasteboardをもっと理解する (前編) - 騒音のない世界 BLOG

後編は具体的な動作例とハマりがちな注意点について見ていきます。

具体的な動作を見る

ペーストボードの中身をログに吐いて、具体的な動作を見ていきます。環境はシミュレータ(10.2)です。ログに吐くコードはこんな感じです。

func printPb(_ pb: UIPasteboard) {
    for item in pb.items {
        for (key, value) in item {
            print("\(key): \(value)")
        }
    }
}

UITextViewやUITextFieldからテキストをコピー

画面を長押しして出るメニューからCopyを選んでHogeという文字をコピーします。

f:id:a_beco:20170124041619p:plain

public.utf8-plain-text: Hoge
com.apple.flat-rtfd: <72746664 00000000 ...>
Apple Web Archive pasteboard type: <3c21444f 43545950 ...>

複数の値が入りました。1つずつ見ていきます。

最初のpublic.utf8-plain-textutf-8エンコーディングのプレーンテキストのようです。エンコーディングごとにUTIが違うことがわかります。2つめのrtfdはRich Text Format Directoryというやつで、画像なども含められるAppleのリッチテキストフォーマットです。3つめのWeb ArchiveというのはSafariでつかうフォーマットで、htmlや画像、音などリソースも含めたウェブページを保存するフォーマットです。

このように、標準のUITextViewなどからコピーすると複数のフォーマットでコピーされることがあります。

写真アプリでコピー

標準の写真アプリで写真をコピーしてみます。

com.apple.mobileslideshow.asset.localidentifier: <31303645 39394131 ...>
public.jpeg: <UIImage: 0x608000092390>, {4288, 2848}

こちらも複数の値が入りました。

最初のものはよくわかりませんが、com.apple.mobileslideshowというのが写真アプリのバンドルIDなので、おそらく写真アプリの内部でのみ用いるデータフォーマットかと思われます。2つめは単にjpegフォーマットです。

また、テキスト、写真ともにUIからコピーするとそれまで保持していた値はクリアされるようです。

コードでコピー

コードから画像と文字列をコピーしてみます。

print("===")
UIPasteboard.general.image = UIImage(named: "sample")
printPb(UIPasteboard.general)
print("===")
UIPasteboard.general.string = "Hoge"
printPb(UIPasteboard.general)
print("===")

結果はこうなりました。

===
public.jpeg: <UIImage: 0x6080002809b0>, {512, 512}
com.apple.uikit.image: <UIImage: 0x60800009fd10>, {170.66666666666666, 170.66666666666666}
public.png: <UIImage: 0x60000009f630>, {512, 512}
===
public.utf8-plain-text: Hoge
===

imageプロパティに画像をセットするとjpeg, pngに加えてcom.apple.uikit.imageというものがコピーされました。元データのフォーマットによらず複数のフォーマットでコピーされます。ドキュメントを見てもpngとjpgで保存される旨が書いてあります。

テキストは単にセットするとUTF8のプレーンテキストになりました。

[2017.03.30 追記] iOS8系, 9系だとimageプロパティに画像をセットした時にcom.apple.uikit.imageしかコピーされません。この場合iOS8系のメモ帳アプリに貼り付けられませんでした。他にも貼り付けられないアプリがあるかもしれないので、直接itemsプロパティに代入したほうが良いかもしれません。

ペースト先の処理を考える

コピーしたデータをUI操作によりペーストできるかどうかは、ペースト先のアプリが対応しているUTIにより決まります。ペースト先のアプリはペースト操作が実行されるとペーストボードから対応UTIのデータ取得を試み、あればそのデータを取得してUIに反映します。

ドキュメントにも注意点として書かれていますが、自分でコピー処理を実装する場合、ペースト先のことを考えてさまざまなフォーマットでコピーしておくと親切です。例えば、画像をコピーするためにpublic.pngにのみ保存していると、public.jpegにしか対応していないアプリにはペーストできないことになり、ユーザが混乱してしまう可能性があるので注意が必要です。UITextViewや写真アプリでコピーした際に複数のフォーマットでコピーされるのもそのような理由があるものと思われます。

間違えがちなポイント

特定フォーマットの存在チェック

ドキュメントによると、stringプロパティやimageプロパティのnilチェックによってペーストボード内にデータがあるかどうかをチェックしてはいけません。代わりにiOS10から追加されたhasStringsなどを使うと良いようです。これはリソースを無駄に消費しないようにするためのようです。

注意点として、hasStringsstringの有無ではなくstringsの有無と対応する、ということがあります。stringは1つめのPasteboard Itemからしか文字列を返さないのに対し、stringsはペーストボード内のすべてのPasteboard Itemからすべての文字列を返します。なので2つめ以降のPasteboard Itemに文字列が入っている場合、hasStringsがtrueでもstringnilになることがあります。下記のコードで再現します。

let pb = UIPasteboard.general
        
pb.image = UIImage(named: "sampleImage") // 1つめのPasteboard Itemは画像を入れる
pb.addItems([["public.utf8-plain-text" : "hoge"]]) // 2つめのPasteboard Itemは文字列を入れる
        
print("hasStrings: \(pb.hasStrings)")
print("string: \(pb.string)")

結果

hasStrings: true
string: nil

stringの有無を調べたければcontains(pasteboardTypes: [String])を使うと良いです。こちらは先頭のPasteboard Itemしかチェックしません。文字列のUTIはUIPasteboardTypeListStringという配列から取得できるので、contains(pasteboardTypes: UIPasteboardTypeListString as! [String])のように書けます。先程の例に適用すると、この戻り値はfalseとなります。

ペーストボードからデータ削除

UIPasteboard deleteなどで検索すると空文字をセットするコードがたくさん出てきますが、これはあまりよくないかと思います。細かい話ではありますが、空文字は文字列としてペーストボードに保存されてしまうため、UITextViewで長押しした際のメニューで無駄にPasteが出てしまいます。

f:id:a_beco:20170124035844p:plain

データ削除はitemsプロパティを直接操作することで可能です。全て削除する場合は単にUIPasteboard.general.items.removeAll()とすれば良いです。先程のメニューはこうなります。

f:id:a_beco:20170124040000p:plain

おわりに

2記事に渡ってUIPasteboardについてまとめてみましたが、いかがでしたでしょうか。何か新しい発見があれば幸いです。