ObjCの継承に関する疑問とselfの暴走とか

詳細Objective-C2.0第3版を読み進めてます。

詳解 Objective-C 2.0 第3版
詳解 Objective-C 2.0 第3版
posted with amazlet at 12.01.26
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 7869

その中の継承に関する項目にて。
A->B->Cという継承構造でsuperをつかうとCからAのメソッドを呼び出せるよ!みたいな事が書かれてました。

文献上の例だとAからCの3つですが、そもそもクラスは全てNSObjectから継承してるはずなので、Superいっぱつで親飛び越して先祖を参照する、っていっきにNSObjectを参照することになるの?どこまで上位を参照しにいくんだ?とか疑問が。

結論から言うと、継承を続けている際に、直近のOverrideされたメソッドを使用する、という挙動でした。
それ以外にも色々勘違い解消できました。

Screen Shot 0024-01-26 at 12.47.21-2

試してみた

実際に、以下の様にサンプルを作ってコンソールに経過を吐かせて動作を確認します。

A.h

#import 

@interface A : NSObject{
}

- (void)method1;
- (void)method2;
- (void)method3;

@end

A.m

#import "A.h"

@implementation A
@synthesize One = One_;
@synthesize Two = Two_;
@synthesize Three = Three_;

-(void)method1{
  NSLog(@"A::method1");
  [self method2];
  [self method3];
}
-(void)method2{
  NSLog(@"A::method2");
}
-(void)method3{
  NSLog(@"A::method3");
}
@end

B.h

#import "A.h"

@interface B : A
-(void)method1;
@end

B.m

#import "B.h"

@implementation B

//*
-(void)method1{
  NSLog(@"B::method1");
}
// */
@end

C.h

#import "B.h"

@interface C : B

-(void)method2;
@end

C.m

#import "C.h"

@implementation C

-(void)method2{
  NSLog(@"C::method2");
  NSLog(@"next line call super method 1");
  [super method1];
}
@end

AppDelegate.h

#import 

@interface AppDelegate : UIResponder 

@property (strong, nonatomic) UIWindow *window;

@end

AppDelegate.m

“- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions”に以下を追加で。

  A* a = [[[A alloc]init]autorelease];
  B* b = [[[B alloc]init]autorelease];
  C* c = [[[C alloc]init]autorelease];

  NSLog(@"a method");
  [a method1];
  [a method2];
  [a method3];
  NSLog(@"b method");
  [b method1];
  [b method2];
  [b method3];
  NSLog(@"c method");
  ;//←WPのバグで余計なのでてるので注意
  ;//←WPのバグで余計なのでてるので注意
  ;//←WPのバグで余計なのでてるので注意
  NSLog(@"end");

実行するとコンソールには以下の様に。

2012-01-26 12:51:36.090 classTest[51229:f803] a method
2012-01-26 12:51:36.092 classTest[51229:f803] A::method1
2012-01-26 12:51:36.092 classTest[51229:f803] A::method2
2012-01-26 12:51:36.093 classTest[51229:f803] A::method3
2012-01-26 12:51:36.094 classTest[51229:f803] A::method2
2012-01-26 12:51:36.094 classTest[51229:f803] A::method3
2012-01-26 12:51:36.095 classTest[51229:f803] b method
2012-01-26 12:51:36.107 classTest[51229:f803] B::method1
2012-01-26 12:51:36.109 classTest[51229:f803] A::method2
2012-01-26 12:51:36.110 classTest[51229:f803] A::method3
2012-01-26 12:51:36.111 classTest[51229:f803] c method
2012-01-26 12:51:36.113 classTest[51229:f803] B::method1
2012-01-26 12:51:36.114 classTest[51229:f803] C::method2
2012-01-26 12:51:36.114 classTest[51229:f803] next line call super method 1
2012-01-26 12:51:36.117 classTest[51229:f803] B::method1
2012-01-26 12:51:36.117 classTest[51229:f803] A::method3
2012-01-26 12:51:36.118 classTest[51229:f803] end
2012-01-26 12:51:36.119 classTest[51229:f803] Applications

ここで注目。Cクラスが送ったメッセージ[super method1]はどこに届いたか?
答え:Bクラスのオブジェクト。

つまり、superで指定した先は直近のクラスでした。

じゃ、もっと上のクラスのを参照するには?

とりあえずBクラスでOverrideしたmethod1をコメントアウトします。

B.h

#import "A.h"

@interface B : A
//-(void)method1;
@end

B.m

#import "B.h"

@implementation B
/*
-(void)method1{
  NSLog(@"B::method1");
}
// */
@end

もういっかい立ち上げる…とコンソールが激しいことに!!

2012-01-26 12:55:10.106 classTest[51311:f803] a method
2012-01-26 12:55:10.109 classTest[51311:f803] A::method1
2012-01-26 12:55:10.110 classTest[51311:f803] A::method2
2012-01-26 12:55:10.110 classTest[51311:f803] A::method3
2012-01-26 12:55:10.111 classTest[51311:f803] A::method2
2012-01-26 12:55:10.111 classTest[51311:f803] A::method3
2012-01-26 12:55:10.112 classTest[51311:f803] b method
2012-01-26 12:55:10.112 classTest[51311:f803] A::method1
2012-01-26 12:55:10.113 classTest[51311:f803] A::method2
2012-01-26 12:55:10.113 classTest[51311:f803] A::method3
2012-01-26 12:55:10.114 classTest[51311:f803] A::method2
2012-01-26 12:55:10.114 classTest[51311:f803] A::method3
2012-01-26 12:55:10.115 classTest[51311:f803] c method
2012-01-26 12:55:10.115 classTest[51311:f803] A::method1
2012-01-26 12:55:10.116 classTest[51311:f803] C::method2
2012-01-26 12:55:10.117 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.117 classTest[51311:f803] A::method1
2012-01-26 12:55:10.118 classTest[51311:f803] C::method2
2012-01-26 12:55:10.118 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.154 classTest[51311:f803] A::method1
2012-01-26 12:55:10.155 classTest[51311:f803] C::method2
2012-01-26 12:55:10.155 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.156 classTest[51311:f803] A::method1
2012-01-26 12:55:10.162 classTest[51311:f803] C::method2
2012-01-26 12:55:10.163 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.163 classTest[51311:f803] A::method1
2012-01-26 12:55:10.164 classTest[51311:f803] C::method2
2012-01-26 12:55:10.164 classTest[51311:f803] next line call super method 1

途中から再帰呼び出し状態に。

これはCクラスのmethod2で呼び出した [super method1];はAクラスのmethod1がselfのmethod2を呼び出している。

-(void)method1{
  NSLog(@"A::method1");
  [self method2];
  [self method3];
}

つまりCクラスのmethod2を呼び出し、再度Aクラスのmethod1が呼ばれ、という再帰状態に。

あれ?selfってthisみたいなものじゃなかったの?

とどのつまり

継承に関しては直近の先祖の中でOverrideされたものを適用する、という事がわかりました。
superはOverrideされたか否かで参照先が変動するということです。

そして、自身が犯した勘違いは、selfはthisと同じ様に、自身を参照するのかと思っていた点。
実際は、呼び出したオブジェクトのメソッドをコールするのでした。

この挙動を実現するにあたって、存在しないメソッドをコールした際に動作が停止しないで無視される言語仕様にした理由がわかりました。

文献に対する理解力不足がおもわぬ勘違い解消に発展した変なお話。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です