Enums as configurationは別にアンチパターンでは無いのでは
ちょっと前の記事だけど。
Enums as configuration: the anti-pattern via @jesse_squires
この記事が言いたいのは
- enumでインスタンスの設定をするのは、提供された選択肢から選ぶしか無いので利用者側の自由度が低く良くない。
- ライブラリやフレームワークがそうなっていると、選択肢を増やしたかったらPR投げてマージしてもらわないといけない。
- Swiftのswitch文はenumを網羅しないとコンパイルエラーになるから、あちこちにエラーがでて悲しいからよくない。
ということらしい。
で、以下が僕の主張。
利用者側はおかしなことをするという前提で提供側が想定したパターンしか設定できないenumのほうが安全なような気がするなあ / “Enums as configuration: the anti-pattern · Jesse S…” https://t.co/phDORFzS0N
— ひらり (@hiragram) 2016年8月25日
Swiftのswitchはenumの中身を網羅してないとコンパイル通りませんよってやつは安全装置だと思ってるし、enumにcase足すとswitchが全部コンパイルエラーになるからenumじゃなくてstructを使えみたいなのはちょっと賛同できないなあ
— ひらり (@hiragram) 2016年8月25日
ビューのスタイルの設定をするパターンと、ネットワークの振る舞いを設定するパターンと説明してるけど、前者はstruct(=利用者側にカスタマイズ可能性を残す)でいいと思うけど、振る舞い決める方は提供側が想定してない使い方をされかねないのを考えるとうーんという感じ。
— ひらり (@hiragram) 2016年8月25日
何事も、使う側は常にバカだから提供側はおかしなことをそもそもやれないように作っておくべきなんじゃないのって思う
— ひらり (@hiragram) 2016年8月25日
こっちが提供する選択肢以外に利用側に自由に設定させても良いかなって気分になるのは色ぐらいかなあ。フォントサイズとか太さとかはレイアウト崩れかねないし
— ひらり (@hiragram) 2016年8月25日
僕はパターンを網羅していないとコンパイル通してくれないSwiftのswitchは好きだ。同じ理由でenum使ってswitchするときにdefaultを使うのは嫌いだ。
Swift3の新しいアクセス修飾子について考察
3行で
- Swift3から新たなアクセス修飾子
open
,fileprivate
が追加された private
の扱いが変わった- ゆるい順に、
open
,public
,internal
,fileprivate
,private
open
「モジュール外からアクセスでき、 サブクラス化(継承)可能である」ことを示すアクセス修飾子。以前まではpublic
がこれを示していたが、public
は「モジュール外からアクセスできるが、 サブクラス化(継承)は出来ない」に変更された。
open
はSwift2以前のpublic
にあたり、Swift3のpublic
はSwift2以前のpublic
にfinal
修飾を付け足したもの?
fileprivate
「同じファイル内からのみアクセス可能である」ことを示すアクセス修飾子。Swift2以前のprivate
と同じ。Swift3のprivate
は、「定義されたスコープ内からのみアクセス可能」に変更された。
利用
open
とpublic
、及びfileprivate
とprivate
の使い方、使い分けについて考察する。
まず、Xcode8でできるSwift3のシンタックスへのコンバートをかけると、
public
はそのままprivate
はfileprivate
になった。
近年Appleはプロトコル指向プログラミング(Protocol Oriented Programming, POP)を推進しており
- 参照型のクラスよりも値型の構造体を使いましょう(構造体は継承が無い)
- 継承を使わずにプロトコルとエクステンションで実現しましょう
と考えているようで、コンバート時にpublic
がopen
に置換されないのは「なるべく継承させないように作ろうね」というメッセージなのではと感じた。
private
とfileprivate
の使い分けについてはまだ考察中で、基本的にはprivate
に寄せて参照できるスコープを狭くするべきだと思うが、一方でSwiftには
struct Human { var name: String } extension Human: HogeProtocol { func hogeMethod() { print("hoge") } } extension Human: FugaProtocol { func fugaMethod() { print(name) } }
可読性の向上のためにプロトコルの実装をエクステンションとして切り出して書く派も存在し、もしプロパティname
がprivate
だった場合には2つのエクステンションからは参照できなくなる。
安全のためにスコープを狭くするべきか、可読性のために多少スコープの広がりを許容するかの問題だが、これはどちらを取るべきなんだろうか。
まとめ
基本的に自作の型でopen
をたくさん使うとしたらライブラリ作者くらいであろう。Appleが継承地獄から脱却しようとしているのは明らかなので、それに沿う形で順応していこうと思う。
fileprivate
については、世間がどう受け止めるかという感じがするので、もうすこし様子を見たい。
参考
複数のプロトコルに適合する型、という書き方
最近知りました。
//: Playground - noun: a place where people can play protocol Animal { func run() } protocol Vehicle { func drive() } struct Nekobasu: Animal, Vehicle { func run() { } func drive() { } } let bus: protocol<Animal, Vehicle> bus = Nekobasu() bus.drive() // ok bus.run() // ok
xibからビューのインスタンスを生成するコードの治安を良くする
AwesomeView.xib
からAwesomeView
のインスタンスを作りたい時、普通にUINibのイニシャライザを使って呼び出すと
let awesome = UINib(nibName: "AwesomeView", bundle: nil).instantiateWithOwner(nil, options: nil).first as! AwesomeView
こんなに治安が悪い。
- nibNameを文字リテラルで指定している
- force cast (as!)
この辺が治安を悪くしている。これをプロジェクト内のあちこちに撒き散らすのは本当にかなしくてみじめなことになるので、こんな方法を考えた。
まず「xibからインスタンス生成できる」という性質を示すプロトコルを作る。
protocol NibInstantiatable { // 上で言う、"AwesomeView"が入ってるプロパティ static var nibName: String { get } // そとから呼び出すメソッド static func instantiateFromNibWithOwner(ownerOrNil: AnyObject?, options: [NSObject: AnyObject]?) -> Self }
次に、UIView用にデフォルト実装を持たせる。
extension NibInstantiatable where Self: UIView { static var nibName: String { get { // 上で言う"AwesomeView"みたいな、「自分のクラス名の文字列」を返す return String(Self.self) } } static func instantiateFromNibWithOwner(ownerOrNil: AnyObject?, options: [NSObject: AnyObject]?) -> Self { // 治安の悪いコードはここだけに閉じ込められる。 return UINib(nibName: nibName, bundle: nil).instantiateWithOwner(ownerOrNil, options: options).first as! Self } }
AwesomeView.swift
の変更は
- class AwesomeView: UIView { + final class AwesomeView: UIView, NibInstantiatable { }
と、NibInstantiatable
の指定をするだけ。protocolのメソッドでSelfを使っている都合からfinal
の指定が必要になるが、ビュークラスの継承はしない、というルールが自分の中にあるので問題なし。
UIViewのサブクラスなのでこれでprotocol extensionに記載されたコードが実行される。 呼び出し側はこんな感じ。
let awesome = AwesomeView.instantiateFromNibWithOwner(nil, options: nil)
AwesomeViewのインスタンスを作っていることが一発でわかるし、
let awesome = UINib(nibName: "AwesomeView", bundle: nil).instantiateWithOwner(nil, options: nil).first as! AwesomeView
これと比べると随分治安がよくなったのではないだろうか。
このprotocol extensionの実装は、xibのファイル名が「クラス名.xib」の時だけ有効である。もし何らかの理由でクラス名とxib名が違うのであれば(リネームしろよ感はあるものの)、以下のように
final class AwesomeView: UIView, NibInstantiatable { static var nibName = "SuperAwesomeView" }
とnibName
だけ自分のクラスに書いてやれば、protocol extensionではなくこっちが優先されるのでこれも対応が楽。
iOSDCに参加した #iosdc
会場が練馬って朝知ったので遅刻した。
到着即ランチセッション #iosdc
— ひらり (@hiragram) 2016年8月20日
ランチセッションの弁当がうまそう pic.twitter.com/UhgOb9hRRp
— ひらり (@hiragram) 2016年8月20日
ベストトーク賞とられたReactive Programmingの話。
FRP、もうちょっとソルジャーの人たちが頑張って世界を平和にしてから自分がやり始めるでもいいかなあと思ってるんだけどだめかな #iosdc
— ひらり (@hiragram) 2016年8月20日
メモリ管理の話。
weak referenceだけの状態になってもまだメモリは解放されていなくて、次に参照された時にnilを返して実際に解放される?ってこと? #iosdc
— ひらり (@hiragram) 2016年8月20日
デザイナーにStoryboard使ってもらう話。
Borderのビューは毎プロジェクト作るけどIBDesignableにしたことなかったな #iosdc
— ひらり (@hiragram) 2016年8月20日
デバッグの話。
DiagnosticsとSymbolic Breakpointはあんま使ったこと無いので勉強しよ #iosdc
— ひらり (@hiragram) 2016年8月20日
余談
個人スポンサーTシャツ、Lサイズで申し込みしてて受付でもLですね〜って言われたんだけど今朝着ようと思って見たらSだった どうにかならんかな #iosdc
— ひらり (@hiragram) 2016年8月22日
追記
@hiragram ごめんなさい!予備がありますので、DMで住所とお名前を頂けますか。Lサイズをお送りします。
— iOSDC (@iosdcjp) 2016年8月22日
対応ありがとうございます。
さらに追記
@hiragram やせろ
— いかもん (@ikamooon) 2016年8月22日
うるせえ!!!!!!!!!!!!!!!!!!!!!!!
NSTextFieldをAutoLayoutで配置すると幅が0になっちゃう
最近趣味プログラミングでAppKitを触っている。
AppKitについて言いたいことはいろいろあるがUILabel代わりに使ったNSTextFieldについてハマったので書いておく。
Slackのメッセージみたいな見た目のビューを作りたくてAutoLayoutでごちゃごちゃ配置してたらNSTextFieldの幅がゼロになってしまって困った。
目指していた形はこんなの。
で、当初ふつうに配置して制約かけたら
こんなんなった。Xcodeのビューの階層見れるアレ(名前知らん)で見たらこんなん。
いる。
幅0のNSTextField2つと、なんか中途半端に生き残ったNSTextFieldがいる。
最初に疑ったのはAutoLayoutで、自分で書けた制約のプライオリティとか、Content Compression Resistance Priorityなんかを気にしていろいろいじってみたが全然改善されず、1時間くらい浪費した。
Content Compression Resistance Priorityについて調べなおしていたら、intrinsicContentSize
にたどり着いた。
Custom views typically have content that they display of which the layout system is unaware. Setting this property allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.
つまりContent Compression Resistance Priorityで登場するビューの固有サイズというのはこいつのことらしい。かけられた制約が固有サイズより大きかった時の固有サイズの優先度がContents Hugging Priority、逆に制約が固有サイズより小さかった時の固有サイズの優先度がContents Compression Resistance Priorityということだ。
で、stringValue
をセットしたあとのNSTextFieldのintrinsicContentSize
を見ると
AppKitの一番気に入らない部分の真似します
— ひらり (@hiragram) 2016年8月18日
(lldb) po nameLabel.intrinsicContentSize
— ひらり (@hiragram) 2016年8月18日
▿ CGSize
- width : -1.0
- height : 21.0
width: -1.0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
— ひらり (@hiragram) 2016年8月18日
自分でかけた制約に問題はなく、Content Compression Resistance Priorityにも問題はなく(そもそも触ってない)、AutoLayoutやめてsizeToFit()
でサイズ決めて配置したとしてもintrinsicContentSize
のwidthは-1.0なので見えないままでしたよ、という結構きついハマりだった。
対応めんどくさかったら投げ出して帰るつもりだったが、StackOverflowにまさにその回答があった。
-[NSTextField intrinsicContentSize] always has undefined width - StackOverflow
この一番voteを稼いでる回答(というか質問者が自己解決したっぽい)によると、
OK, finally figured it out…
[_textfield setEditable:NO]
That's it. I guess that with an editable textfield one must have an explicit constraint for the textfield width. Which kind of makes sense, imagine editing a textfield and it would constantly grow horizontally with every keystroke... not an ideal UI.
僕はSwiftなので
nameLabel.editable = false
これでいけた。他のNSTextFieldも同じ。
Content Compression Resistance Priorityが具体的にどう動くのかを把握してなかったので時間くってしまった。
AutoLayoutに関して僕が良いなと思ったおすすめの本はこちら。
よくわかるAuto Layout iOSレスポンシブデザインをマスター
- 作者: 川邉雄介,所友太
- 出版社/メーカー: リックテレコム
- 発売日: 2016/06/17
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
いやーはまったはまった。
awakeFromNibのドキュメント読んだ
awakeFromNib
ちゃんと理解してる自信がなかったので読んでみた。
Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
Interface Builder archiveもしくはnibファイルから読み込まれたあとに、サービスに必要なレシーバを用意します
The nib-loading infrastructure sends an
awakeFromNib
message to each object recreated from a nib archive, but only after all the object in the archive have been loaded and initialized.
nib読み込み基盤はnib archiveから再生成されるオブジェクトが読み込まれ、初期化されたあとにawakeFromNib
メッセージを送ります。
When an object receives an
awakeFromNib
message, it is guaranteed to have all its outlet and action connections already established.
オブジェクトがawakeFromNib
メッセージを受け取った時、そのオブジェクトのoutletとactionはすでに初期化されていることが保証されます。
You must call the
super
implementation ofawakeFromNib
to give parent classes the opportunity to perform any additiional initialization they require.
親クラスが必要とする初期化を実行するために、super classのawakeFromNib
を呼びださなければいけません。
Although the default implementation of this method does nothing, many UIKit classes provide non-empty implementations.
このメソッドのデフォルト実装では何も処理されないが、多くのUIKitのクラスはこのメソッドで何らかの処理が行われている。
You may call the
super
implementation at any point during your ownawakeFromNib
method.
super classのawakeFromNib
を呼び出すのは、自分のawakeFromNib
メソッド内のどこでも良いです。
NOTE During Interface Builder's test mode, this message is also sent to objects instantiated from loaded Interface Builder plug-ins.
Because plug-ins link against the framework containing the object definition code, Interface Builder is able to call their
awakeFromNib
method when present.The same is not true for custom objects that you create for your Xcode projects.
Interface Builder knows only about the defined outlets and actions of those objectsl it does not have access to the actual code for them.
このNOTEはまだ理解が追いついてないので省略
During the instantiation process, each object in the archive is unarchived and then initialized with the method befitting its type.
生成プロセスの間、archive内のそれぞれのオブジェクトはunarchiveされそれぞれの型にしたがって初期化されます。
Objects that conform to the
NSCoding
protocol (including all subclasses ofUIView
andUIViewController
) are initialized using theirinitWithCoder:
method.
NSCoding
プロトコルに適合するオブジェクト(UIView
およびUIViewController
のすべてのサブクラスを含む)はinitWithCoder:
メソッドによって初期化されます。
All objects that do not conform to the
NSCoding
protocol are initialized using theirinit
method.
NSCoding
プロトコルに適合しないオブジェクトはinit
メソッドによって初期化されます。
After all objects have been instantiated and initialized, the nib-loading code reestablishes the outlet and action connections for all of those objects.
すべてのオブジェクトが生成され初期化されたあと、nib読み込みコードは再びそれらのオブジェクトのoutletとactionを有効にします。
It then calls the
awakeFromNib
method of the objects.
そしてそのオブジェクトのawakeFromNib
をコールするのです。
For more detailed information about the steps followed during the nib-loading process, see
Nib Files
in Resource Programming Guide.
もっとnib読み込み処理の続きを知りたかったらResource Programming GuideのNib Files
の項を見てな。
IMPORTANT Because the order in which objects are instantiated from an archive is not guaranteed, your initialization methods should not send messages to other objects in the hierarchy.
オブジェクトがarchiveから生成される順番は保証されていないので、初期化メソッド(=イニシャライザ?)内で他のオブジェクトにメッセージを送るべきではありません。
Messages to other objects can be sent safely from within an
awakeFromNib
method.
awakeFromNib
メソッド内では他のオブジェクトに安全にメッセージを送ることが出来ます。 (= 既に初期化が済んでることが保証されているから、かな)
Typically, you implement
awakeFromNib
for objects that require additional set up that cannot be done at design time.
一般に、デザイン時に出来ない (= xib上で出来ない?) セットアップをする処理をawakeFromNib
に実装します。
For example, you might use this method to customize the default configuration of any controls to match user preferences or the values in other controls.
例えばこのメソッドを、あるコントロールのconfigurationをユーザー設定や他のコントロールの値によって決めたい時などに利用できます。
You might also use it to restore individual controls to some previous state of your application.
また、アプリケーションの過去の状態に応じてそれぞれのコントロールをリストアしたいときにも使うことが出来ます。