指定イニシャライザを確実に呼ばせる方法

# 概要
よく`UIKit`にあるクラスのサブクラスとしてカスタムビューなどを作る際に、既存のイニシャライザではなく新たな指定イニシャライザを追加することがあるかと思います。
しかし、これを利用してイニシャライズされることを前提としていると利用者が既存のイニシャライザを使ってしまうことでバグの原因になる可能性があります :sweat: 
こんな時は、指定イニシャライザ以外のイニシャライザを呼ばないようにすればいいんです！

※ Storyboardを経由して`initWithCoder`を呼んでしまう場合は回避できませんでした。
@dokubeko さんコメントでの指摘ありがとうございます :tada:

# アプローチ
`Swift`にも`Objective-C`にも`attribute`というコンパイラディレクティブがあります。
これに`unavailable`という属性があるのでこれを指定します。
例えば`UIViewController`のサブクラスである`ViewController`クラスの指定イニシャライザを必ず呼ばせたい場合

### for Swift
```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
```objc
//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
```

```objc
//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
補完時に使えないことを明示してくれるし、それを強行突破してもビルドエラーになる。

![Objective-Cで既存のinitファミリが呼べない図](https://qiita-image-store.s3.amazonaws.com/0/65758/b7dc1304-19c0-792a-ac3c-c3e8c00853e5.png)

![Objective-Cで既存のnewメソッドが呼べない図](https://qiita-image-store.s3.amazonaws.com/0/65758/41239aae-e701-ce02-6326-9933a9cca34d.png)

![強行突破してもビルドエラーにObjCのnew](https://qiita-image-store.s3.amazonaws.com/0/65758/1226e073-3ba9-78a6-9e75-4368e34c7307.png)

###for Swift
そもそも補完時に候補として出てこなくなるし、当然ビルドも失敗する。

![補完の候補として出てこないSwift](https://qiita-image-store.s3.amazonaws.com/0/65758/6c4beecd-3b61-a787-73e3-b9ffba8f1df4.png)

![強行突破してもビルドエラーに swiftのinit](https://qiita-image-store.s3.amazonaws.com/0/65758/8da3060e-fb3d-a2bc-ffc9-d6b070e35f71.png)

#まとめ
`Swift`では特にコードがちょっと汚くなるような気がするけど、**絶対に呼んでほしい指定イニシャライザだけ呼ばれる** というメリットは大きいと思います。
バグの防止にもなるので是非使ってみてはいかがでしょうか。

#One more thing
今後指定イニシャライザをさらに追加した場合に、そのイニシャライザに必ず経由させたい指定イニシャライザがある場合は、`NS_DESIGNATED_INITIALIZER` というマクロをつけるといいです。
この場合は警告しか出ずビルドは通ってしまいますが補助的効果は十分にあると思います。
※ ここで紹介できるのは`Objective-C`の場合のみで、`Swift`でのやり方がわかる方はコメントいただけると助かります。

```objc
//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を使いましょう")));
```

```objc
//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`メソッドで初期化すると警告は消えます。

![指定イニシャライザ経由でないとWarning](https://qiita-image-store.s3.amazonaws.com/0/65758/196ba889-1dd9-6187-1833-b47fec174928.png)

![警告消去](https://qiita-image-store.s3.amazonaws.com/0/65758/5b8ae84b-ade6-9da9-8abd-eebd60d7941b.png)