Objective-Cのクラスは基本的にNSObjectをルートクラスに持ち、そのinit()
が継承またはオーバーライドされるが、
SwiftのクラスはNSObjectを継承していなかったり他のdesignated initializerが存在することでinit()
が存在しないことや、default initializerのために明示的なinit()
が存在しない場合がある。
Swiftのdesignated/convenience/required/default initializerと継承 - sambaiz-net
そんなクラスのMetatypeをObjective-CのClass型に代入してinitするとどうなるか確認する。
SwiftのMetatypeとMetadata - sambaiz-net
#import <Foundation/Foundation.h>
@interface Hoge: NSObject
@property (weak, nonatomic) id <HogeDelegate> delegate;
@property Class klass;
- (void)fuga;
@end
#import "Hoge.h"
#import <UIKit/UIKit.h>
@implementation Hoge
- (void)fuga {
dispatch_async(dispatch_get_main_queue(), ^{
[[_klass alloc] init];
});
}
@end
func f() {
var hoge = Hoge()
hoge.klass = B.self
hoge.fuga()
}
init()
を実装したクラス: 実行時にUnrecognized selector -[***.B init]
で落ちる
init()
を実装しないでdefault initializerが存在する場合も同様。
class B{
init() {}
}
- NSObjectを継承したクラス: 落ちない
class B: NSObject{
}
- NSObjectを継承し、
init()
以外のdesignated initializerを実装したクラス: 実行時にUse of unimplemented initializer 'init()' for class '***.B'
で落ちる
class B: NSObject{
init(a: String) {
}
}
- Objective-Cのクラスを継承したクラス: 落ちない
class B: Hoge{
}
- Objective-Cのクラスを継承し、
init()
以外のdesignated initializerを実装したクラス:Use of unimplemented initializer...
で落ちる
class B: Hoge {
init(a: String) {
}
}
ここまでの結果からNSObjectを間接的にも継承してないかinit()
が呼べないと落ちることが分かる。
だが、いずれの条件も満たしていそうな次のクラスはなぜか落ちない。
class B: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
ブレークポイントを設定してコールスタックを見るとまず[UIView init]
が呼ばれ、そこからinit(frame:.zero)
が呼ばれている。
実際init(frame:)
を消すと Fatal error: Use of unimplemented initializer 'init(frame:)' for class '***.B'
で落ちる。
UIKitCore`-[UIView init]:
0x111add74b <+0>: pushq %rbp
0x111add74c <+1>: movq %rsp, %rbp
0x111add74f <+4>: movq 0x7e699a(%rip), %rsi ; "initWithFrame:"
0x111add756 <+11>: movq 0x3c598b(%rip), %rax ; (void *)0x0000000113a2bd00: CGRectZero
0x111add75d <+18>: movq 0x18(%rax), %rcx
0x111add761 <+22>: movq 0x10(%rax), %rdx
0x111add765 <+26>: movq (%rax), %r8
0x111add768 <+29>: movq 0x8(%rax), %rax
0x111add76c <+33>: pushq %rcx
0x111add76d <+34>: pushq %rdx
0x111add76e <+35>: pushq %rax
0x111add76f <+36>: pushq %r8
0x111add771 <+38>: callq *0x3c6d39(%rip) ; (void *)0x000000010d3b1640: objc_msgSend
-> 0x111add777 <+44>: addq $0x20, %rsp
0x111add77b <+48>: popq %rbp
0x111add77c <+49>: retq
そもそもUIViewにはinit()
がないはずだが、UIView.init()
は確かに呼べて、コールスタック上はエイリアスのように直接init(frame:.zero)
が呼ばれているように見える。
それも他のdesignated initializerを定義して継承してないのでB.init()
は呼べないはずだが呼べる。ただしinit(frame:)
を継承しないで呼ぼうとすると実行時ではなくビルド時にエラーになる。
試しにObjective-CのクラスHogeにも- (instancetype)init
を追加してみたがやはり継承されない。さっぱり分からない。分かる人がいたら教えて欲しい。