2011年6月21日火曜日

Java脳でもわかるObjective-C入門

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

photo: Some rights reserved by yukiv


Javaと比較しながら「へぇーObjective-Cってそういう感じか」と理解した気になろう。

流れ




軽くジャブ


まずは以下のソースを眺める

//SampleClass.h
#import <Foundation/Foundation.h>
#import "SampleClassDelegate.h"

@interface SampleClass : NSObject <sampleclassdelegate>{
 int index;
 NSString *str;
}
@property(assign) int index;
@property(nonatomic, retain) NSString *str;

+(void) dump : (SampleClass*) instance;
-(void) setData : (NSString*) str  str: (NSString*) number;
-(void) setData : (NSString*) str  num: (NSInteger) number;
-(NSString*) toString;

@end

//SampleClass.m
#import "SampleClass.h"
@implementation SampleClass
@synthesize index;
@synthesize str;
+(void) dump:(SampleClass *)instance{
 NSLog(@"%@", [instance toString]);
}
-(void) setData : (NSString*) text  str: (NSString*) number{
 self.str = text;
 self.index = [number intValue];
}
-(void) setData : (NSString*) text  num: (NSInteger) number{
 self.str = text;
 self.index = number;
}
-(NSString*) toString{
 return [NSString stringWithFormat:@"%@:%d", self.str, self.index];
}
//delegate
-(NSString*) toStringDao{
 return [NSString stringWithFormat:@"%@:%d%@", self.str, self.index, @"だお"];
}
@end
さすがのSyntaxHighlighterさんもObjective-Cはサポートしてねーって事でC++でハイライト。
ハイライトしなさ杉。
うんざりする見た目だけど、これから説明する事を読めばスラスラ理解できるようになりますよ!



クラスファイルの構成


Javaなら"クラス名.java"なわけですが、Objective-CはC言語の上に乗ってる言語なので、
その辺はCの仕組みに準拠します。
C/C++同様にヘッダファイルと実装ファイルに分かれてます。分けなくてもいいけど分けた方がいいので分けてます。
XCodeでクラスを新規作成した場合は以下の2ファイルが作成されます。
"クラス名.h" //ヘッダ
"クラス名.m" //実装ファイル

まーCです。



コメント

全く何もかも同じ

■Java

//コメント
/* コメント */

■Objective-C

//コメント
/* コメント */


import文


Objective-CはC言語と違って#includeではなく#importを使うようになっています。
違いは二重includeを自動的に防いでくれるだけです。#ifndefとかそういう制御がいらんという事です。
Javaしか知らない人はこの辺は気にしなくていいです。#importって書いておけばいいんです。

■Java
import java.lang.string;

クラスパスが通っていれば読み込めます。


■Objective-C
#import <UIKit/UIKit.h>
#import "Moge.h"

上の書き方は標準ライブラリから探す感じ(ほんとは違います)
下の書き方はプロジェクトの中から探す感じっぽい雰囲気。多分



変数の定義


概ねJavaと同じです。



■Java
int i = 10;

■Objective-C
int i = 10;


参照


クラスオブジェクトは必ず参照型です。Javaと同じですね。
書き方は"クラス名*"です。

■Java
Object object;

■Objective-C
NSObject *object;


id型


javaでいう処のObject型。
どんなオブジェクトでも入れられる型です。生ポインタという認識で大体いいかもしれません。
クリックイベントなどの引数でsenderをidで渡すといった形で使われます。

■Java
String object1 = new String("あはは");
Object object2 = object1;

■Objective-C
NSObject *object1 = [[NSObject alloc] init];
id object2 = object1;

ちなみにidにプリミティブ型を突っ込むと、警告が出ます。
Javaだとコンパイルエラーになりますね。



クラス定義


Javaと違って、Objective-Cではまずクラスを宣言します。その後実装コードを書きます。
一般的には宣言はヘッダに、実装は実装ファイルに書いていきます。
また、宣言と実装で宣言子が異なります。

宣言


■Java
宣言のみとかってねーよ。(ないよね?)


■Objective-C
@interface Moge : NSObject{
//メンバ変数宣言
}
//メソッド宣言
@end
キモイんですが、 @interface クラス名 : スーパークラス名で始まり
{ }の中にメンバ変数を宣言し、その後メソッドを宣言、最後に @end で閉じます。
メンバやメソッドは宣言のみで初期化や実装をここで書くことは出来ません。
この宣言を書いたヘッダファイル(.h)を実装ファイル(.m)でimportして実装を行います。


実装


■Java
public class Moge{

}

■Objective-C
#import "Moge.h"
@implementation Moge
//メソッドの実装などなど
@end
ヘッダをimportし、そこに宣言されたクラスを実装します。
宣言子は@implementationです。
ヘッダで宣言したメソッドの実装をガリガリ書きます。(実際の実装例はもうちょい下でやります)



メソッドの宣言、実装


メソッドには二種類あります。クラスメソッドとインスタンスメソッドです。
Javaでいう所のstaticメソッドとメンバメソッドですね。一緒ですね。
ヘッダのクラス宣言の所で各メソッドを宣言していきます。
ちなみにヘッダで宣言したメソッドは全てpublicになります。
privateなメソッドはmファイル内に実装だけいきなり書くことで実現する事が出来ます。


■宣言の仕方!


まぁキモイんですが慣れです。
-(void) moge : (NSString*) str isSave : (BOOL) isSave;
そうです、":"で色々区切ってます。":"の後に引数を書いていくイメージですね。
最初見たとき「えっ」ってなりました。意味不明ですよねこれ。
つまりこういう事です

-(戻り値の型) メソッド名 : (引数の型) 引数名 ラベル名 : (引数の型) 引数名

さて気になるのが「ラベル名」です。これ実はいりません。なくてもビルド通ります。
どういう時に使うかっていうとオーバーロード的な事をしたいときに使うんですね。
Javaなら勝手にやってくれるってーのにねー。やれやれですね。

-(void) moge : (NSString*) str isSave : (BOOL) isSave;
-(void) moge : (NSString*) str num : (NSInteger) number;
呼び出し側でラベル名を指定しながら引数をぶっこむ事でメソッドの呼び分けができるわけですね。
オーバーロードの際はラベル名付けて無いとコンパイル時に怒られます。

さて勘のいい方は気がついたかもしれません。
メソッド宣言の先頭に"-"とかいって毛が生えていました。
これ気のせいじゃないです。"-"には意味があります。
先頭の記号はメソッドの種類を現しています。



クラスメソッド


Javaで言うところのstaticなメソッド。
クラスをインスタンス化しなくても使えます。

■Java

public static void moge(String str, Boolean isSave){}


■Objective-C

+(void) moge : (NSString*) str isSave : (BOOL) isSave;
先頭を"+"にするとクラスインスタンスである、という事になります。


■インスタンスメソッド


いわゆる普通のメソッド。

■Java

public void moge(String str, Boolean isSave){}


■Objective-C

-(void) moge : (NSString*) str isSave : (BOOL) isSave;



メッセージ式


基本的に見た目がキモいだけです。色々と高尚な何かがあるみたいですけど、気にしなくてもいいです。

■Java

Integer num = new Integer(120);
num.toString();


■Objective-C

NSNumber* num = [NSNumber numberWithInt:120];
[num stringValue];

「メッセージ式」でググると色々出てくるので興味ある方はアレして下さい。
引数付きのコンストラクタも特殊です。ていうかコンストラクタは無いです。
allocの後に初期化メソッドを呼びます。引数付きのコンストラクタは引数付きの初期化メソッドで実現するんですねー。
上の例ではallocすらしてませんね、ファクトリメソッド的な物っぽいですね。多分そうです。



プロパティ


Javaでいうgetter/setterを隠蔽してくれる感じの仕組みです。
いろんな設定ができますがココでは省きます。

■Java

public void setIndex(int index){
  this.index = index;
}
public int getIndex(){
  return this.index;
}

■Objective-C

プロパティも宣言と実装的なものが分かれます。

・宣言
これはクラス宣言の{}の後に書きます。
@property(オプション的な何か) 名前;

こういう感じです。
@interface Moge : NSObject{
 NSInteger index;
}
@property(assign, nonatomic) NSInteger index;
@end


・実装的な感じ
@synthesize index;

上記の例だと自動的にgetter/setterが見えない所で実装されています。
色々と他にも使い方がありますがとりあえずこれだけでおkな気がします。
これによって以下の様な書き方ができます。

NSInteger a = self.index;
self.index = 10;
内部的にはgetter/setterメソッドが実行されています。

詳細はプロパティの宣言と実装とか読んで下さい。



セレクタ


いわゆるコールバックです。関数ポインタみたいなもんです。
Javaで言うとjava.lang.reflect.Methodだなぁというのが個人的なイメージ。

■Java

public class Moge{
  //コールバック
  public void callback(String str){
    System.out.println(str);
  }
  public void caller(Object target, Method callback)throws Exception{
    callback.invoke(target, "あはは");
  }
  public static void main(String[] args){
    try{
      Moge moge = new Moge();
      //リフレクションで取り出す
      moge.caller(moge,Moge.class.getMethod("callback", String.class));
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

■Objective-C

Objective-Cでは@selectorでメソッドを取りだす事ができます。
取り出したメソッドはSEL型という型になります。
コールバックを実現するメソッドは引数にセレクタを受け取って結果をセレクタに返してやります。
メディア再生のフレームワークとか使う時にセレクタをセットしてメディアの再生ステータスの変更を受け取ったり、
ボタンにセットしてクリックの通知を受ける時などに使います。

@implementation Moge
//コールバック
-(void) callback:(NSString*) str{
  NSLog(@"%@", str);
}
-(void) caller:(id) target callback:(SEL) callback{
  NSInvocation* invoker = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:callback]];
  NSString* string = @"あはは";
  [invoker setTarget:target];
  [invoker setSelector:callback];
  [invoker setArgument:&string atIndex:2];
  [invoker invoke];
}

-(void) exec{
  //@selectorでcallback:(NSString*) strを渡す
  [self caller:self callback:@selector(callback:)];
}
@end



プロトコル


iOSではデリゲートと呼ばれるものです。Javaでいう所のインタフェースにかなり近いです。
ていうか一緒です。

■Java
//宣言
public interface Cat{
  public String getBreed();
}
//実装
public class Mike implements Cat{
  @Override
  public String getBreed(){
    return "tortoiseshell cat";
  }
}


■Objective-C
//プロトコルの宣言
@protocol Cat
-(NSString*) getBreed;
@end

//クラスの宣言
@interface Mike : NSObject < Cat >
//書かなくてもいけるよ
@end

//クラスの実装
@implementation Mike
-(NSString*) getBreed{
  return @"tortoiseshell cat";
}
@end

クラス宣言の最後に< プロトコル名達 > でプロトコルの実装を宣言できます。
プロトコルは複数実装できます。Javaのインタフェースと同じっすね。
プロトコルはiOSではデリゲートとして頻繁に登場します。
例えばWebView(UIWebView)。
AndroidではWebViewClientを継承してshouldOverrideUrlLoadingを実装しますが、
iOSではUIWebViewDelegateというプロトコルを実装してセットしてあげる感じになります。
その他アプリケーションのライフサイクルの通知もデリゲートです。



カテゴリ


カテゴリはちょっと異質で、javascriptのプロトタイプ拡張みたいな雰囲気を持っています。
Javaには無い機能です。超便利です。

■Java
m9(^Д^)

■Objective-C
書き方はクラスと大体同じです。

@interface 拡張するクラス (カテゴリ名)

例えば全てのクラスの基底であるNSObjectに適当にメソッドを追加するなら
@interface NSObject (Unko)
-(void) buriburi;
@end
こんな感じでヘッダに書き、普通に実装します。
ファイル名は判りやすいように"NSObject+Unko.h"という風に作られる事が多いです。それしか見た事ないす。
拡張したNSObjectが使いたい人は"NSObject+Unko.h"をimportするだけです。
それだけでいつでもburiburiメソッドを呼べます。凄いっすね。
Objective-Cじゃなきゃできません。

もしObjective-Cじゃなかったら、そんなのObjective-Cじゃないんです



文字列リテラル


Objective-Cでの文字列リテラルでは、先頭に@を付ける必要があります。
@"文字列"はNSStringのリテラル表現となります。

■Java
String str = "moge";
Object str2 = "hoge";

■Objective-C

NSString* str = @"moge";
id str2 = @"hoge";



ログ


ログは重要すなぁ。Javaでいう標準出力なんですが、ほんとに標準出力なのか怪しいのでログという事にしました。

■Java
System.out.println("わーい");


■Objective-C
NSLog(@"わーい");



ひと通り使ってみる感じ


では最後にこれらの機能をひと通り使うクラスなどを載せてみます。

SampleClassDelegate.h
//プロトコル(デリゲート)
#import <UIKit/UIKit.h>

@protocol SampleClassDelegate
-(NSString*) toStringDao;
@end

SampleClass.h
#import <Foundation/Foundation.h>
#import "SampleClassDelegate.h"

@interface SampleClass : NSObject <SampleClassDelegate>{
 int index;
 NSString *str;
}
@property(assign) int index;
@property(nonatomic, retain) NSString *str;

+(void) dump : (SampleClass*) instance;
-(void) setData : (NSString*) str  str: (NSString*) number;
-(void) setData : (NSString*) str  num: (NSInteger) number;
-(NSString*) toString;

@end

SampleClass.m
#import "SampleClass.h"
@implementation SampleClass
@synthesize index;
@synthesize str;

+(void) dump:(SampleClass *)instance{
 NSLog(@"%@", [instance toString]);
}

-(void) setData : (NSString*) text  str: (NSString*) number{
 self.str = text;
 self.index = [number intValue];
}

-(void) setData : (NSString*) text  num: (NSInteger) number{
 self.str = text;
 self.index = number;
}

-(NSString*) toString{
 return [NSString stringWithFormat:@"%@:%d", self.str, self.index];
}
//delegate
-(NSString*) toStringDao{
 return [NSString stringWithFormat:@"%@:%d%@", self.str, self.index, @"だお"];
}

@end

SampleClass+Suffix.h
//カテゴリ
#import "SampleClass.h"
@interface SampleClass (Suffix)
-(void) toStringSuffix:(id) target callback:(SEL) callback;
@end

SampleClass+Suffix.m
#import "SampleClass+Suffix.h"
@implementation SampleClass (Suffix)
-(void) toStringSuffix:(id) target callback:(SEL) callback{
 NSInvocation* invoker = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:callback]];
 NSString* string = [NSString stringWithFormat:@"%@%@", self.str, @"っていう"];
 [invoker setTarget:target];
 [invoker setSelector:callback];
 [invoker setArgument:&string atIndex:2];
 [invoker invoke];
}
@end

User.h
//SampleClassを利用するクラス
#import <Foundation/Foundation.h>
#import "SampleClass.h"
#import "SampleClass+Suffix.h"
@interface User : NSObject {
}
-(void) exec;
@end

User.m
#import "User.h"
@implementation User

-(void) exec{
 SampleClass* sample = [[SampleClass alloc] init];
 id<SampleClassDelegate> delegate = sample;
 
 NSNumber* num = [NSNumber numberWithInt:120];
 [num stringValue];
 
 
 [sample setData:@"一回目" num:100];
 NSLog(@"%@", [sample toString]);

 [sample setData:@"二回目" str:@"50"];
 NSLog(@"object:%@", [sample toStringDao]);
 NSLog(@"delegate:%@", [delegate toStringDao]); 
 //セレクタ
 [sample toStringSuffix:self callback:@selector(callback:)];
 
}
-(void) callback :(NSString*) str{
 NSLog(@"callback:%@", str);
 [str release];
}
@end


実行結果:
一回目:100
object:二回目:50だお
delegate:二回目:50だお
callback:二回目っていう


まとめ

ね、簡単でしょ?

6 件のコメント:

  1. このコメントはブログの管理者によって削除されました。

    返信削除
  2. 私も最近objective-cの勉強始めました。
    javaとの比較とても勉強になりました。
    ありがとうございます。

    返信削除
  3. これはかなり助かりました〜!
    セレクタ部分はJava側も理解できてないので分からないのが悲しいw

    返信削除
  4. さっと理解出来て大変助かりました!

    最近仕事でObjective-C必要になったのですが、めんどくさいし、ホント見た目キモいですよね。


    返信削除
  5. JAVAerですが最近Objective-Cが必要になり、吐き気しながら勉強してました。
    凄く助かりました。ありがとうございます。

    返信削除
  6. 簡潔にまとまっていてとてもわかりやすかったです.
    CやJavaの経験があるとなんとなくとっつきやすいような気がしますねやっぱり.

    返信削除