2016年12月13日火曜日

Instruments を使っていてシンボルが見えないときの対処法

Instruments を久しぶりに起動して Time Profiler を使おうとしたら、びっくり仰天シンボルが全部見えない状態になっているじゃあありませんか。 Swift化したのがあかんのか!?と思って調べてみました。

結論から言うと、先日ビルドの軽量化のためにDebugビルド時にdSYMの出力を止めていたのが原因でした(´・_・`)
http://stackoverflow.com/questions/36882788/how-to-create-dsym-file-in-xcode-7-0
Debug Information FormatをDWARF with dSYM Fileに戻して、dSYMの出力を再開したら問題なくシンボルが出力されるようになりました。なんじゃそりゃ。

それでもだめな場合はInstruments側のFile -> Symbols... を選択すると、直接モジュールのdSYMを指定する事が可能になります。覚えておくと便利かもです。
http://stackoverflow.com/questions/33683690/apple-instruments-has-mangled-symbols-and-greyed-out-symbol-names-when-profiling

2016年12月1日木曜日

pyspa Advent Calendar 2016 1日目: 今年の話

去年に引き続きまして pyspa Advent Calendar 2016 一番槍担当の斧さんakisuteです。よろしくお願いします。

去年はゲームの話をしましたが、今年は人生に一度しかやらなさそうなイベントを大量に発生させましたので、主に今年何をやっていたのかについてご報告させていただきたいと思います。

1. 家を買いました

ずっとワンルームのアパートにしか住んだことがない男だったのですが、30過ぎたので人生一度きりだし家でも買ってみるかと思い、半年ほど探して思い切ってタワーマンションの部屋を買ってしまいました。

正直ちょっと前まで家を買うとか単に負債を抱えるだけの大アホだと思っていたのですが、実際に買ってみると案外悪くなかったです。売ればいいとか資産価値があるとか賃貸より買ったほうが割安とかそういうことはさんざん世間様がおっしゃってるかと思いますので、ここではあんまり世の中では言われていない気づきを書いてみようかと思います。

買うと(当たり前だけど)自分のものになる

賃貸で引っ越しするのとはまるで違う感覚です。まず引き渡しですが鍵と権利書をもらうだけです。クリーニングすらされてません。売主は貸主様じゃないので当たり前です。そこでハウスクリーニングを自分で呼んで掃除してもらい、あとは業者を呼んで鍵を付け替えてもらったりします。なんというか、自分のものなんだな、今まで家を借りてた相手の家主さんというのはこういうことをやってたのかな、という感じがします。

当然(マンションの管理規約の範囲内で)部屋は自分のものなので、壁に穴を開けようがぶち破ろうが工事してみようが全て自由・自己責任となります。逆に老朽化した設備を保守点検するのも自分の責任となります。とにかくなんというか、守るものができたなぁという感じを大いに受けます。

売買は意外と大雑把

私の場合売主さんが全ての鍵を渡すのを忘れてましたというのが後から発覚して大騒動になりました。最終的には無事全部私の手元に来たのですが、大手の不動産仲介屋というのも案外雑なもんだなという気持ちになりました。

あと管理費・修繕積立金の将来の計画(当然年度が上がるごとに高くなる、大雑把に言って5年おきに値上がりで、30年で2倍に上昇すると見積もればだいたいOK)ですとか、固定資産税の額ですとか(これは実際に買うまで見積もりもなかなかできない、年間20万円ぐらいすることもある)、家が広くなるので水道光熱費が上がるとか、そういう諸費用は契約直前までなかなか教えてくれないので注意した方がいいです。大体不動産屋も銀行もローンと最初の管理費・修繕積立金ぐらいまでしか計算に入れずに大丈夫ですよとか言って勧めてくるので目に唾を付けて聞いてください。私の場合はお願いした不動産屋さんが知り合いのツテで、大変親切に説明していただいたお陰で難を逃れました。

買うと銀行が身近になる

家を買わない限り銀行なんてただの財布の延長程度の扱いでしかない、という感覚だと思うのですが(特に私と同年代以下の方は)、家を買うとなると銀行が本格的にお客さんというかパートナーというか商売相手になるというか・・・とにかく全く別の相手になります。銀行の担当の人とは仲良くしましょう。
それから自分の口座から頭金として3桁万円の現金札束を引き出したり、4桁万円の融資が飛び込んできたりするのが目の前で見られてちょっと社長にでもになった気持ちが味わえます。でもできればあんまり味わいたくないです(´・_・`)

買うと強制的に勉強せざるを得ない状態になる

やれ保険があるだのいざとなれば売ればいいだの言われますが、借金は借金です。返せなければ死ぬしかありません。今は金利も安いですが将来はわかりませんし、何より管理費・修繕積立金・自宅設備の更新など、将来必要になるお金は増える一方で減ることは絶対にないと悲観的に考えたほうがよいぐらいです。そして我々の給料はほっといても絶対に上がりません。退職金も年金もありません。

よく家を買うと保守的になるだの守りに入るだのというネガティブな考えを持ちそうになりますが、そんなことはありません。むしろこの激動の時代において、守りに入ったら即座に借金が返せなくなって死にます。死にたくなければ勉強し続け変化し続け攻め続けるしかありません。最大の防御は攻撃です。

そういった理由で最近はゲームをする時間もめっきり減って、代わりに株式投資だの投資信託だのの勉強をしてみたりとか、経済のニュースを見て将来の住宅ローンの金利がどうなるか調べてみたりだとか、仕事もiOSアプリばっかりいじってないでDockerだのansibleだのに手を染めてみたりだとか、筋トレする時間を毎日取って体調を維持してみたりだとか、そういうおっさんっぽい規則正しい生活を強制的に余儀なくされております。

家を買うまではそれまでの努力の成果に甘えてダラダラ過ごせてしまっていたので、家を買って強制的に借金を持って自分を追い込んだのは大正解だったと思っています。

買うと視野が広くなる

生活が強制的に変わるので当然といえば当然なのですが、その他家が広くなるので今まで置けなかった家具が置けるようになったり、筋トレ器具を置けるようになったり、最新のIoTっぽいアイテムを買って試してみたり、カーシェアリングサービスを使ってみたりと、試せるものの範囲が広がります。これは今まで生まれてきてプログラマーしかやったことがない私には大変刺激的です。特にルンバや食洗機には本当に感動していて、私が作ってきたiPhoneのアプリなんてこのルンバの1/100も人の役に立たないと反省することしきりです。

近所付き合いの話

よくタワーマンションを買うと上下格差が〜とか近所付き合いが〜とか言われますが、今私が住んでいるところでは特に上下格差が〜みたいなものは一切見られません。その代わりと言っては何ですが管理組合の人が良くも悪くも恐ろしく有能で厳しい人で、とにかく「マンションの資産価値を守る」という点において凄まじいです。引っ越しの日に私が使った業者の養生がショボかったのを見つけて廊下が割れたらどうするつもりだとものすごい勢いで業者の人に怒鳴ってました。後で菓子折り持って謝りに行きました(´・_・`)

逆に防災訓練を定期的に開催したり、子供向けのハロウィンパーティを開催したり、設備に不備があったらゼネコン建設会社や販売会社のアフターサービス担当に殴り込みに行って無償修理させるなどの活躍をしているようで、敵に回したら怖いけど味方にしたら頼りになるなぁと思って見てます。

近所付き合いに関してはやはり多少はあって、ちゃんと出会った人には挨拶して、お子さんには愛嬌よくして、お隣さんには菓子折り持ってよろしくお願いしますと挨拶に行って、ぐらいは当然必要になります。ですが慣れればなかなか楽しいものです\(^o^)/

2. 婚活をしました

30過ぎたのでいっちょ結婚ぐらいした方がいいだろと思い、ツヴァイというところに申し込んで婚活というやつをしてみましたのでご報告いたします。興味のある方は参考にしていただければと思います。

金の話

大体1年間で30万円払えばOKです。そうすると年間72人だか48人は紹介してもらえることがシステム上保証されます。私はと言うと大体300人弱ぐらい紹介を受けることができました。
なお2年目以降はまた30万円が必要になりますので、短期決戦をおすすめします。なお後述しますがどっちにしろ2年以降続けることはあまりないと思います。

ツヴァイの話

個人的にイオンが好きなのでツヴァイにしたのですが、どうやら婚活業界的には以下のようなヒエラルキーが構成されているようです。

無料アプリ
↓
Omiai, Match, ゼクシィなどの有料アプリ(1万円〜)
↓
楽天オーネット(15万円〜)
↓
ツヴァイ(30万円〜)
↓
パートナーエージェント(40万円〜)

大変身も蓋もない暴言を申しますと、上のものほど婚活とは名ばかりの出会い系アプリで、下の方になるほど今度はアラフォーの行き遅れが滞留する阿鼻叫喚地獄となっております。このことは各社の資料を取り寄せてみて平均会員年齢などを調査すれば分かるかと思います。ツヴァイはだいたい35~40歳、パートナーエージェントになると40〜50歳がボリュームゾーンとなっていました。より上位のサービスで駄目だった人が下に流れてくるという構造があるのかもしれません。

大変な暴言を申し上げてしまいましたが、やはり高い額を払うだけあって、下のサービスになればなるほど入会審査も厳格で(市町村が発行する独身証明書や2年分の源泉徴収票、大学卒業証書を提出する必要があります)、きちんとした担当の方がついてくださってアドバイスを受けたり、相手の女性の方も本気度が高いです。実際に1年間やってみてツヴァイは本当に大満足でしたのでおすすめしておきます。

婚活の話

皆さんおそらく「本当に出会えるのか?」とか「居たとしてもアラフォーの嫁ぎ遅れとサクラばかりなのでは?」とか「自分の趣味に合う人なんているわけがない」とか色々不安があるかと思いますが、断言します。そんなことは一切ありません。普通に魅力的な方ばっかりです。

私のような海外のPCゲームばっかりやってて普通の女の子の話題なんぞ何一つわからないおっさんですら、1年間で300人近く紹介してもらえまして、1/5以上の方には普通にお付き合いの連絡を申し込み、連絡先を交換して実際にお会いしたのが1/10程度で、最終的に趣味がバッチリ合う理想的な女性の方と付き合う事ができました。私自身ビックリです。年間30万円出せて本気で結婚したい方は即座に試す価値があると断言します。

あとはもう一つ気になる、どういう男が人気があるか、ですが、周りの様子を見てみたところ、汚くなくて臭くなくて笑顔が気持ち悪くなければ見た目に関してはどんな人でも全く何の心配もありません。ただし汚いのと臭いのと笑顔が気持ち悪いのだけは即座にアウトで門前払いだったのでそこだけは徹底してください。あとやっぱり身長が高くて体格が良くて声が低い人のほうが(誤差の範疇程度ですが)人気があった気がします。

職業はやはり医者と公務員から順番に売り切れます。付き合ってた彼女もおっしゃってましたが、女性は驚くほど現実を見ます。ただ当たり前ですが医者と公務員じゃないと駄目ということは一切ないのでご安心ください。

むしろそんなことより大事なのは相手を思いやる気持ちをいついかなる瞬間も忘れないことだなぁと思う次第です。

結果の話

そして皆さん気になる結果の方はですが、四ヶ月ほど付き合った彼女に私の不徳のなすところから見事にフラれまして、私が伴侶を持つに値しない人徳のない男であるということが完全に証明された形となりました(´・_・`)
これに懲りて来年以降は変な欲を持たずに良いカルマを積んでいきたいと思います(´・_・`)

3. 焼肉をおごりました





シャトーブリアン、大変美味しゅうございました。

以上、よろしくお願いします。
明日の担当は「お前、誰よ」で一世を風靡したイアン・ルイスさんです。

2016年11月17日木曜日

UITouchGestureRecognizer をやめて UILongPressGestureRecognizer や UIButton を使ってみる

適当なUIViewにUITouchGestureRecognizerを貼ってタッチアクション可能にするという実装を行うことがありますが、UITouchGestureRecognizerはUIButtonと違ってタッチして離すタイミングではなくタッチした瞬間に反応してしまうためユーザビリティ的に望ましくない場合が結構あります。

そんなときはUILongPressGestureRecognizerを代わりに使ってみましょうと言う話です。
http://stackoverflow.com/questions/12830547/how-to-implement-touch-up-inside-in-touchesbegan-touchesended

UILongPressGestureRecognizerというと、長押しして何かコンテキストメニューを表示する際に使うイメージが強いものですが、minimumPressDurationを0.01など十分に短く取ると、事実上タップとほとんど変わらないユーザビリティを得ることができます。さらにUITouchGestureRecognizerとは異なりタッチを認識した後に離すまでの間のイベントをUILongPressGestureRecognizer.stateプロパティ経由で監視することができます。その為例えば
  • タッチされている間は見た目を変える
  • タッチを話したときにアクションを実行する
といった味付けが簡単にできます。

しかしもっと簡単に透明なUIButtonを作って目的のUIViewの上に重ね、そちらにタッチをハンドリングさせたほうがより綺麗で簡単にボタンらしい挙動を再現できたので別にUILongPressGestureRecognizerを使う必要はなかったかもしれません(´・_・`) まぁこういう使い方もあるんだよという参考になれば幸いです。

ビルドが遅いのをどうにかこうにかやりくりする方法

すでに皆様痛感されていると思いますが、Swiftはビルドがとんでもなく遅いです。正確に言うと、Swiftのビルドが遅いのに加え、clang moduleのビルドが遅いため、frameworkの数が増えてくるとビルドにかかる時間がどんどん破滅的なことになってきます。

Swift & dynamic framework導入前はアーカイブ込みで10分以下だったビルド時間が、現在は20分を超えてしまっており、更に今後もSwiftコードの増加ととframeworkの増加に従ってビルド時間が伸びることが予想されます。そこでなんとかしてこの時間を短縮しようと思って調べてみました。

そもそもビルド時間が伸びる原因


  • Swiftのビルドが遅い、特に型推論が遅い
  • frameworkのビルドが遅い
  • Xcode上でcleanを実行するとプロジェクト内でビルドしているのframeworkが全てビルドし直しになり分割ビルドの恩恵が得られない

それぞれ詳しく見ていきます。

Swiftのビルドが遅い、特に型推論が遅い

以下の記事が大変参考になります。
https://thatthinginswift.com/debug-long-compile-times-swift/
Swift 3.0になって随分と高速化したため、型推論のせいで異常に時間がかかるという自体は減りましたが、それでもObjective-Cのコードと比べると10倍以上時間がかかることが多々あります。型を明示すれば高速化に寄与できますが、そうするとSwiftのせっかくの旨味が減るため悩ましいところです。

frameworkのビルドが遅い

dynamic framework = clang moduleのビルドはコードのコンパイル以上の作業を多分に含むため、分割すれば分割するほどそのオーバーヘッドが増えて無駄に時間がかかることになります。

Xcode上でcleanを実行するとプロジェクト内でビルドしているのframeworkが全てビルドし直しになり分割ビルドの恩恵が得られない

個人的に最悪なのがこれだと思います・・・せめて特定のコードに対してのみcleanできれば良いのですが、Xcodeで各種トラブルを避けるためにはcleanは必須、しかしながらcleanを実行すると全てのframeworkがビルドし直しとなって分割ビルドの恩恵が得られません。

特にCocoaPodsを使っていると全てのpod管理下のframeworkが再ビルドとなるため大変効率が悪いです。

これらを踏まえて対処法を考えてみました。と言っても大したことは出来ないのですが、

  • 定期的にビルドタイムをチェックする環境を用意する
  • CocoaPodsをやめてcarthageを使用する
  • microframeworkをやめる、どうしてもmicroframeworkを行うのであればcarthageと併用する

ビルドタイムのチェック方法は https://thatthinginswift.com/debug-long-compile-times-swift/ に記載がありますが、Other Swift Flags-Xfrontend -debug-time-function-bodiesを追加してビルドし、ビルドレポートを見ればOKです。結果はpbpaste | egrep '\.[0-9]ms' | sort -t "." -k 1 -n | tail -10などで整形すると見やすくなります。100msを超えている関数があれば手を打つ必要があるかもしれません。

CocoaPodsですが、手元で全てのframeworkをビルドしようとするためcleanした際の無駄が多いです。carthageを使えば最初にビルドを一回行って後はそれを参照して使いまわすだけ、という風にできます。ただしcarthageは最初のビルドがやたら長く、CocoaPodsを使った場合より5倍以上遅い?と言う問題があるのでその点は注意が必要です。

microframeworkを辞める論についてはおそらくいちばん賛否両論議論があるところかと思いますが、少なくとも殆どの場合、開発中はコードを使いまわす回数と時間よりXcodeをcleanしてビルドし直す回数と時間のほうが支配的であると信じています。Xcodeがちゃんとしてさえいればベストプラクティスに従ったほうが良いのでしょうが、そのようなことは伝統的にないので、ベストプラクティスにこだわらずビルドを高速化するために一つのframeworkにまとめるのも悪くはないのではと思っています。
ただしcarthageと併用できるのであれば、一度ビルドしたmicroframeworkを使いまわすメリットが享受できるので良いかもしれません。その場合、microframework側はコードベースがある程度安定している必要があります。さもなければ何度も何度もcarthage updateを走らせ直して結局時間がかかることになります。

結論

いろいろ考えてみましたが、頑張ってビルド高速化するより、新しいビルドマシンを増設したほうが結果的に良いかと思います\(^o^)/

2016/12/01 追記

Qiitaにより詳細で素晴らしいまとめがあったのでそっちを見ておいたほうが良い気がします(´・_・`)

全体的に見て型キャストとかワンライナー(とそれを誘発する演算子等)による途中の型推論が劇的に遅い原因のようですね。まぁ来年ぐらいまでにはclangが賢くなって直ってるのではないでしょうか・・・多分・・・

2016年11月14日月曜日

UNNotificationAttachment.init(identifier: String, url: URL, options: [AnyHashable : Any]? = nil)に拡張子のないURLを渡す際の注意

iOS 10で導入されたUserNotifications.frameworkは大変便利ですが、落とし穴が早速幾つか見つかっておりますのでご紹介いたします。
参考: iOS 10で画像つきのNotificationを配信する - Qiita

表題にありますUNNotificationAttachment.init(identifier: String, url: URL, options: [AnyHashable : Any]? = nil)ですが、第二引数のurlに画像や動画などのメディアのURLを渡してUNNotificationContentに付与すると言う使い方をするのが一般的かと思います。

このときどうやらUserNotifications.frameworkはurlのpathExtension、要するに拡張子の情報を利用してメディアの種類を判別しているようで、以下のような状況が発生すると実機でのみUNNotificationAttachment.initの実行が失敗してnilが返却されます。

  • 第二引数のurlに拡張子が付与されておらず、かつoptionsに[UNNotificationAttachmentOptionsTypeHintKey: "<当該URLのメディアの適切なMIME Typeを表す文字列>"]が指定されていない場合。
  • または間違った拡張子やMIME Typeが指定されている場合。

なおシミュレータでは問題ありません。いきなり実機に持っていって困るということが有るかと思いますので十分ご注意ください。

対処法は2つあります。

  1. URLに適切な拡張子を付与する
  2. optionsに[UNNotificationAttachmentOptionsTypeHintKey: "<当該URLのメディアの適切なMIME Typeを表す文字列>"]を付与する

何れにせよURLが指し示すメディアの種類が正確にわかっていないと問題になるため、その点は注意が必要です。

2016年10月4日火曜日

Q. ATS を Debug ビルドでだけ無効にしたいのですが...

A. こちらの内容に従えば一発です。

要するにBuild Phaseにビルドスクリプトを追加してそこでPlistBuddyを使って値を書き換えましょうと言う作戦です。

もうちょっとマシな解説

普段、Info.plistの設定内容をConfiguration毎に書き換えたい場合は、たいていよくやるのがxcconfigファイルを以下のように用意して、
APP_BUNDLE_NAME = MyApp_Dev
でもってInfo.plistに対して
Bundle Display Name = ${APP_BUNDLE_NAME}
こんな風に書いておけばビルド時に自動的にXcodeが環境変数による置換処理を行ってくれる、というのを使うのですが、まいったことにATSの設定を司るNSAllowArbitraryLoadsはString値ではなくBool値でして、このInfo.plistに対するXcodeの環境変数置換処理はString値(と、確かNumber値)にしか使えないというオチがあります。仕方がないので最初に説明した方法が最善になるかと思います。Info.plistファイルをConfiguration毎に用意する方法もありますが、大概そっちのほうが設定のメンテナンスが面倒になるのでオススメしません。

2016年10月3日月曜日

ATS の AVFoundationに対する 例外条項の注意点

2016/10/03段階でのATSを有効にする際の覚書です。基本的には全てTLS 1.2以上をサポートするhttpsにしてしまえばOKですが、例外条項としてAVFoundationのStreamについてはhttpでもよいと例外条項が定められています。

https://developer.apple.com/videos/play/wwdc2016/706/?time=324

これについては設定不要で自動的に適用されるようにWWDCのビデオ上では見えますが、実は落とし穴があり、この例外条項はiOS 10以降でのみしか適用されません。iOS 9については実はAVFoundationのStreamについてATSが完全に適用されているようで、httpの動画をStreamingしようとしても失敗してしまうので注意が必要です。

2016/10/03 16:45 追記
こちらXcode 7.3.1でビルドしたバイナリにて調査した内容ですので、SDKのバージョンが異なるXcode 8.0以降でビルドしたバイナリであればiOS 9.x系でもAVFoundationのStreamについてATSの例外条項が適用されてhttpで通信できるようになるかもしれません。ただ紹介した参照元のWWDCのビデオを見ても分かる通り、基本は例外を考えず全てhttps化するのが良いとされているため、ビデオストリームについても可能であれば全てhttps化するほうがトラブルがないかと思います。

2016年9月11日日曜日

Parse.com が潰れたので新しい mBaaS を探す旅に出た

定番のmBaaSとして人気だったParse.comが終わってしまうことが確定してしまった2016/09/11現在、次はどのようなmBaaSを選ぶのが良いのか調べてみました。なおどのプロダクトも全然試せていないので実戦での詳細な評価についてはできかねます。むしろこれを読んだ皆さんに果敢にトライしていただきたい!\(^o^)/

Niftyクラウド mobile backend


http://mb.cloud.nifty.com

  • 国内だとここ
  • ちょっと前にひとりぼっち惑星がここ使ってるといって有名になった
  • 正直機能的にはダメだと思ってる
    • その話題になったブログ記事の内容が、データストアにトランザクションに相当する処理を新機能として導入しました、とかDBキーによる高速ルックアップを実装しました、とかそういう次元の、あまりにも最初から存在して当然と思われる機能を新規実装として誇っているような内容だったため
    • 逆に言えば今後もこの調子で継続的に改善してくれるかも
  • Niftyが社命賭けてるっぽいしそう簡単には潰さないと信じてる

Google Firebase


https://firebase.google.com

  • なんといってもGoogle様ブランドがある
  • Analyticsが非常に素晴らしい、GAをリプレイスして、Analyticsのためだけに導入する価値がある
  • 一方でiOS/Android/Webプラットフォーム全てで同一の概念を利用できるように、各プラットフォーム独自の箇所をラップして激しく潰しているように見える
    • 特にFCMと呼ばれるPush通知に相当する箇所についてはAPNSが元の影も形も見えないぐらい激しくラップされていて操作できないため、例えば今の時期だとiOS 10向けのUserNotifications.frameworkに対応させるのが実質不可能とかそういうデメリットがある
    • iOS側が割を食いそうな不安はある
    • とはいえ調べてみたところiOS 9からのUniversal LinkはApple仕様に従ってきちんと対応するようになっていて、Firebase側は全く無視する気はなく、対応が遅れるのを許容できるのであれば大丈夫そう
  • Googleだし気合入れてるみたいだしそう簡単には潰れない・・・はず・・・
    • とはいえ最近のGoogleは不採算プロダクトを平然と潰すので油断ならない

backendless


https://backendless.com

  • パッと見一番Parse.comっぽい
  • トップページからして「Migrating from Parse? Welcome to Backendless!」だし
  • 一番Parseっぽくて下手にラップするようなことをしておらず、かつ必要な物は全部あるので最も個人的には好感
    • Firebaseみたいに激しくラップされすぎる構造は、例えるならRuby on RailsやPython Djangoのような感覚があってどうも個人的には好きになれない
  • ただしParseの二の舞いのように潰れる可能性もまた否定出来ない


まとめ

どこも潰れるリスクがワンチャンありそうですね(´・_・`)
ご利用はご計画的に\(^o^)/

iOSのフォントのお話

最近フォント周りについて深く掘り下げる機会がありましたので、その際のメモを残しておこうと思います。かなり読む人置いてけぼりな中身になってますが、フォントを詳しく触り始めるとなるほどーとためになる(と思う)のでどうかご了承ください(´・_・`)

UIFontのプロパティについて

UIFontにはフォントに関する数値を表すプロパティが存在します。いろいろありますが、もっとも重要なのは以下に列挙するプロパティです。

  • pointSize
  • lineHeight
  • ascender
  • descender
  • leading
  • capHeight

以下の画像を見ると非常にわかりやすいかと思います。


参照元: https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html および http://stackoverflow.com/a/35922853

これを踏まえて各プロパティを解説すると以下のとおりになります。
  • pointSize
    • フォントのサイズを表します。
    • ほとんどすべてのケースでpointSize = ascender + descenderとなり、すなわちpointSizeは上端と下端に空白を含まない純粋なフォントの高さだけを表す数値であると言っても良いと思います。
      • 上下に空白を作らない高さでフォントを描画したい場合はこの高さを使うと良い、と言い切って間違いないと思います、ごく一部の変なフォント以外。
  • lineHeight
    • フォントの上部末端(上端空白が存在する場合はそれも)から下部末端(line gapも存在するなら含む)までの完全な高さを表します。
    • 飾り文字などではないまっとうなフォントであれば、必ずこのlineHeightの中に文字の高さが収まるようになっています。
      • 一部飾り文字のあるフォントについてはfont leadingを使って下部末端のline gapを表現していることがあります。
    • pointSizeとの最も大きな違いは、上端と下端にline gapに相当する空白を持っています。特にシステムフォントで顕著にそれが見られます。詳しくは後述。
    • 実際にフォントが画面上にレンダリングされるときはこのlineHeightがすべての基準になっているケースが多いです。
  • ascender
    • 全てのアルファベットの文字の最大高さを表します。
    • この高さまで描画されるのはごく一部の文字だけで、殆どの文字はcapHeightまでの高さしか使いません。詳細はこの後のcapHeightを参照してください。
  • descender
    • アルファベットのBaselineより下に描画される文字、例えばj p q yなどの下付き部分の高さを表します。
    • 日本語の文字でこの領域に描画される文字は私が知っている限りでは存在しません。
    • UIFontが返す値は常にマイナスになるので気をつけてください。
    • CTFontが返す値は常にプラスです。
      • めんどくさいので常にfabs()を通すといいと思います。
  • leading
    • Baselineからフォントの下部末端(line gapを含む)までの高さを表します。
    • 上記の図ですとline gapの部分だけがleadingとなっていますがヒラギノフォントで試した限りは誤りです。Baselineから下全てが対象になっているようです。
    • lineHeightに含まれている下部末端のline gapとは「全く別の値が付与されていることがあります」。
      • 例えば飾り文字のあるフォントについて、lineHeightよりもフォントの描画が大きくなるぐらい下に飾りが伸びていたとしても、leadingの値はそこまで考慮して付与してあるので、usesFontLeadingのオプションを付与すると下の行にはみでなくなる、みたいなケースが存在します。
  • capHeight
    • アルファベットの大文字の最大高さを表します。
    • 日本語の文字は(特に漢字は)基本的にすべてcapHeightの高さに描画されます。
    • capHeightより上に描画されるのは、例えばÅ(Aウムラウト)のように修飾子が付いているアルファベットや、日本語だと濁点および【】隅付き括弧のような一部の文字が、capHeightよりも上に描画されます。
特に覚えておきたいのがleadingというプロパティの働きです。UIKit, TextKit, Core TextにusesFontLeadingというような名前のオプションが存在するケースが多々ありますが(例えばNSAttributedString.boundingRect(with:options:context:)のoptionsとかですね)、これは何かというと、
usesFontLeadingが指定されている場合、lineHeightの計算の際に常にUIFont.leadingの値が使用される。従って、もしattributed stringにparagraph styleを付与してlineSpacingに0を付与していたとしても、フォントにleadingの値が付与されていたら、それ以上に行の高さが低くならず必ず行間が開いてしまう。
というオプションになります。paragraph styleでlineSpacingやparagraphSpacingを0に指定しているにもかかわらずどうしても行間がゼロにならないとか、指定するより広がってしまうみたいな状況になった場合は、このfont leadingを疑ってみてください。

ちなみにiOS 8以上については以下のとおりです。

  • UILabelは常にfont leadingを使用しません。
  • UITextFieldは未調査ですがおそらく常にfont leadingを使用しません。
  • UITextViewはデフォルトfont leadingを使用しますが、UITextView.layoutManager.usesFontLeadingの値をfalseにすることで回避可能です。
  • その他のラベルパーツ(UIButton.titleLabelなど)については完全には調査できていませんが、UIButton.titleLabelに関してのみいうとiOS 9の地点ではfont leadingが常に「使用されてしまいます」。これはおそらくiOS 6以前のUILabelの挙動がそのまま残っているためではないかと思われます。同じUILabelクラスですが中身と挙動がUILabelと異なるため気をつけてください。このようなケースは他にもあると思われます。

iOSビルトインフォントの問題点

幾つかのiOSビルトインフォントについては、先述のUIFontのプロパティの値に疑問がある数値が設定されているケースが有るようです。具体的にご紹介します。

1. ヒラギノフォントの場合

ヒラギノフォント、すなわちヒラギノ角ゴシックW3/W6、それからヒラギノ明朝W3/W6については以下の様なフォントの設定の特徴があります。
  • leadingの値がきちっと付与されています。
  • 常にpointSize == lineHeightになります。
  • おそらくバグだと思うのですが、descenderの値が本来必要な量の半分しか設定されていません。そのためヒラギノフォントを明示的に付与したラベルにg, j, y, qなどの下付き部分があるアルファベット文字を描画すると下が途中で千切れてしまいます。
    • 対処法としてはfont leadingを使用するか、または自力でdescender分だけラベルの高さを高くするか、明示的に指定してヒラギノフォントなんて使うのをやめてシステムフォントを使用してください。

2. システムフォントの場合

次にシステムフォントはどうなっているのかというと、以下の様な特徴があります。
  • 常にleadingの値がゼロになります。従ってfont leadingを使うモードで描画しても使わないモードで描画しても全く同じ結果になります。
    • iOS 8はHelvetica Neue, iOS 9以上はSan Franciscoが採用されていますが、いずれのケースでも全く同様の設定になっています。
  • pointSizeよりlineHeightが高く、ちょうどその差分の分だけ自動的にline gapとして行間が開くような設定になっています。
    • 要するに簡単にいえば、font leadingを使わないで、かつattributed stringでlineSpacingやparagraphSpacingをゼロに設定したとしても、必ず行間がちょっとだけ空いてしまいます。まずやることはないと思いますが、正確に行間ゼロを作るのは著しく困難です。

UIFontのpointSizeについて

UIFont.pointSizeについて、みなさん普段から何気なしに17とか14とか値を指定されているかと思いますが、このpointSizeに指定する値の単位って気にされていますでしょうか?このフォントのpointSizeとはPostScriptポイントないしDTPポイントと呼ばれるものらしいです。一般的に1PostScriptポイント=1/72インチとされています。

ではこの単位はiOS上で使われている論理座標系のpt単位とは全く違うものなのかというと、どうやら大昔のMacintoshのころからの名残で、macOS/iOS上の論理座標系の1pt=1PostScriptポイントとなるように定められていて、これは現在に至るまで常に維持されているということらしいです。

要するに、UIFontのpointSize=iOS上での1ptと完全に一致します。このことはlineHeightが16のフォントを画面上にレンダリングすると論理単位16pt(2倍Retinaなら32pxになる)ことからも確認ができます。便利ですね。


UIFont.leadingのiOS 8以下での不具合

かいつまんで言うと、UIFont.leadingの値は、iOS 8以下のときに常にUIFont.lineHeightと同じ値を返します。これは"font leading"という単語が使われている文脈や文化圏によって意味が異なり、font leading = line heightとなる文化圏があったので誤用されてしまった、ということらしいです。しかしながら実際のiOSでは先述の通り「font leading = BaselineからフォントのLine gapを含む下端までの距離」と定義されていますから、このような値では困ります。

対処法として、iOS 8以下の場合はCTFontを使ってleadingの値を取得してください。こちらにコードを用意しました。
https://gist.github.com/akisute/3c3d162da73abd784525c9ed7859cda2

なお、iOS 9以上はこの問題は発生しません。iOS 8なんてそろそろサポート切れるんで忘れていいかもしれませんが、念のため。

まとめ

デザイナーさんにフォント周りで行間調整がおかしいぞーとかベースラインがずれてるぞーとか突っ込まれまくっていて必死こいて修正方法を調べている時に、こんな記事を見つけました。

https://www.raizlabs.com/dev/2015/08/advanced-ios-typography/

Stop Saying "No" to Designers.
このサイトには英字フォントをBaselineではなくcapHeightの位置で上揃えにするべく奮闘した一人の漢の話が書かれています。おお、海外にも私と同じ境遇の漢が居たのです。

我々もデザイナーにNOと言わないタイポグラフィを実現させたいですね\(^o^)/

2016年6月30日木曜日

Auto Layout と Manual Layout を混載させるときに役立つ UIView.translatesAutoresizingMaskIntoConstraints プロパティの話

Auto LayoutがiOS 6で導入されてはや4年、未だによく理解していなかった挙動に UIView.translatesAutoresizingMaskIntoConstraints があります。このプロパティは自分がプログラムコード上で生成したviewをAuto Layoutするときにfalseにする必要があるものということで皆様記憶されているかと思うのですが、具体的にこのプロパティは何をやっているのかが個人的に全く謎でした。それが今日一つ謎が解けましたのでここに共有させていただきたいと思います。

UIView.translatesAutoresizingMaskIntoConstraintsの値がtrueのときとfalseのときの違いについて以下に記載します(iOS 8以上で確認しています)。

  • trueのとき
    • 対象のviewのframe、すなわちx, y, width, heightの4つの要素をview.frame, view.bounds および view.center プロパティから直接操作することが可能になります。これはAuto Layoutが導入される以前のiOSの世界と同じ状態です。この挙動をAuto Layoutとマッチさせるため、対象のviewのx, y, width, heightの4要素を指定された値に固定するようなAuto Layout Constraintsが自動的にシステムによってviewに挿入されます。この自動的に挿入されるAuto Layout Constraintsのpriorityは常に1000 (Required)になります。
  • falseのとき
    • 対象のviewのframe、すなわちx, y, width, heightの4つの要素はすべてAuto Layoutエンジンが管理するようになり、view.frame, view.bounds および view.centerの値を直接書き換えても一切無視されるようになります。Auto Layout Constraintsが設定されていない場合、viewのframeはCGRect.zeroになります。

プロパティの名前にAutoresizing Maskとか入っているのでてっきりAutoresizingの仕組みに影響している用に見えますが、実際には全く関係ありません。その証拠にAutoresizingMaskの値をどのように変化させても勝手にAuto Layout Constraintsが挿入されてしまいます。このプロパティはあくまで当該viewのframeを自動的に操作するようなAuto Layout Constraintsを挿入するか否かを決めるフラグとして覚えると良いでしょう。

さてこの挙動を覚えると何が嬉しいかと申しますと、Auto Layoutと非Auto Layoutを混載させるときに非常に役立ちます。こうすることで、特定のviewだけをframe手動操作で設定し、他のviewはAuto Layoutに任せるというような荒業が自由自在に可能になります。

具体例を見てみましょう。例えば以下の様なニュースを表示する画面を作ってみようと思います。



ここでこのnewsを表示するviewのframeは複雑なアニメーションをさせたいなどの理由で外部からマニュアルで設定したいが、viewの中身はauto layoutに任せたいというようなケースがあるかと思います。

というわけで普通にAuto Layoutで作ってみましょう。
private func commonInitialize() {
        self.translatesAutoresizingMaskIntoConstraints = true
        self.backgroundColor = UIColor.white()
        
        self.imageView = UIView()
        self.imageView.translatesAutoresizingMaskIntoConstraints = false
        self.imageView.backgroundColor = UIColor.green()
        self.addSubview(self.imageView)
        
        self.titleLabel = UILabel()
        self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.titleLabel.text = "factorio alpha 0.13 has been released!"
        self.titleLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleTitle1)
        self.titleLabel.numberOfLines = 2
        self.addSubview(self.titleLabel)
        
        self.articleLabel = UILabel()
        self.articleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.articleLabel.text = "In 0.13 we have the new multiplayer matching server and server browser. This will let you find games of people online join your friends and other stuff. Server games are published to the server and clients can browse existing games. The first thing you will notice is the new multiplayer menu. When you click on 'Browse Public games' you will be asked to log in to your factorio account."
        self.articleLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleBody)
        self.articleLabel.numberOfLines = 0
        self.addSubview(self.articleLabel)
        
        let views: [String: AnyObject] = ["imageView": self.imageView,
                                          "titleLabel": self.titleLabel,
                                          "articleLabel": self.articleLabel]
        
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[imageView]|", options: [], metrics: nil, views: views))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[titleLabel]-10-|", options: [], metrics: nil, views: views))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[articleLabel]-10-|", options: [], metrics: nil, views: views))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[imageView]-10-[titleLabel]-10-[articleLabel]|", options: [], metrics: nil, views: views))
        self.imageView.addConstraint(NSLayoutConstraint.init(item: self.imageView, attribute: .height, relatedBy: .equal, toItem: self.imageView, attribute: .width, multiplier: 0.66, constant: 0))
    }


しかしながらこのコードはAuto Layout Warningが発生してしまいます。

2016-06-30 23:24:12.050906 AutoLayout[1725:80607] [LayoutConstraints] Unable to simultaneously satisfy constraints.
 Probably at least one of the constraints in the following list is one you don't want. 
 Try this: 
  (1) look at each constraint and try to figure out which you don't expect; 
  (2) find the code that added the unwanted constraint or constraints and fix it. 
 (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "NSAutoresizingMaskLayoutConstraint:0x7fd60ae1db90 h=--& v=--& AutoLayout.Example2View:0x7fd60ad35b70.width ==   (active)",
    "NSLayoutConstraint:0x7fd60ac360a0 H:|-(10)-[UILabel:0x7fd60ad3c6b0'factorio alpha 0.13 has b...']   (active, names: '|':AutoLayout.Example2View:0x7fd60ad35b70 )",
    "NSLayoutConstraint:0x7fd60ac36380 H:[UILabel:0x7fd60ad3c6b0'factorio alpha 0.13 has b...']-(10)-|   (active, names: '|':AutoLayout.Example2View:0x7fd60ad35b70 )"
)

これは先程のUIView.translatesAutoresizingMaskIntoConstraintsについての説明を元にすると以下のように解釈できます。

  1. UIView.translatesAutoresizingMaskIntoConstraintsがtrueに設定されていることにより、このviewにはwidth=frame.size.widthになるようなAuto Layout Constraintsが自動的に設定されている。
  2. このviewにはさらに "H:|[imageView]|"となるようなAuto Layout Constraintsが設定されている。これはimageViewを横幅いっぱいに表示するため。
  3. しかしながらこのような設定を行うと、imageViewが親となるviewの横幅を自分の横幅に合わせて引っ張ろうとするConstraintsが定義されてしまうので、1. で自動的に設定されたConstraintsと衝突してしまう。
  4. 結果としてwarningが発生する。
これを回避してやるにはいくつか方法があります。

  1. "H:|[imageView]-(0@999)-|"のように設定することで、右側ないし下側のpriorityを999に下げる。こうすることによってUIView.translatesAutoresizingMaskIntoConstraintsによって設定されるConstraintsのpriorityが勝つためワーニングは発生しなくなる。
  2. 両側を引っ張るようにvisual formatを使って設定するのをやめて、view.x=imageView.x, view.width=imageView.widthとなるようにConstraintsを付与する。
例えば2. のケースはiOS 9以降であればNSLayoutAnchorを使って簡単に設定ができます。

self.imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.imageView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0).isActive = true

これでAuto LayoutとManual Layoutをより自由自在に混載させることが可能になると思います。

2016年6月19日日曜日

ゼロから始めるマインクラフトのサーバー構築の話

突然なのですが、最近いまさらながらマインクラフトに大ハマリしておりまして。きっかけは友人のマルチプレイサーバーでプレイしたことだったのですが、やっぱり皆で遊ぶゲームは面白いなと再確認している次第です。マインクラフトはリリース直後、βが取れたぐらいに遊んで面倒でやめてしまったのですが、近頃はFeed The Beastと呼ばれるオールインワンのランチャーアプリが登場しており、こいつがMODの導入や管理も全て一発でやってくれるため面倒やストレスが一切なく楽しめます。

で、しばらく遊んでいたのですが、友人が他のゲームにハマってしまったためマインクラフトのサーバーを立てなくなってしまいました。そこで私のほうで自分でマインクラフトのサーバーを立ててやろうと思い立ちまして早速今週末チャレンジしてみました。

ちなみに私インフラに関しては大の苦手で、サーバーをゼロから構築するのは初めてだったりします。大昔新卒の頃にSIerの仕事でちょっと触ったりとか、その後の仕事でちょっと触ったりとかした程度。アプリケーションなら分かるんですけど(´・_・`)

前置きはこのぐらいにして早速やってみましょう。

ホスティングサービスを選定

パッと思いつくところだとさくらVPSとAWSが有名ですが、今回は友人のおすすめでConoHaというVPSホスティングサービスを選んでみました。ほとんどさくらVPSと大差ないように見えたのですが、ConoHaのほうがさくらVPSと違い初期費用がかからないらしいです。新しいもの好きですしせっかくなのでConoHaを選択。

サーバーのスペック等は以下のようにしました。CentOS 7.2に3コア2GBメモリで月1750円。マインクラフトのサーバであればこのぐらいの性能で大丈夫なはず。


とりあえずサーバーを起動してsshまで

構築したらすぐにインスタンスが起動します。ConoHaでは自動的に固定Public IPが1インスタンスに割り当てられるようで、この点固定IPを用意したりするのが何かと面倒なAWSに比べて楽でした。管理画面もすっきりまとまっていて使いやすく好印象です。キーペアもその場で同時にすぐ作成してくれて手間がかかりません。

生成したキーペアをローカルの ./ssh 以下に保存して ./ssh/config を適当に設定してログイン。root@を付け忘れてssh接続要求に失敗するみたいなよくあるお約束のミスをしましたが、特に問題なくログインできました。

ログインに成功したのを確認してから手持ちのドメイン (akisute.com) のサブドメインを用意してDNSを設定してやります。疎通して問題なし。

nginxを立てる

サーバーの情報などをウェブに公開したいので、nginxを利用してstatic htmlファイルをホスティングできるようにします。nginxのCentOS 7.2に対するインストール自体はこちらのページに従えばすぐにできます。
http://nginx.org/en/linux_packages.html
後は以下のビギナーズガイドに従って設定を行えば良いのですが、基本的には最初から全て必要そうな設定は用意されているので、/usr/share/nginx/html以下のindex.htmlを適当に編集してやるだけでオッケーです。
http://nginx.org/en/docs/beginners_guide.html

firewalldを設定する

しかしながらnginxを立ち上げただけでは外からアクセスしてもつながりません。これはCentOS(に限らないですが、ほとんどのLinuxのディストリビューションについて)は何らかのファイアウォールがデフォルトで入っているためです。一般的にはiptablesが使用されているのですが、CentOS 7ではiptablesではなくfirewalldというやつが使われているらしいので、そちらを設定することにしました。
http://urashita.com/archives/963
http://www.unix-power.net/centos7/firewalld.html

httpサービスをpublicゾーンの設定に追加するだけで簡単に外からアクセスできるようになります。iptablesより簡単でいいですね。

Javaをインストールする

マインクラフトはJavaで動作するので最新版のJavaが必要です。Javaには大きくOpenJDKとOracle Javaがありますが、個人的にはOracle Javaのほうが速そうなのでそちらを使うことにしました。以下のページで紹介されているとおりにすれば簡単にインストールできるかと思います。
https://www.digitalocean.com/community/tutorials/how-to-install-java-on-centos-and-fedora

minecraft-serverを立てる

いよいよ本題のminecraft-serverです。これについてはFTBのmodpackであればFTBのクライアントからDownload Serverボタンを押すだけでzip形式で最初から用意されたサーバーセットアップ一式がダウンロードできますので、ダウンロードURLをコピーしておいてサーバー側からwgetして取得しunzipするだけで準備完了です。
http://www.feed-the-beast.com/servers

unzipして展開した中身のServerStart.batをServerStart.shにして中身を適当に書き換えてやり(主にJVMの起動オプションからヒープに割り当てる量を調整するなど)実行すれば簡単にサーバが立ち上がります。ただし普通に立ち上げるとシェルを専有されてしまうので、screenなど経由でServerStart.shを実行するようにすればいいでしょう。
http://www.minecraftforum.net/forums/support/server-support/server-administration/1897274-how-to-use-screen-on-your-linux-minecraft-server

あとはサーバーコマンドで自分をopにでも追加しておけば良いと思います。起動したサーバーのコマンドラインから /op 自分の名前 とコマンドを実行すればopを付与できます。その他必要に応じてminecraft-serverの設定ファイルを操作したりgameruleを調整すれば良いと思います。

最後に先ほど設定したfirewalldにminecraftサービスを追加して、外からアクセスできるようにすればおしまいです。minecraftはhttpと違ってデフォルトでサービスが用意されていないので、自分でサービスを追加する必要があります。サービスの追加は/etc/firewalld/servicesにxml設定ファイルを追加すればよいです。サンプル代わりにデフォルトのサービスのxml設定ファイルが/usr/lib/firewalld/services以下に配置されているので適当なものをコピーして改造して使えばよいです。minecraftはtcp/udpの25565がデフォルトポートなのでここを開くように設定してfirewalldを再起動すれば外から繋がるようになるかと思います。

その他の設定

ログローテーションを設定しておかないとディスクがログで溢れて死ぬかと思ったんですが、最初からnginxについてはログローテーションが設定されているため気にしなくてOKです。minecraft-serverについてはどうなるかわかりませんので、必要に応じてlogrotateを設定して対応したほうが良いと思います。
https://genchan.net/server/4907

まとめ

いろいろ調べながらで時間がかかりましたが、3時間ほどでゼロ知識の状態からでもマインクラフトのマルチプレイサーバーを立てることができました。現在のところ無事快適に遊べています\(^o^)/

こういう自分の趣味嗜好のために技術を勉強すると非常に捗るのでオススメですね。みなさんもゲームを楽しみながら技術を勉強して一石二鳥になりませんか\(^o^)/


2016年1月7日木曜日

Asset Catalog を使用しているときは [UIImage imageNamed:] が遅くなることがある

タイトルのとおりですが、本日発見してひどくパフォーマンスに影響が出たため注意喚起を兼ねて共有いたします。

最近のiOSプロジェクトは全て画像をxcassetsすなわちAsset Catalog経由で管理することが多いかと思いますが、このAsset Catalogを使用している際に[UIImage imageNamed:]経由で画像を取得するとiOS 8以前とは異なり大変画像の取得が遅くなることがあるようです。

詳細はこちら。
https://forums.developer.apple.com/thread/17888

実際に私の環境では目に見えて遅くなりました。Instruments経由で計測したところ最大で10倍ほど差が出ているように見えました。iOS 9.1以降は修正されているとのことですが、それでも[UIImage imageNamed:inBundle:compatibleWithTraitCollection:]を使用したほうがパフォーマンスへの悪影響が少ないとの情報があります。実際Instruments上でもTrait Collectionを検索するのに無駄なパワーを使っているように見えたのでパフォーマンスがタイトな箇所では自分で非同期ロードするなりキャッシュするなりして補ってあげたほうが良い気がします。

こちらからは以上です。