月別アーカイブ: 2012年1月

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と同じ様に、自身を参照するのかと思っていた点。
実際は、呼び出したオブジェクトのメソッドをコールするのでした。

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

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

GoogleAppを使ってリマインダーを作ってみた。

作った動機は以下の記事を読んで。
http://readingmonkey.blog45.fc2.com/blog-entry-556.html

効率良く記憶/勉強するためには特定の法則間隔でリマインドするのが効率良いらしい。
でもそのリマインドの間隔ではOmnifocusではやれない。

うまいことやれないかなーと考えてみたらGoogleカレンダー上に通知イベントを生成するスクリプトを書けばいいじゃんと思い至る。

んでちょいと調べた結果、可能だということが分かったので早速作成。
作業2時間。久々にJavascript触った割りにちゃんとできました。

要件整理

  • ボタンを押すと前述の前述のURLの間隔単位でGoogleCalendarにイベントを生成してくれる。
  • イベント名は任意で。
  • リマインドしてくれる回数もしていさせて欲しい。
  • 可能であれば今何回目なのかも知らせて欲しい。
  • リマインダーなんだから通知して欲しい。
  • 詳細もメモできたらいいな。

とする。

作り方

  1. google docsに新しいスプレッドシートを作成する
  2. スクリプトエディタを開いて後述のソースをコピペする
  3. ボタンを設置してfunctionを関連づけさせとく
  4. google calendarに新しいカレンダーを作り、ソース上に記述されてるカレンダー名と同じ名前を付けておく
  5. 4のカレンダーの通知の項目にメールとかポップアップとかでリマインドさせるようにしとく
  6. 完成!

1:google docsに新しいスプレッドシートを作成する

割愛

2:スクリプトエディタを開いて後述のソースをコピペする

スプレッドシートを開き、メニューバーからツール->スクリプトエディタでエディタを開き、
後述のソースをコピペします。

3:ボタンを設置してfunctionを関連づけさせとく

makeRemindEventOnCal change inspector
メニューバーから↑の手順で図形描画を選択し、”図形描画を挿入”画面に移行し、
makeRemindEventOnCal create rect
矩形を配置します。
makeRemindEventOnCal draw rect
矩形が用意できたら右上の”保存して閉じる”ボタンを押してスプレッドシートに戻ります。
makeRemindEventOnCal Add Script
スプレッドシートに描画された図形にマウスオーバーすると”図形描画”の文字が。ここのプルダウンからスクリプトを関連づけさせます。
makeRemindEventOnCal set script
記述する関数名はソースの2つ目の関数名を記述します。
“makeRemindEventOnCal change inspector”

4:google calendarに新しいカレンダーを作り、ソース上に記述されてるカレンダー名と同じ名前を付けておく

自分のアカウントのグーグルカレンダーを開いて、新しいカレンダーを追加します。カレンダーに名前を付けれるのでソースで指定してある”記憶強化リマインダ”に。

名前を変えるのであればソース側のカレンダー名も偏向すること。

5:4のカレンダーの通知の項目にメールとかポップアップとかでリマインドさせるようにしとく

makeRemindEventOnCal alert setting
カレンダーの通知の項目を開いて、メールやポップアップに適当な時間を記述して通知してくれるようにしときます。これを設定しないとただのカレンダー止まりなのでちゃんと能動的に動いてくれる様にします。

6:完成!

ここまでやれば完成。動作は以下の通りです。
ReminderOnCalendar write eventName
イベント名を記述。
カレンダー上に表記される文字なので短く分かりやすいものに。
ReminderOnCalendar write loop count
最初の1ヶ月は間隔が長くなる不定期なので、固定でリマインドするようにしてあります。
その後の安定的(31日毎)にリマインドする回数を指定します。
ReminderOnCalendar write description
カレンダーに記載する詳細を入力する欄。思い出すべき内容を出来るだけ細かく書くか、何をやるかだけ書くか、見出し以外の事を書いときます。
ReminderOnCalendar run script end
スクリプトが完了するとこれが表示されます。

ではカレンダーを見てみます…。

makeRemindEventOnCal result1
おお!ちゃんと書かれてる!
makeRemindEventOnCal result2
詳細もご覧の通り記述されます。

ソース

ソースは以下の通り。

/*
カレンダーにリマインダーの理論間隔でイベントを設置するスクリプト
ver. 0.1 2012/1/3: OHGAKI Kunihiro
ver. 0.2 2012/1/4: OHGAKI Kunihiro  : コメントを書き直し。
*/

/*
makeCalEvent
カレンダーにイベントを書き込む関数
cal as Calendar Object
カレンダーオブジェクト。何度も取得するのもアレなので一度取得したら保持させるようにしたので引数で渡してます。
グローバル変数にしとけよって話だ…。
GoogleAppってグローバル変数ってつかえるのかね

eventName as String.
カレンダーに記述するイベント名を文字列で取得。

targetDate as Date
実際に記述する日時を日付で指定。

count as Integer
リマインドした回数を詳細とかイベント名に記述する。

descript as String
カレンダーのイベントの詳細部分に記述する文字列。

*/

function makeCalEvent(cal,eventName, targetDate,count,descript){
  var cname = eventName;
  if (cname != "" & cname != "cancel"){
    targetDate.setHours(0);
    targetDate.setMinutes(0);
    targetDate.setSeconds(0);
//    Browser.msgBox(descript);
    cal.createEvent(
      eventName+"@"+count+"回目",
      targetDate,
      targetDate,
      {
        description:count + "回目のリマインド" + descript,
        sendInvites:false
      }
    );
  }
}

/*
メインの関数な。スプレッドシートから呼び出すのはこれ。
makeRemindEventOnCal カレンダーにリマインダーイベントをセットするスクリプト。
リマインドの間隔は以下のURLの記事を元に作成してます。
http://readingmonkey.blog45.fc2.com/blog-entry-556.html
*/
function makeRemindEventOnCal(){
    var cal = CalendarApp.openByName("記憶強化リマインダ"); //←リマインダー専用のカレンダーを予め作成。名前は任意で。

  //カレンダーに表示するイベント名を入力する。
  var eventName = Browser.inputBox("イベント名を入力してください");
  if(!eventName)return;
  //リマインドする回数を入力。
  var count = Browser.inputBox("安定期後にリマインドしたい回数を数値で入力", "整数でよろしく", null);
  var description = Browser.inputBox("詳細を記述してください。未記入でもOK");
  var remindArray = [1,2,4,7,15,21];
  //軌道に乗るまでの1ヶ月はこの間隔で広げて行く。
  var remindMonth = 31;
  //軌道にのったら一月以上あけてはならないらしい。
  var beforStationaryPhaseCount = remindArray.length;
  for(var i = 0; i < beforStationaryPhaseCount; i++){
    var day = new Date;
    var target = remindArray.shift();
    day.setTime(day.getTime() + target*24*3600*1000);//指定の日にリマインドタイミングをセット。
    makeCalEvent(cal,eventName,day,i+1,description);
  }
  var afterDay = new Date;
  if(count != 0){
    for(var i = 0; i < count; i++ ){
      afterDay.setTime(afterDay.getTime() + remindMonth*24*3600*1000);//31日後にセット。
      makeCalEvent(cal,eventName,afterDay,i + beforStationaryPhaseCount +1,description);
    }
  }
      //完了通知
  Browser.msgBox("イベントのセットが完了しました。");
}

ハマったところ

カレンダーのイベントを生成する.createEvent()の引数に詳細を文字列で入力するんだけども、この引数がAdvanced Argumentsであり、省略可能引数オブジェクトのプロパティで、最初に発見したサンプルはバージョン違いなのか記述方法が違っていた。エラーが出ていたので本家サイトを探すに至り、難を逃れた。今後は最初から本家を見るように心がける。日本語サイトに逃げがちなのを戒めますわよ。

あと、タイポで反映されてなかったのに気づかずに5分くらい悩んだ。相変わらずエラー出ないのでこういうバグ発見はマジ困る。ブラウザ上のエディタではなくてローカルのちゃんとしたエディタで作業すべきなんだろうな。※英語記述のタイポを戒めてくれるエディタを教えて下さい!英語力低い僕には(ry

参考にしたサイト