概要
よくUIKit
にあるクラスのサブクラスとしてカスタムビューなどを作る際に、既存のイニシャライザではなく新たな指定イニシャライザを追加することがあるかと思います。
しかし、これを利用してイニシャライズされることを前提としていると利用者が既存のイニシャライザを使ってしまうことでバグの原因になる可能性があります
こんな時は、指定イニシャライザ以外のイニシャライザを呼ばないようにすればいいんです!
※ Storyboardを経由してinitWithCoder
を呼んでしまう場合は回避できませんでした。
@dokubeko さんコメントでの指摘ありがとうございます
アプローチ
Swift
にもObjective-C
にもattribute
というコンパイラディレクティブがあります。
これにunavailable
という属性があるのでこれを指定します。
例えばUIViewController
のサブクラスであるViewController
クラスの指定イニシャライザを必ず呼ばせたい場合
for Swift
//ViewController.swift
import UIKit
class ViewController: UIViewController {
//properties
var identifier:NSString?
//呼ばせたい指定イニシャライザ
init(identifier:NSString) {
self.identifier = identifier
super.init()
}
//呼ばせたくない既存のイニシャライザ
@availability (*, unavailable, message="代わりにinit(identifier)を使いましょう")
override init() {super.init()}
@availability (*, unavailable, message="代わりにinit(identifier)を使いましょう")
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init()
}
@availability (*, unavailable, message="代わりにinit(identifier)を使いましょう")
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
for Objective-C
//ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
//必ず呼ばせたい指定イニシャライザ
- (instancetype)initWithIdentify:(NSString *)identify;
//呼ばせたくない既存のイニシャライザ
- (instancetype)init
__attribute__((unavailable("代わりにinitWithIdentifyを使いましょう")));
- (instancetype)initWithCoder:(NSCoder *)aDecoder
__attribute__((unavailable("代わりにinitWithIdentifyを使いましょう")));
- (instancetype)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
__attribute__((unavailable("代わりにinitWithIdentifyを使いましょう")));
+ (instancetype)new
__attribute__((unavailable("代わりにallocした後initWithIdentifyを使いましょう")));
@end
//ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (copy, nonatomic) NSString *identify;
@property (copy, nonatomic) NSString *name;
@end
@implementation ViewController
- (instancetype)initWithIdentify:(NSString *)identify
{
self = [super init];
if(!self){
return self;
}
self.identify = identify;
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
@end
結果
for Objective-C
補完時に使えないことを明示してくれるし、それを強行突破してもビルドエラーになる。
###for Swift
そもそも補完時に候補として出てこなくなるし、当然ビルドも失敗する。
#まとめ
Swift
では特にコードがちょっと汚くなるような気がするけど、絶対に呼んでほしい指定イニシャライザだけ呼ばれる というメリットは大きいと思います。
バグの防止にもなるので是非使ってみてはいかがでしょうか。
#One more thing
今後指定イニシャライザをさらに追加した場合に、そのイニシャライザに必ず経由させたい指定イニシャライザがある場合は、NS_DESIGNATED_INITIALIZER
というマクロをつけるといいです。
この場合は警告しか出ずビルドは通ってしまいますが補助的効果は十分にあると思います。
※ ここで紹介できるのはObjective-C
の場合のみで、Swift
でのやり方がわかる方はコメントいただけると助かります。
//ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
//必ず呼ばせたい指定イニシャライザ
//下記の指定イニシャライザは必ず経由させたい
- (instancetype)initWithIdentify:(NSString *)identify NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithName:(NSString *)name;
//呼ばせたくない既存のイニシャライザ
- (instancetype)init
__attribute__((unavailable("代わりにinitWithIdentifyを使いましょう")));
- (instancetype)initWithCoder:(NSCoder *)aDecoder
__attribute__((unavailable("代わりにinitWithIdentifyを使いましょう")));
- (instancetype)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
__attribute__((unavailable("代わりにinitWithIdentifyを使いましょう")));
+ (instancetype)new
__attribute__((unavailable("代わりにallocした後initWithIdentifyを使いましょう")));
//ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (copy, nonatomic) NSString *identify;
@property (copy, nonatomic) NSString *name;
@end
@implementation ViewController
- (instancetype)initWithIdentify:(NSString *)identify
{
self = [super init];
if(!self){
return self;
}
self.identify = identify;
return self;
}
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if(!self){
return self;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
@end
下図のように、[super init]
で初期化すると警告が表示されます。
ここを [self initWithIdentify:@"nameId"]
などとしてinitWithIdentify
メソッドで初期化すると警告は消えます。