shobylogy

叩けシンプルの杖

Swift + iOS 8のUITableViewControllerのバグを回避するためのSwiftLintカスタムルール

iOS 8のUITableViewControllerの実装にはバグがあり、Swiftでサブクラスを作ると、iOSのクラッシュが発生する場合があります。

今回はそれをSwiftLintで検知して回避するためのカスタムルールについて説明します。

なお、以下の説明はXcode 7.3.1 + Swift 2.2環境を想定しています。

Swift + iOS 8のUITableViewControllerバグとは

SwiftでUITableViewControllerのサブクラスを実装している場合、iOS 8での実行時にクラッシュが発生する場合があります。

具体的には、以下の条件を満たす際に発生します。

  • SwiftでUITableViewControllerのサブクラスを実装
  • letでpropertyを宣言
  • 独自のinitializerを実装し、内部でsuper.init(style:)を呼んでいる
  • init(nibName:bundle:) が未定義

具体的な以下のようなコードです。

class ViewController: UITableViewController {
    let id: Int
    
    init(id: Int) {
        self.id = id
        super.init(style: .Plain)
    }
}

上記の条件が満たされる場合、実行時に以下のようなエラーが出てクラッシュします。

fatal error: use of unimplemented initializer 'init(nibName:bundle:)' for class 'AppName.SubClassTableViewController'

これはiOS 8のバグが原因で、ビルド時にはエラーが出ず、実行時にしか検知することができません。

どうやら、UITableViewControllerの init(style:) 内で super.init(nibName:bundle:) ではなく、 init(nibName:bundle:) が呼ばれてしまっているのが原因なようです。

詳しい原因についてはこちらの記事が詳しいです。ご参照ください。 Swift 1.2 + UITableViewControllerで発生する問題と回避方法

このバグが厄介な点は、以下の3点にあります。

  • immutableなSwiftらしいコードを書いて初めて発生する
  • 実行時にしか問題に気づけない
  • 主流でない過去のバージョンのiOSでしか発生しない

そのため、チームのSwift開発力が向上したタイミングで問題が起き始め、過去のiOSのバグの対応に追われる羽目になります。

このバグへの対応策ですが、根本的にはiOS 8をサポート対象外にする以外はありませんが、以下のように回避策を取ることはできます。*1

  • propertyのletをやめてvarにする
  • nonoptionalをoptionalにする
  • init後に代入する
class ViewController: UITableViewController {
    var id: Int?
    
    init(id: Int) {
        super.init(style: .Plain)

        self.id = id
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {        
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
}

発生条件を満たすすべてのUITableViewControllerに上記のような対応をする必要があり、対応が漏れていても実行時まで一切気づけないのがつらいところです。

そのため、SwiftLintのカスタムルールを使い、発生条件に相当するUITableViewControllerに対してエラーを出すことで、実行時にしか気づけない問題をビルド時に気づけるようにしました。

SwiftLintのカスタムルール

SwiftLintのカスタムルールを使うと、気合いや根性で解決していた問題をルールに落とし込むことができます。*2

今回は、以下の条件をすべて満たす場合にerrorを出すルールを作りました。

  • ファイル内にUITableViewControllerのサブクラスが実装されている
  • ファイル内にinit もしくは init?(という文字列が存在する
  • ファイル内に override init(nibName という文字列が存在しない
    ios8_table_view_controller_bug:
        name: "iOS 8 TableViewController Bug"
        regex: "class.+UITableViewController(?=[\s\S]*init(\?)?\()(?![\s\S]*override.+init\(nibName)"
        message: "Please implement init(nibName:bundle)."
        severity: error

以下のようなエラーが出て、対応漏れに気づけます。

f:id:shoby:20161002004703p:plain

ViewController.swift:11:1: error: iOS 8 TableViewController Bug Violation: Please implement init(nibName:bundle). (ios8_table_view_controller_bug)

まとめ

iOS 8のUITableViewControllerにはバグがあり、Swiftでサブクラスを実装している場合、実行時にクラッシュする場合があります。

この問題は実行にしか気づけないですが、SwiftLintのカスタムルールを使うことによりビルド時にエラー表示することができます。

このように、SwiftLintを使うと、気合いや根性でなんとかするしかなかった問題をルール化できるので、皆さんもご活用ください。

*1:公式のRelease Noteによれば、Objective-CのBridging Headerに特殊なコードを記入すれば、回避できるようですが、私の環境(Xcode 7.3.1 + Swift 2.2)ではうまく動きませんでした

*2:スクリーン計測用コードの実装漏れを防ぐこともできます

iOS DCの前夜祭で大規模リニューアルのノウハウについて発表してきました

だいぶ時間が経ってしまいましたが、 iOS DC 2016の前夜祭で、大規模リニューアルのノウハウについて発表してきました。

こちらが発表資料です。

この発表で伝えたかったのは「ノウハウがあれば、リニューアルは怖くない」ということです。

リニューアルがユーザーに酷評されてしまうのは、その変更自体が悪いのではなく、受け入れてもらうためのノウハウが足りなかっただけかもしれません。 発表内には、私の体験を元に「これを数年前の自分が知ってたら...」という知見を詰め込んでいます。

皆さんが力を注いで開発したリニューアルが失敗扱いされず、ユーザーに受け入れてもらえるのを願っています。

最後になりますが、iOS DCの運営をしていただいたスタッフの皆さんありがとうございました。

エンジニア向け日頃の疲れを取るおすすめの入浴剤

エンジニア、肩や腰が凝りますよね。

温泉に行きたい…でも忙しくてなかなか行けない。 そんな方は入浴剤を使って毎日の疲れを取るのがおすすめです。

今回は、私が日頃から使っている入浴剤を紹介します。

温泉に行きたい方はこちらをどうぞ

blog.shoby.jp

おすすめ入浴剤

  • きき湯 マグネシウム炭酸湯
  • きき湯 FINE FEAT RESET
  • クナイプ グーテナハト バスソルト

きき湯 マグネシウム炭酸湯

私が一番愛用している入浴剤です。

主成分として、硫酸塩泉に含まれる硫酸マグネシウムが配合されており、炭酸ガスと合わせて血行が改善されます。

硫酸マグネシウム - Wikipedia

製造元のバスクリンは、温泉成分を科学的に分析して入浴剤に生かしており、きき湯シリーズはその最新商品です。

きき湯シリーズには温泉成分が含まれ、擬似的に温泉と同じ効能が得られます。

www.bathclin.co.jp

私は基本的に毎日この入浴剤を使っています。約600円で12回ほど使えるため、常用しても問題ない価格です。

入浴後は、1時間以上は体がポカポカして、使い始める前と比べると、肩こりや腰痛が大幅に改善されました。

きき湯 FINE FEAT RESET

きき湯ファインヒート レモングラスの香り 400g 入浴剤 (医薬部外品)

きき湯ファインヒート レモングラスの香り 400g 入浴剤 (医薬部外品)

きき湯シリーズのハイグレードモデルです。 おそらくアスリートなどの体を酷使する人のために開発されているように思えます。

主要な温泉成分はきき湯シリーズとほぼ同じですが、生姜の粉末が加えられています。

生姜には体を温める作用があり、漢方薬としても使われています。

takeda-kenko.jp

私はひどく疲れた場合や、日頃の疲れが溜まった時にこちらを使っています。

体のコリがかなり改善されるように思います。

クナイプ グーテナハト バスソルト

クナイプBソルトGN850

クナイプBソルトGN850

きき湯シリーズは入浴自体で肩こりや腰痛を改善するタイプですが、こちらは入浴後に安眠して体力を回復できるタイプです。

入浴によるリラックスを目的としており、特に温泉成分としての効能はありません。

クナイプシリーズは効能ではなく香りで選ぶと良いと思います。

入浴剤を使った時の入浴の仕方

温泉でもそうですが、入浴剤を使った場合、お風呂に浸かった後にシャワーで洗い流さずに出るようにしてください。

体の表面に残った温泉成分が入浴後も作用し、体を温め続けてくれます。

まとめ

なかなか温泉に行けない時は入浴剤がおすすめです。

きき湯シリーズは温泉成分が含まれ、擬似的に温泉と同じ効能が得られます。

きき湯 マグネシウム炭酸湯は日常の疲労回復に効き、 きき湯 FINE FEAT RESETは日頃の溜まった疲れに効きます。

また、クナイプはリラックスによる安眠が期待できます。

SwiftとObjective-C混在のプロジェクトで、BridingHeaderのビルドが低頻度で失敗する問題を修正する

SwiftとObjective-C混在のプロジェクトで、CI上でBridingHeaderのビルドが低頻度で失敗する現象に悩まされていました。(失敗した場合もRebuildすると成功します)

常に失敗するのではなく低頻度で失敗するケースの場合、Headerのimport周りが怪しいことが多いようです。*1

特にBridingHeaderでimportしているHeaderファイル上で、別のHeaderファイルを読み込んでいると、多重importになってしまい、ビルドが失敗します。

これを解決するには、Objective-C側を修正し、.hファイルでは他のHeaderをimportせず、.mファイルでimportするように書き換えると問題なく動作します。

エラーの例

このようなエラーが出ます。

/Users/UserName/RepositoryName/AppName/Classes/MYViewController.h:10:9: 'LibraryName.h' file not found

error: failed to import bridging header /Users/UserName/RepositoryName/AppName/Supporting Files/Fril-Bridging-Header.h

上記のエラーの場合、CocoaPods経由で読み込んでいるライブラリのヘッダーが多重importになっていました。

解決例

以下のような修正を加えることで問題を解決しました。 プロトコルを継承している場合でもInterface extensionを使うことで.hファイルに書かずに.mファイルにプロトコルの宣言を移すことができます

Before

MYViewController.h

#import <UIKit/UIKit.h>
#import "LibraryName.h"

@interface MYViewController : UIViewController <LibraryNameProtocol>
@end

After

MYViewController.h

#import <UIKit/UIKit.h>

@interface MYViewController : UIViewController
@end

MYViewController.m

#import "MYViewController.h"
#import <LibraryName/LibraryName.h>

@interface MYViewController () <LibraryNameProtocol>
@end

*1:仮説ですが、ビルドされるファイルの順序やキャッシュの有無により結果が不安定になるのかもしれません

心理的安全性を向上させる「ドラッカー風エクササイズ」を自分のチームでやってみた

最近、良いチームには「心理的安全性」が必要だということが話題になっていて、個人的に気になっていたところに、ninjinkunさんが「ドラッカー風エクササイズ」を紹介してくれたため、自分の所属するチームで試してみた。

ドラッカー風エクササイズのすすめ - けんちゃんくんさんの Web日記

(「心理的安全性」とは、思いやりがあり、どんなことでも率直に言い合えて、何を言っても自分の立場が悪くならないような状態を示しているらしい。)

個人的には、この取り組みで以下のことが改善できたら良いなと思っていた。

  • チームメンバーの入れ替えによるチームの変化を受け入れる
  • 今やっている仕事と、客観的に見て自分に向いている仕事のミスマッチを防ぐ

目的を伝えた

以下のようにエクササイズの目的を伝えた。

  1. 自分の得意なこと、自分がどうやって会社に貢献したいかを皆に知ってもらう
  2. 皆が自分にどういうことを期待しているかを知る

あくまでもこのエクササイズの目的は「知ること」なので、それ以上は何もしないということを伝えた。

進め方

  1. 自分の得意なこと、自分がどうやって会社に貢献できるかを書く
  2. 他のチームメンバーそれぞれについて、自分が期待していることを書く
  3. 共有
  4. 話し合う

「得意なこと」については、「自分が他の人より上手くできること、得意だと思っていることを書いてください」

「貢献できること」については「自分が会社やチームに貢献できることは何か書いてください」

「期待していること」については「チームでどう活躍してくれるか、期待していることを書いてください」というように伝えた。

また、「期待していること」は無記名にした。(恥ずかしがらずに書いて欲しかった)

感想

チームメンバーの変更があったタイミングや、達成すべき目標が変わったタイミングで是非やるべき。 チームメンバーについて深く理解でき、自分の仕事に対する納得感が出る。

チームメンバーの入れ替えによるチームの変化を受け入れる

顔合わせとしては、一緒にランチを食べるよりも良い方法だったと思う。 一緒に仕事をしたことがない間柄でも、お互いの得意分野や人柄が深く理解できる。

今やっている仕事と、客観的に見て自分に向いている仕事のミスマッチを防ぐ

全体を通して、自分が貢献できると思っていることと、他の人が自分に期待していることに対して、大きなミスマッチはなかったが、それが知れたのは大きいと思う。 客観的に見た向いている仕事(期待されている仕事)と、自分がやっている仕事が一致していることが把握できるのは、納得感が出る。

「意外と皆、自分を見てくれていることに気づいた」という感想が出たが、新チームメンバーを「得体の知れない他人」ではなく「自分をよく見てくれているチームメイト」という認識にできたのは大きいと思った。

iOSアプリの画像を最適化してアプリの容量を減らす

最近アプリの容量が増えてきたので、容量を減らすべく画像の最適化を行いました。 調べて出てくる情報がどれも古かったため、今だとどうしたらいいかを書いておきます。

以下の情報はXcode 7.2.1環境を想定しています。

概要

  • Asset Catalog を使う
  • ASSETCATALOG_COMPILER_OPTIMIZATIONspaceにする
  • ImageOptimで画像を最適化する
  • PDFではなくPNGを使う
  • PNGではなくJPEGを使う

Asset Catalogを使う

Asset Catalogに入っていない画像をAsset Catalogに入れると容量が減ります。 Asset Catalogに入れるとSlicingが有効になり、デバイスごとに必要な画像のみが配布されるバイナリに含まれるようになるためです。

developer.apple.com

ASSETCATALOG_COMPILER_OPTIMIZATIONspaceにする

Build SettingsのAsset Catalog Compiler > Optimization にあります。

このsettingをspaceにすると、コンパイル後のasset catalogのサイズを最適化してくれるようです。 (timeだと画像の読み込み時間を高速化してくれるようですが、体感ではあまり違いを感じませんでした)

ImageOptimで画像を最適化する

ImageOptimというアプリケーションを使うと画像の最適化ができます。 最適化の設定は最高レベルの「クレイジー」にしておくと良いと思います。

f:id:shoby:20160316164213p:plain

このアプリケーションは、内部ではPNGOUTPngcrushなどのツールを組み合わせて、最も容量が減った方式を採用してくれるようです。*1

注意点として、ImageOptimにより画像を最適化した場合、ASSETCATALOG_COMPILER_OPTIMIZATIONspaceではなくtimeにした方が容量が減る場合があります。

Asset Catalogに含めた画像は、Xcodeでのビルド時に必ず最適化がかけられてしまうのですが*2、ImageOptimで最高レベルに最適化をした画像の場合、space を指定した場合の圧縮だとtimeを指定した場合よりファイル容量が増大することもあるようです。

この辺りはファイルに寄ると思いますので実際にお試しください。

前は COMPRESS_PNG_FILES があり、再圧縮を防止できたのですが、このオプションはデフォルトでは表示されなくなった上、Configurationファイルなどで指定してもAsset Catalogには適用されません。

PDFではなくPNGを使う

PDFではなくPNGを使う方が容量が減る場合があります。*3

体感として、アイコンのようなVector画像向きのファイルはPDFの方が容量が減りますが、スクリーンキャプチャや実写画像などのファイルはPDFではなくPNGの方が容量が減るように思えます。

PDFはビルド時、内部ではPNGとして書き出されているため、書き出しと最適化をXcodeに任せるのではなく、最初から最適化済みのPNGを入れておいた方が容量が減る場合があります。

PNGではなくJPEGを使う

Asset CatalogではPNGとPDFだけでなく、JPEGも扱うことができます スクリーンキャプチャや実写画像などのファイルは、PNGではなくJPEGの方が容量が減る場合があります。

developer.apple.com

まとめ

Asset Catalogに入っていない画像をAsset Catalogに入れるとSlicingが効いて容量が減ります。 また、ASSETCATALOG_COMPILER_OPTIMIZATIONspaceにするとAsset Catalogのコンパイル時に容量が減ることがあります。 ImageOptimで画像を最適化したり、画像フォーマットを変更して、画像ファイル自体を最適化すると容量が減る場合があります。

*1:いつの間にかZopfliにも対応してました

*2:実際にビルド後のcarファイルを解析してみたのですがtimeでもspaceでも再度最適化がかけられてファイルの容量が変化していました

*3:PDFとはなんだったのか…

I'll talk about color detection from fashion images in Fasion Tech Meetup #2

I'll participate in Fashion Tech Meetup #2, and talk about color detection from fashion images. The meetup will be held on March 22.

fashion-tech.connpass.com

Sad to say, the event will not be translated into English. However I'll translate my presentation after the event.

My talks contains following contents.

  • Removing background from fashion images
  • Color correction of images taken with a fluorescent lamp
  • Extracting "main color" and "sub color" from a image
  • Color matching between RGB values and color groups

Please check it! Thanks.