Objective-C 入門
Objective-CはC言語とのハイブリッド言語であるため、ここではC言語の説明は極力省き、Objective-C特有の構文や仕組みを説明します。
なお、本記事は言語経験のある方がスムーズにObjective-Cの概要を掴めるよう構成されています。もちろんプログラミング初心者であってもObjective-Cの雰囲気は十分感じられる内容なのではないかと思います。Objective-Cの入門記事やチュートリアルとしても活用できます。
目次
Objective-Cとは
Objective-CはC言語にオブジェクト指向のシステムを取り入れたプログラミング言語です。そのためC言語に対して上位互換性を持つという特徴があります。よってC言語で書かれたコードやライブラリも、Objective-C上で活用することが可能です。
現在はCocoa/Cocoa Touchフレームワークと共に、macOSやiOS向けのアプリケーション開発で利用されています。
出力
C言語標準のprintf関数を用いる方法とObjective-Cのログ関数NSLogを用いる方法の2種類があります。
printf("%s %d", "Shop", 99); // "Shop 99"
NSLog(@"%@ %d", @"Shop", 99); // "Shop 99"
NSLogで出力された内容はデバイス側(Mac, iPhone等)のログファイルにも書き出されますので注意してください。
/var/log/system.log
12/21/16 19:56:46.519 a.out[3718]: Shop 99
値・変数
Objective-CはC言語とのハイブリッドな言語であるため、C言語由来のプリミティブ型(基本型)とObjective-C規格のオブジェクト型の2種類の型が存在します。
プリミティブ型
NSInteger val = 1; // 整数型
CGFloat pi = 3.14; // 実数型
BOOL flag = YES; // 論理型
const char *chars = "Shop"; // 文字列ポインタ
printf("%s %.2lf", chars, val + pi); // Shop 4.14
NSInteger
Objective-Cではint型やlong型の代わりにNSInteger型が使われています。NSIntegerは基本的にlong型の別名として定義されています(32bit OS環境ではint型の別名)。
CGFloat、BOOL、ポインタ
CGFloatはdouble型の代替型です(32bit環境ではfloat型の代替)。 boolean型(true/false)の代替としてはBOOL型(YES/NO)が使われています。 char *等のポインタ型を扱うこともできます。
オブジェクト型
Objective-Cのオブジェクトを利用する際には、値の先頭に@
記号を付けて宣言します。
また、オブジェクトはポインタとして扱う必要があるため、変数名の先頭には*
記号を付ける必要があります。
NSNumber *num = @1;
NSNumber *pi = @3.14;
NSNumber *flag = @YES;
NSString *str = @"Shop";
NSInteger v = [num integerValue];
NSLog(@"%@ %lf", str, v + pi.doubleValue); // Shop 4.14
メソッド呼び出し
ここで作成したNSNumberのオブジェクトnum
はプリミティブ型(longやdouble)のラッパーであるため、実際にそれらの値を取得するためにNSNumberオブジェクトのメソッドを呼び出す必要があります。
ここではnumオブジェクトの値を[num integerValue]
という形式で取得しています。[num doubleValue]
と記述すればdouble型として値を取得することも出来ます。
プロパティ呼び出し
サンプルコードにあるpi.doubleValue
という記述は[pi doubleValue]
と同じ意味です。引数なしのメソッドに関してはこのドット記法によるメソッド呼び出しが可能となっており、後に説明する「プロパティ」の仕組みとも深く関係します。
このドット記法は結局のところ同名のメソッドを呼び出すだけです。
このドット記法による呼び出しはプロパティアクセスと呼ばれることもありますがあまり一般的な呼び方ではないかもしれません。
引数を持つメソッドの呼び出し
NSNumber *num = @1;
// 従来の引数無し
[num doubleValue]; // 1.0
[num stringValue]; // "1"
// 引数あり
[num isEqualToNumber:@1]; // true
[num isEqualToNumber:@2]; // false
引数を受け取るメソッドはこのように[オブジェクト メソッド名:引数]
という形式で呼び出します。
引数を複数持つメソッドの呼び出し
// 引数無し
[@"2" doubleValue]; // 2.0
// 第1引数のみ
[@"a" isEqualToString:@"A"]; // false
// 第2引数あり
[@"AbC" stringByReplacingOccurrencesOfString:@"b" withString:@"B"]; // "ABC"
第2引数を受け取るメソッドは上記サンプルのように[オブジェクト メソッド名:第1引数 ラベル名:第2引数]
の形式で呼び出します。
ちなみにstringByReplacingOccurrencesOfString
メソッドはJava言語で言うところのreplace
メソッドに相当する物です。
// Java
"AbC".replace("b", "B")
// Objective-C
[@"AbC" stringByReplacingOccurrencesOfString:@"b" withString:@"B"]
文字列
/* 生成 */
NSString *str = @"こんにちは世界";
str = [NSString stringWithUTF8String:"Shop 99"]; // "Shop 99"
str = [NSString stringWithFormat:@"%@ %@", @"こんにちは", str]; // "こんにちは Shop 99"
/* 長さ */
[@"Shop 99" length]; // 7
/* 比較 */
[@"Shop 99" isEqualToString:@"Shop 99"]; // true
[@"Shop 99" hasSuffix:@"99"]; // true
/* 結合 */
[@"Shop" stringByAppendingString:@" 99"]; // "Shop 99"
[NSString stringWithFormat:@"%@ %@", @"Shop", @"99"]; // "Shop 99"
/* 分割 */
[@"Shop 99" componentsSeparatedByString:@" "]; // ["Shop", "99"]
/* 検索 */
[@"Shop 99" containsString:@"Shop"]; // true
[@"Shop 99" rangeOfString:@"99"]; // {5, 2}
/* 置き換え */
[@"Shop 99" stringByReplacingOccurrencesOfString:@"99" withString:@"108"]; // "Shop 108"
/* 切り取り */
[@"Shop 99" substringFromIndex:5]; // "99"
[@"Shop 99" substringToIndex:4]; // "Shop"
[@"Shop 99" substringWithRange:NSMakeRange(2, 2)]; // "op"
可変長文字列
Objective-Cの基本型はイミュータブル(不変的)なものがほとんどです。つまり一度生成したオブジェクトの内容を変化させることは出来ません。
NSStringも同様にイミュータブル型であるため、代わりの型を使用する必要があります。
文字列の内容を動的に変化させたい場合はNSMutableStringというミュータブル(変動的)な型を使用します。
NSMutableString *ms = [NSMutableString string];
[ms appendString:@"9"]; // "9"
[ms appendString:@"8"]; // "98"
[ms insertString:@"Shop " atIndex:0]; // "Shop 98"
配列
NSStringやNSNumberの時と同様に@記号を配列リテラルの先頭に記述します。
/* 生成 */
NSArray *ary = @[@"Shop", @"99"];
/* 長さ */
[ary count]; // 2
/* アクセス */
ary[0]; // "Shop"
[ary objectAtIndex:1]; // "99"
[ary firstObject]; // "Shop"
[ary lastObject]; // "99"
/* 探索 */
[ary containsObject:@"99"]; // true
[ary containsObject:@"100"]; // false
[ary indexOfObject:@"99"]; // 1
/* 結合 */
[ary arrayByAddingObjectsFromArray:@[@"DAISO", @"100"]]; // ["Shop", "99", "DAISO", "100"]
可変長配列
NSString同様NSArrayにもミュータブル版のNSMutableArrayが存在します。
NSMutableArray *list = NSMutableArray.array;
[list addObject:@"Shop"]; // ["Shop"]
[list addObject:@"99"]; // ["Shop", "99"]
連想配列
Objective-Cの世界では連想配列のことを辞書型と呼びます。
/* 生成 */
NSDictionary *dic = @{@"name": @"Shop 99", @"price": @99};
/* 長さ */
[dic count]; // 2
/* アクセス */
dic[@"name"]; // "Shop 99"
dic[@"price"]; // 99
dic.allKeys; // ["name", "price"]
dic.allValues; // ["Shop 99", 99]
/* 探索 */
[dic.allValues containsObject:@99]; // true
可変連想配列
NSMutableDictionary *dic = NSMutableDictionary.dictionary;
dic[@"name"] = @"Shop 99"; // {name: "Shop 99"}
dic[@"price"] = @99; // {name: "Shop 99", price: 99}
[dic removeObjectForKey:@"price"]; // {name: "Shop 99"}
条件分岐
if (条件式) { 文1; } else { 文2; }
の形式で記述します。
else
の後にif文を繋げることで複数条件の判断も可能です。
NSInteger val = 99;
if (val == 99) {
printf("Shop 99");
} else if (val == 108) {
printf("DAISO");
} else {
printf("unknown");
}
Java言語やSwift言語とは違い、if文の条件式の値が0以上となる場合はtrue式として解釈されます。
NSInteger val = 99;
if (val) {
printf("true"); // valの値が0以上の場合表示
} else {
printf("false"); // valの値が0の場合表示
}
オブジェクトが空(nil)の場合は0と同じ意味になるため、true式とはなりません。
NSString *str = nil;
if (str) {
print("true"); // strはnilのため表示されない
}
nilオブジェクトに対するメソッド呼び出し
Objective-Cの世界ではnilが代入されたオブジェクトに対して、メソッド呼び出しを行うことができます。
エラーは発生せず、代わりにメソッドの戻り値型に応じて、nilと等価な値(0, NULL, false, NO, 等々)が返されます。
NSString *str = nil;
if (str.length == 0) {
printf("文字列の長さが0もしくはnilである可能性があります。");
}
繰り返し文
C言語スタイルfor文の他に高速列挙と呼ばれるObjective-Cオブジェクト専用の仕組みが存在します。
/* C言語スタイル for */
for (NSInteger i = 0; i < 3; i++) {
printf("Shop 99"); // 3回 "Shop 99"が表示される
}
/* while文も使える */
int i = 3;
while (i--) {
printf("Shop 99"); // 3回 "Shop 99"が表示される
}
/* Objective-C 高速列挙 */
NSArray *ary = @[@"Shop 99", @"DAISO"];
for (NSString *shop in ary) {
NSLog(@"%@", shop); // "Shop 99", "DAISO"の順で表示される
}
/* Objective-C 高速列挙 辞書型の場合はキーが渡される */
NSDictionary *dic = @{@"name": @"Shop 99", @"price": @99};
for (NSString *key in dic) {
NSLog(@"%@: %@", key, dic[key]); // "name: Shop 99", "price: 99"の順で表示
}
クラス
クラス作成
ヘッダーファイルShop.h
と実装ファイルShop.m
の2つのファイルを作成する必要があります。
Shop.h
#import <Foundation/Foundation.h>
@interface Shop : NSObject
// ここにプロパティやメソッドを宣言する
@end
Shop.m
#import "Shop.h"
@implementation Shop
// ここでメソッドを実装する
@end
メソッド実装
メソッドの宣言は- (戻り値)名前;
という形式で行います。
+ (戻り値)名前;
という形式で宣言した場合はクラスメソッドとなります。
Shop.h
#import <Foundation/Foundation.h>
@interface Shop : NSObject
- (NSString *)name; // インスタンスメソッド
- (NSString *)test; // インスタンスメソッド
+ (NSString *)name; // クラスメソッド
@end
メソッドの定義は実装ファイル側で行います。
Shop.m
#import "Shop.h"
@implementation Shop
- (NSString *)name { /* インスタンスメソッド */
return @"Hello 99";
}
- (NSString *)test {
return [self name]; // self構文で自身のメソッドにアクセス
}
+ (NSString *)name { /* クラスメソッド */
return @"Shop 108";
}
+ (void)load {
Shop *hoge = [Shop new]; // オブジェクト生成
NSLog(@"%@", [hoge name]); // @"Shop 99" // メソッド呼び出し
NSLog(@"%@", [hoge test]); // @"Shop 99"
NSLog(@"%@", [Shop name]); // @"Shop 108" // クラスメソッド呼び出し
}
@end
クラスからオブジェクト(インスタンス)を生成する際にはShopクラスのnew
メソッドを呼び出します。
メソッドの呼び出しは[オブジェクト メソッド名]
、
クラス・メソッドの実行は[クラス メソッド名]
の形式で行います。
メソッド内から自身のメソッドにアクセスする際には自身のインスタンスを表すself
を使用します。
Objective-Cの世界ではメソッド呼び出し時の式をメッセージ式と呼びます。
プロパティ・インスタンス変数
プロパティはヘッダーファイル側に@property
構文を用いて宣言します。
インスタンス変数の場合はブレース({}
)内に記述する必要があります。
Shop.h
#import <Foundation/Foundation.h>
@interface Shop : NSObject {
NSNumber *_price; // インスタンス変数
}
@property (nonatomic) NSString *name; // プロパティ
@end
Shop.m
#import "Shop.h"
@implementation Shop
+ (void)load {
Shop *shop = [Shop new];
shop.name = @"Shop 99";
shop->_price = @99;
NSLog(@"%@%d", shop.name, shop->_price.intValue); // "Shop 9999"
NSLog(@"%@", [shop test]); // "Shop 99Shop 99"
}
- (NSString *)test {
NSString *n1 = self.name; // self構文で自身のプロパティにアクセス
NSString *n2 = _name; // nameプロパティが保有するインスタンス変数に直接アクセス
return [n1 stringByAppendingString:n2];
}
@end
プロパティ・インスタンス変数へのアクセス
プロパティへのアクセスはオブジェクト.プロパティ名
、インスタンス変数へのアクセスはオブジェクト->インスタンス変数名
形式で行います。
メソッド内から自身のプロパティにアクセスする際には自身のインスタンスを表すself
を使用します。
ちなみに_name
という見慣れない変数は@property構文がnameプロパティ用に自動作成したインスタンス変数です。また、クラス自身のメソッドからであれば、self->_name
形式でのアクセスは不要です。
@property構文は_nameインスタンス変数とそれを戻り値として返すnameメソッドを自動生成しているのです。ちなみに以前、プロパティ呼び出しは単に同名のメソッドを呼び出しているだけだという趣旨の説明をしましたが。あれはまさにこの@propertyの仕組みを実現するための仕様でもあります。
メソッド引数
引数を受け取るメソッドは- (戻値型)メソッド名:(引数型)引数名;
という形式で記述します。
@implementation Shop {
NSString *_name; // インスタンス変数は実装ファイル側でも宣言できる
}
- (void)setName:(NSString *)name {
_name = name;
}
- (NSString *)name { return _name; }
+ (void)load {
Shop *shop = [Shop new];
[shop setName: @"DAISO"];
NSLog(@"%@", [shop name]); // "DAISO"
shop.name = @"Shop 99"; // プロパティ形式による代入も可能(setName:が呼ばれる)
NSLog(@"%@", [shop name]); // "Shop 99"
}
@end
今回サンプルとして実装したメソッドはヘッダーファイルにnameプロパティ(@property (nonatomic) NSString *name;
)を宣言した際に自動生成されるメソッドと同等のものです。
Objective-C 2.0以前は@property構文がなかったため、このように手作業でアクセサ・メソッドを実装する必要がありました。
メソッド引数(複数)
複数の引数を受け取るメソッドは- (戻り値)メソッド名:(型)引数名1 ラベル名1:(型)引数名2;
という形式で記述します。
メソッドの呼び出しは[オブジェクト メソッド名:引数1 ラベル名1:引数2]
という形式で行います。
@implementation Shop {
NSString *_name;
NSNumber *_price;
}
- (void)setName:(NSString *)name price:(NSNumber *)price {
_name = name;
_price = price;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %@", _name, _price];
}
+ (void)load {
Shop *shop = [Shop new];
[shop setName:@"Shop" price:@(99)];
NSLog(@"%@", shop); // "Shop 99"
}
@end
descriptionメソッド
上記サンプルのdescription
という見慣れないメソッドは、Java/JavaScriptの世界で言うtoString
メソッドのようなものです。
NSLogのようなフォーマット関数等にオブジェクトを渡すと、オブジェクトの文字列化を行うために、関数内部でdescriptionメソッドが強制的に呼ばれます。
実際、今回のNSLog(@"%@", shop);
でも、NSLog関数内部で[shop description]
呼び出し処理が行われています。
関数
C言語のネイティブ関数を作成することも出来ます。 Objective-Cのメソッドよりも高速で呼び出されるという特徴があります。
Objective-Cの実装ファイルに記述する際にはstatic
キーワードを付けた状態で宣言しましょう。
複数の実装ファイルで同名の関数が宣言されていても、名前衝突が起こらず、別関数として定義されます。
static NSNumber *shop() {
return @99;
}
またインライン関数を宣言することも可能です。この場合shop関数呼び出し箇所ではマクロと同等のインライン展開が行われるようになります。マクロが必要なケースでは極力インライン関数で代用するようにすると良いでしょう。
NS_INLINE NSNumber *shop() {
return @99;
}