わかった わかった 天地がひっくり返るほどの驚き

また Objective-Cの話です

どんなプログラムでもほとんどの場合 「文字列」を使用します 「文字列」とは、実はメモリーの連続した番地に格納されている何らかのデータです

それを人が見れば、”齋藤 滋”のように理解できるのです でも、コンピューターから見れば、これは単なるメモリー中の数字とかプログラムとかと同一程度のデータでしかありません

コンピューターの機械語に非常に近い C言語では、文字列は、そのまま単なる連続アドレスに格納されたデータであり、文字列の最後に「終了マーク」である、0 (ゼロ) というデータが入っています。そして、この文字列は「静的変数」として宣言されたり、「動的に」確保されたりしない限り、メモリー中の「スタック領域」という部分に格納されるのです。

問題は、この「スタック領域」というのはプログラム実行に大切な領域であり、そこには、プログラムの実行に伴って、その他の変数とか、暫定データとか、戻り番地とか、色々と重要なデータも連続して書き込まれていきます。

そのような重要なデータが、例えば “1234”という文字列の次に書き込まれていたとします、この場合メモリーの連続した番地に、’1′, ‘2’, ‘3,’ ‘4’そして終了マークの’0′ が入っています その次には重要データが格納されています

ここでもしも、この文字列を書き換えた場合どうなるでしょうか?

もしも書き換えが”123″であれば、最初の文字列より短いので、’4’のデータが終了マークの’0’となり、’1′, ‘2’, ‘3’, ‘0’, ‘0’ となるだけでそれに引き続く重要データは保護されていて安全です

しかし、例えば “12345”を書き込んだ場合どうなるでしょうか? 当然ながら溢れて (= overflow)しまい、重要データが突然意味の無い ‘0’に書き換えられてしまいます。

こんれば、当然のことながらプログラムは停止したり、意図しない動作をしていました。この原理を巧みに使用している悪意のあるプログラムのことを “バッファー・オーバーフロー攻撃” と呼びます

もちろんプログラムの規模が小さければ、プログラムを書くプログラマーも、この危険性を意識してそのような間違いが起こらないように常に気をつけて書きます しかし、ある程度大きくなれば、どうしても見落としが起こります そして、重大な問題が起こるのです

これに対しては、システム的にそのようなエラーが混入しないようにしないといけません それで、その後出てきた新しいコンピューター言語では一様に、上に述べたような単純な文字列モデルではなく、ストリング (String) というモデル、これをストリング・クラスと呼びますが、それが導入されていて、C言語文字列ではなく、ストリング・クラスを使用することが強制あるいは推奨されています

Objective-Cでは、単純な C文字列も普通に使用できますが、安全なストリング・クラスを使用することが推奨されています それが、NSStringクラスなのです しかし、ここにもう一つ NSMutableStringクラスというのも存在するのです

この違いは何でしょうか? 書籍や解説ホームページでは、大体一様に以下のような説明が書いてあります

「NSStringクラスでは、いったん作成された文字列はその内容を変更することができません」しかし、「NSMutableStringクラスでは、可変な文字列となります」

この説明を読んでああそうなのか、NSString文字列では、C言語文字列のようなもので、buffer overflowを防ぐために、可変ではないのだ、と理解したのです その理解の下では、以下のプログラムは不正となります

NSString* work0 = @"1234";
work0 = [work0 stringAppendByString: @"5"];

// このようにするとはじめのwork0に確保された
// 文字列アドレスの最後に'5'が書き込まれ、
// 重要データが破壊される筈
// 従って言語仕様でエラーにすべき

しかしながら、これが普通にOKなのです えーーー?? どういうこと??

だったらば、NSMutableStringクラスは何なの?  このように重大な疑問を抱きました そして、色々調べてもどうも明確に書いていないのです 何故だろう? 何故だろう? 疑問はどんどん膨らみました

そして、分かったのです 根本的な発想という概念というか世界観の違いなのです これには驚きましたが、そのようなパラダイムの重大な変化に柔軟に着いていける僕の頭脳の柔軟さに嬉しくなりました

そうなのでは Objective-Cでは オブジェクトに何かをさせるための、メソッド呼び出しは、いわゆるメソッド呼び出しではなくって、メッセージの送信なのです つまり、Objective-Cでは、オブジェクトに何かを強制的にやらせる、のではなくって、「これして下さい」というメッセージを送って、「オブジェクトがやる気になれば」それに応えて振る舞うのです

この結果が実際問題どのように違うのでしょうか? 次のプログラムで明らかです

NSString* work1 = [NSString stringWithString: @"1234"];
//
// NSStringの場合通常下記のように初期化する
// NSString* work1 = @"1234";
//
NSMutableString: work2 = [NSMutableString stringWithString: @"1234";
//
// それぞれの変数を@"一二三"で書き換えるために setStringメッセージを送る
//
[work1 setString: @"一二三"];
[work2 setString: @"一二三"];
//
// それぞれの出力結果は
//
NSLog(@"%@", work1); // (1)
NSLog(@"%@", work2); // (2)

(1)の出力は “123”

(2)の出力は “一二三” というようになります つまり、これが可変かどうか? ということなのです

従って、NSStringで (2) と同様の出力を得るためには

work1 = [work1 setString: @"123"];

という風に代入せねばならないのです 何とも気づいてしまえば当たり前の概念ですが、パラダイムが違うので面食らいました

我ながら馬鹿ではないのか? そのように思ってしまう

我ながら馬鹿ではないか、どうしてこんなプログラムにそれほど入れ込んでいるのか?

それでも大幅にブログラムが簡略化され、短くなっているのをどうしても記録しておきたいのです・・・

///////////////////////////
//  Cyclist.h
///////////////////////////
#import <Foundation/Foundation.h>

@interface Cyclist : NSObject
@property (copy) NSString* name;
@property (assign) float power;
-(id) init;
-(id) initWithName:(NSString*)name:(float)power;
-(NSString*) run:(float) slope;
-(NSString*) description;
@end

///////////////////////////
//  GrowingCyclist.h
///////////////////////////
#import "Cyclist.h"

@interface GrowingCyclist : Cyclist
-(NSString*)run:(float) slope;
@end

///////////////////////////
//  Utility.h
///////////////////////////
#import <Foundation/Foundation.h>

void MyPrint(NSString*);

///////////////////////////
//  Cyclist.m
///////////////////////////
#import "Cyclist.h"

@implementation Cyclist
@synthesize name;
@synthesize power;
-(id) init{
    if(self = [super init]){
    }
    return self;
}
-(id) initWithName:(NSString *)_name:(float)_power{
    if(self = [super init]){
        name = _name;
        power = _power;
    }
    return self;
}
-(NSString*) run:(float)slope{
    NSString* _temp;
    if(power >= slope) {
        _temp=[[NSString alloc]initWithFormat:@"%@ 登攀力 %.1fは、登れます!!", name, power];
    } else {
        _temp=[[NSString alloc]initWithFormat:@"%@ 登攀力 %.1fは、登れません", name, power];
    }
    return _temp;
}
-(NSString*) description{
    NSString* _temp=[[NSString alloc]
                     initWithFormat:@"[名前:%@] [登攀力:%.1f]", name, power];
    return _temp;
}
@end

///////////////////////////
//  GrowingCyclist.m
///////////////////////////
#import "GrowingCyclist.h"

@implementation GrowingCyclist:Cyclist
-(NSString*)run:(float)slope{
    NSString* _temp = [super run:slope];
    self.power += 0.5;
    return _temp;
}
@end

///////////////////////////
//  Utility.m
///////////////////////////

#import "Utility.h"

void MyPrint(NSString* str){
    printf("%s\n", [str UTF8String]);
    return;
}

///////////////////////////
//  main.m
///////////////////////////

#import <Foundation/Foundation.h>
#import "Cyclist.h"
#import "GrowingCyclist.h"
#import "Utility.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Cyclist* cyclist1=[[Cyclist alloc] initWithName:@"老成した齋藤":4.0];
        GrowingCyclist* cyclist2=[[GrowingCyclist alloc] initWithName:@"成長する齋藤":4.0];
        float slope = 3.0; int trial = 3;
        printf("****今度の坂道は%.1f度です これから%i回練習します****\n", slope, trial);

        MyPrint([cyclist1 description]);
        MyPrint([cyclist2 description]);
        for (int i=0; i<trial; i++){
            printf("@試技%i回目\n", i+1);
            MyPrint([cyclist1 run:slope]);
            MyPrint([cyclist2 run:slope]);
        }

        MyPrint([cyclist1 description]);
        MyPrint([cyclist2 description]);

        slope = 7.0; trial = 5;
        printf("\n\n****今度の坂道は%.1f 度です これから%i回練習します****\n", slope, trial);
        MyPrint([cyclist1 description]);
        MyPrint([cyclist2 description]);
        for (int i=0; i<trial; i++){
            printf("@試技%i回目\n", i+1);
            MyPrint([cyclist1 run:slope]);
            MyPrint([cyclist2 run:slope]);
        }
    }
    return 0;
}

これで結果出力は以下の通り

****今度の坂道は3.0度です これから3回練習します****
[名前:老成した齋藤] [登攀力:4.0]
[名前:成長する齋藤] [登攀力:4.0]
@試技1回目
老成した齋藤 登攀力 4.0は、登れます!!
成長する齋藤 登攀力 4.0は、登れます!!
@試技2回目
老成した齋藤 登攀力 4.0は、登れます!!
成長する齋藤 登攀力 4.5は、登れます!!
@試技3回目
老成した齋藤 登攀力 4.0は、登れます!!
成長する齋藤 登攀力 5.0は、登れます!!
[名前:老成した齋藤] [登攀力:4.0]
[名前:成長する齋藤] [登攀力:5.5]


****今度の坂道は7.0 度です これから5回練習します****
[名前:老成した齋藤] [登攀力:4.0]
[名前:成長する齋藤] [登攀力:5.5]
@試技1回目
老成した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 5.5は、登れません
@試技2回目
老成した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 6.0は、登れません
@試技3回目
老成した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 6.5は、登れません
@試技4回目
老成した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 7.0は、登れます!!
@試技5回目
老成した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 7.5は、登れます!!

自転車乗りプログラム改良

//
//  GrowingCyclist.m
//

#import "GrowingCyclist.h"

@implementation GrowingCyclist:Cyclist

-(NSString*)run:(float)slope{
    NSString* _temp = [super run:slope];
    self.power += 0.5;
    return _temp;
}

-(NSString*) description{
    NSString* _temp=[[NSString alloc]
                     initWithFormat:@"%@, 力はgrowingで初期値%.1f", [self name], self.power];
    return _temp;
}

@end

先ほどのプログラムでは class GrowingCyclistが折角 class Cyclistを継承しているのに、坂を登るか否かの判定methodがダブリ冗長でしたので、改良しました

自転車乗りプログラム Objective-C版

ようやく完成です 標準コンソール出力の NSLog()を使用すると、時刻とか色々な情報があわせて出力されるので、 Utility.hにおいて printf()を用いて結果のみ出力できるようにしました

///////////////////////////////////
//  Cyclist.h
///////////////////////////////////
#import <Foundation/Foundation.h>

@interface Cyclist : NSObject

@property (copy) NSString* name;
@property (assign) float power;

-(id) init;
-(id) initWithName:(NSString*)name:(float)power;
-(NSString*) run:(float) slope;
-(NSString*) description;

@end


///////////////////////////////////
//  GrowingCyclist.h
///////////////////////////////////
#import "Cyclist.h"

@interface GrowingCyclist : Cyclist

-(NSString*) run:(float) slope;
-(NSString*) description;

@end


///////////////////////////////////
//  Utility.h
///////////////////////////////////
#import <Foundation/Foundation.h>

int MyPrint(NSString*);


///////////////////////////////////
//  Cyclist.m
///////////////////////////////////
#import "Cyclist.h"

@implementation Cyclist
@synthesize name;
@synthesize power;

-(id) init{
    if(self = [super init]){
    }
    return self;
}

-(id) initWithName:(NSString *)_name:(float)_power{
    if(self = [super init]){
        name = _name;
        power = _power;
    }
    return self;
}

-(NSString*) run:(float) slope;{
    NSString* _temp;
    if(power >= slope) {
        _temp=[[NSString alloc]initWithFormat:@"%@ 登攀力 %.1fは、登れます!!", name, power];
    } else {
        _temp=[[NSString alloc]initWithFormat:@"%@ 登攀力 %.1fは、登れません", name, power];
    }
    return _temp;
}

-(NSString*) description{
    NSString* _temp=[[NSString alloc]
                     initWithFormat:@"%@, 力は%f", name, power];
    return _temp;
}

@end


///////////////////////////////////
//  GrowingCyclist.m
///////////////////////////////////
#import "GrowingCyclist.h"

@implementation GrowingCyclist:Cyclist

-(NSString*) run:(float)slope{
    NSString* _temp;
    if(self.power >= slope) {
        _temp=[[NSString alloc]initWithFormat:@"%@ 登攀力 %.1fは、登れます!!", self.name, self.power];
        self.power += 0.5;
    } else {
        _temp=[[NSString alloc]initWithFormat:@"%@ 登攀力 %.1fは、登れません", self.name, self.power];
        self.power += 0.5;
    }
    return _temp;
}

-(NSString*) description{
    NSString* _temp=[[NSString alloc]
                     initWithFormat:@"%@, 力はgowingで初期値%f", [self name], self.power];
    return _temp;
}

@end


///////////////////////////////////
//  Utility.m
///////////////////////////////////
#import "Utility.h"

int MyPrint(NSString* str){
    printf("%s\n", [str UTF8String]);
    return 0;
}


///////////////////////////////////
//  main.m
///////////////////////////////////
#import <Foundation/Foundation.h>
#import "Cyclist.h"
#import "GrowingCyclist.h"
#import "Utility.h"


int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Cyclist* cyclist1=[[Cyclist alloc] initWithName:@"成熟した齋藤":4];
        GrowingCyclist* cyclist2=[[GrowingCyclist alloc] initWithName:@"成長する齋藤":4];
        float slope = 3.0;
        int n = 3;
        printf("****今度の坂道は%.1f度です これから%i回練習します****\n", slope, n);

        MyPrint([[NSString alloc]initWithFormat:@"%@は、練習開始前の登攀力が%.1fです", cyclist1.name, cyclist1.power]);
        MyPrint([[NSString alloc]initWithFormat:@"%@は、練習開始前の登攀力が%.1fです", cyclist2.name, cyclist2.power]);
        for (int i=0; i<n; i++){
            MyPrint([[NSString alloc]initWithFormat:@"%@",[cyclist1 run:slope]]);
            MyPrint([[NSString alloc]initWithFormat:@"%@",[cyclist2 run:slope]]);
        }
        
        slope = 7.0;
        n = 5;
        printf("\n\n****今度の坂道は%.1f 度です これから%i回練習します****\n", slope, n); 
        MyPrint([[NSString alloc]initWithFormat:@"%@は、練習開始前の登攀力が%.1fです", cyclist1.name, cyclist1.power]);
        MyPrint([[NSString alloc]initWithFormat:@"%@は、練習開始前の登攀力が%.1fです", cyclist2.name, cyclist2.power]);
        for (int i=0; i<n; i++){
            MyPrint([[NSString alloc]initWithFormat:@"%@",[cyclist1 run:slope]]);
            MyPrint([[NSString alloc]initWithFormat:@"%@",[cyclist2 run:slope]]);
        }
    }
    return 0;
}

これで結果出力は以下の通り

****今度の坂道は3.0度です これから3回練習します****
成熟した齋藤は、練習開始前の登攀力が4.0です
成長する齋藤は、練習開始前の登攀力が4.0です
成熟した齋藤 登攀力 4.0は、登れます!!
成長する齋藤 登攀力 4.0は、登れます!!
成熟した齋藤 登攀力 4.0は、登れます!!
成長する齋藤 登攀力 4.5は、登れます!!
成熟した齋藤 登攀力 4.0は、登れます!!
成長する齋藤 登攀力 5.0は、登れます!!


****今度の坂道は7.0 度です これから5回練習します****
成熟した齋藤は、練習開始前の登攀力が4.0です
成長する齋藤は、練習開始前の登攀力が5.5です
成熟した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 5.5は、登れません
成熟した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 6.0は、登れません
成熟した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 6.5は、登れません
成熟した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 7.0は、登れます!!
成熟した齋藤 登攀力 4.0は、登れません
成長する齋藤 登攀力 7.5は、登れます!!

ようやく少しずつ Objective-Cが分かってきました

新しい言語を使えるようになるのは これまでと全く違う世界が目の前に広がり、とても楽しくもかつ辛いものです。今回、年末よりObjective-Cという独特の世界観を持つ言語にとりかかりました。合計5冊の書籍を読みあさり、それと共に Internetで、各種の解説ページを読み、またPDFとして確保しました。

Objective-Cはいわずと知れた Mac OSXで使われている開発言語です。当初はとてもマイナーな言語でしたが、iiPhone/iPadの爆発的ブームと共に、主要言語の一つとなりつつあります。共に C言語をルーツに持つ Objective-Cと C++ですが、その両者はとんでもなく違うアプローチにより いわゆるオブジェクト指向言語を実現しました。まだこんなふうに評論的に述べられる立場ではないと思いますが、敢えて言わせてもらえば、C++は徹底的にコンパイラの道を歩み、その優位性は開発されたアプリケーションが有する圧倒的なスピードにあります。

一方 Objective-Cはどちらかと言えばインタプリタの道に近づき、その優位性は、実行時の圧倒的な柔軟性にありますが、スピートという点では C++には相当劣ります。

しかしながら、現在の hardware/CPUの進捗の前では 言語がもたらすスピードが重要な局面というのはものすごく限定されてきました。むしろ、いかにユーザーに応じて実行時に柔軟に変化できるか? そのようなアプリを用意に開発できるか? それが重要になってきたのです。それと共に、Objective-Cが脚光を浴びつつあるのです。

まあ、そんな御託を並べるよりも、要するに僕としては、MacBook Proの上で走るかっこいいアプリを、できるならば iPhoneで動く簡単なアプリを作れるようになりたいから、そして新しい世界を見て、そこに入れる自分がまだいるか、自分の脳味噌が未だ新しい物を受け入れるぐらいに柔軟であるか否か、それを検証するため、そんな目的で Objective-Cの勉強を始めたのです

最初は茨の道でした、だって もう端から概念が違うのです 「なになに メッセージ?」 そんなのmethodではないの? ここから始まります

ようやく、以前に C++で書いた自転車乗りを Objective-Cで書くことができました それは以下のようなものです

まずは宣言ファイルである、Cyclist.hとそれを継承する GrowingCyclist.hです

// Cyclist.h
#import 

@interface Cyclist : NSObject

@property (copy) NSString* name;
@property (assign) float power;

-(id) init;
-(id) initWithName:(NSString*)name:(float)power;
-(NSString*) run:(float) slope;
-(NSString*) description;

@end

そして GrowingCyclist.h

// GrowingCyclist.h
#import "Cyclist.h"


@interface GrowingCyclist : Cyclist

-(NSString*) description;

@end

そして、それぞれの定義ファイルである Cyclist.mをまずは

// Cyclist.m
#import "Cyclist.h"


@interface GrowingCyclist : Cyclist

-(NSString*) description;

@end

そして次は GrowingCyclist.mという定義ファイル

// GrowingCyclist.m
#import "GrowingCyclist.h"


@implementation GrowingCyclist:Cyclist

-(NSString*) description{
    NSString* _temp=[[NSString alloc]
                     initWithFormat:@"%@, 力はgowingで初期値%f", [self name], self.power];
    return _temp;
}

@end

そしてプログラム本体は以下のmain.mファイルです

// main.m

#import <Foundation/Foundation.h>
#import "Cyclist.h"
#import "GrowingCyclist.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        // insert code here...
        Cyclist* cyclist1=[[Cyclist alloc] initWithName:@"齋藤 滋":5.0];
        NSLog(@"名前は %@", cyclist1.name);
        NSLog(@"%@",[cyclist1 run:6.0]);

        GrowingCyclist* cyclist2=[[GrowingCyclist alloc] initWithName:@"growing齋藤 滋":10.0];
        NSLog(@"名前は %@", cyclist2.name);
        NSLog(@"%@", [cyclist2 run:6.0]);
        
    }
    return 0;
}

これで出力は以下の通り

2013-01-05 14:40:53.590 cycling[18188:303] 名前は 齋藤 滋
2013-01-05 14:40:53.593 cycling[18188:303] 齋藤 滋は登れません
2013-01-05 14:40:53.593 cycling[18188:303] 名前は growing齋藤 滋
2013-01-05 14:40:53.594 cycling[18188:303] growing齋藤 滋は登れます

もっとも今回はだんだんとslope角度を変化させたり、繰り返し練習させていませんので、最初登れなかったcyclistが次第に登れるようになることを simulateしてはいませんが、既に本質的な部分は理解できたのです。

明けましておめでとうございます

何のかんの言いつつ、あっという間に年が明けてしまいました 毎年この時期、「今年こそは良い年になるように」と、祈ります

暮れから正月にかけて、いつの間にか年が明けた、という気分です この間、Objective-C/Cocoaの勉強をしています 仕事もせねばなりませんが、なかなか気分が向きません

明日からは改めねばならいなでしょう 本年も宜しくお願いします。

非常に判りやすい説明ありがとう

ここに Mac OS-Xのメモリ管理方法についての判りやすい説明がありました 今勉強中なのです

Mac向けの開発言語として OS X v10.5 (Leopard) から Objective-C に代わって Objective-C 2.0 が導入されました.Objective-C 2.0 は Objective-C を大幅に拡張したものですが,その中の最大のものはガベージコレクタの導入でしょう.もはや retain/release は考えなくてよくなったのです.

その後登場した iOS (当時は iPhone OS)では Objective-C 2.0 が採用されましたがガベージコレクタは引き継がれませんでした.プログラマは手動で retain/release しなければならないのです.(リファレンスカウンタが使えるため C++ の new/delete よりははるかに安全ですが.)

OS X v10.8 (Mountain Lion) になり,この Objective-C 2.0 ガベージコレクタ機能は非推奨になりました.iOSとの整合性を考えると,iOSにガベージコレクタを搭載するか,OS X から再びガベージコレクタを取り除くかですが,アップルは後者を選択しました.(プログラマから見るとやや驚きですよね.)

そのかわり,Xcode 4.2 からコンパイル時に retain/release を自動的に挿入する機能が付きました.アップルはこの技術を Automatic Reference Count (ARC) と呼んでいます.

ガベージコレクタを前提にしたコードは変更の必要は(原則として)ありません.一方,Objective-C 1.0 時代のコードはおそらく NSAutoreleasePool を多用していると思いますが,この NSAutoreleasePool はサポートされなくなったため,書き直しが必要です.(おそらくいちばん簡単なコード移行方法は,retain/release を削除してしまうことです.)