[iOS 7] アプリ開発入門 クラスを作る

クラスを定義する

いい加減Objective-Cの書き方にも慣れていかねばならないので、クラスの作り方を見てみることで慣れていこうと思います。
入門に調度良い資料をmixiさんが公開してくれているので、そちらの課題を題材に少しずつ見てみます

クラスを作成する

事前準備として、以下はすでにプロジェクトが作成済みであることを想定しています(Single View Application Project)

クラスを作成してみます。

File > New > Fileを選択します
スクリーンショット 2013-10-07 0.53.04

Objective-C Class を選択します
スクリーンショット 2013-10-07 0.55.18

クラス名を入力します
スクリーンショット 2013-10-07 0.59.19

作成場所を決定します。ディレクトリによる階層分けを行う場合等はここで指定するようです
スクリーンショット 2013-10-07 1.02.57

作成が完了すると、ファイルのツリーに新たに作成したファイルが登録されています
スクリーンショット 2013-10-07 1.04.38

ヘッダファイルと実装ファイルのひな形が完了しました。

ヘッダと実装

Objective-Cも通常のC、C++同様にヘッダファイルと実装ファイルが別々に必要なようです。他のクラスから見えるのは原則としてヘッダファイルに記述されているもののみで、実装そのものは隠されます。つまりは外部からは見れません。
C++等のヘッダとは違い、Privateセクション等での区切りがないようなので、基本的にはヘッダに記述されたものは、全てPublic に公開されるようです

インスタンス変数

Objective-Cにおいて、インスタンス変数の指定は複数あるようですが、一番手軽なpropertyを利用するのが良さそうです。
宣言できる箇所は、ヘッダファイルの中、または実装ファイルのinterface定義の中になります

ヘッダファイルの場合

#import <Foundation/Foundation.h>

@interface CmTestStack : NSObject
@property NSString *input;
@end

@interface セクション内に @property というPrefixをつけて宣言します

@property [クラス名] *[変数名];

上記の記述で変数は宣言することができます。基本的にObjective-Cのクラスの変数を宣言する際は必ず「*」をつけます
独自のクラスを宣言する場合は、先頭の#importセクションにヘッダファイルをインポートすることをお忘れずに

この@propertyを利用することで、コンパイラは勝手に[setter], [getter], と[_]付きの変数を自動的に保管してくれます。
そのため、自分自身でsetterやgetterを用意する必要がありません。さらにpropertyは、dot syntaxでアクセスできるため、より直感的にアクセスが可能になっています

// 自分自身のクラスのインスタンス変数「input」のgetterを呼び出したのと同等。inputの文字列をConsoleに出力する
NSLog(@"%@", self.input);

インスタンスメソッド

メソッドの宣言は以下のように行うようです

CmTestStack.h

#import <Foundation/Foundation.h>

@interface CmTestStack : NSObject

@property NSString *input;

// 引数なし
- (void)sampleMethod;

// 引数1つ
- (void)sampleMethod :(NSString *)str1;

// 引数2つ
- (void)sampleMethod :(NSString *)str1 :(NSString *)str2;

@end

こちらも@interface 内に宣言します。記述の形式は

- (戻りの型) メソッド名 :(クラス名 *)引数名1 :(クラス名 *)引数名2 ...

[-] で始まるメソッドはインスタンスメソッドとなるため、インスタンス化していないと利用できません。
[+] で始まるメソッドはクラスメソッドとなるようです。

ヘッダファイルにメソッドを宣言し、実装ファイルにはまだそのメソッドを実装していないとこのような警告が出力されます
スクリーンショット 2013-10-07 2.01.17

この警告が出力された時はおとなしく実装ファイルにメソッドを実装してあげましょう。補完されるのが非常に便利ですね
スクリーンショット 2013-10-07 2.03.05

CmTestStack.m

- (void)sampleMethod {
    
}
- (void)sampleMethod:(NSString *)str1 {
    
}
- (void)sampleMethod:(NSString *)str1 :(NSString *)str2 {
    
}

Queueのクラスを作成する

mixiがGithubにて公開しているiOS Trainingの課題を自分なりに解いてみようと思います。Queue機能を持つ「TestQueue」というクラスを作成することを目指します

クラスを作成する

先ほど確認した手順を実行し「CmTestQueue」というクラスをNSObjectを継承して作成します。ヘッダファイルには課題で指定された通り、「push」「pop」「size」のメソッドを宣言します。

CmTestQueue.h

#import <Foundation/Foundation.h>

@interface CmTestQueue : NSObject

// Push Object to Last position at Queue
- (void)push:(id)object;

// Get First Object from Queue
- (id)pop;

// Get Queue Size
- (NSInteger)size;
@end

実装ファイルを見てみましょう。メソッドを実装していないと警告を出力されてしまうので、空のメソッドを作成しておきます。
返り値がある場合はとりあえずnilを返しておきましょう

CmTestQueue.m

-(void)push:(id)object
{
   
}

- (id)pop
{
    return nil;
}

- (NSInteger)size
{
    return 0;
}

追加されたObjectを保存する変数を追加

課題には NSMutableArray を利用せよ、という指示があるので変数を定義します。先ほどの手順にしたがって、@propertyを利用して変数を宣言します。
はて、ここで疑問に思うですが、このままヘッダファイルに変数を宣言してしまうと外部から丸見えです。Queueクラスの中身が実は単なるArrayだということがバレてしまうし、勝手にアクセスされたりするのでよろしくありません。ぜひとも外部から隠してしまいたいところです。

外部からはヘッダファイルの宣言のみ見えるため、実装ファイルの中に定義してしまえば見えないとのことなので、実装ファイルの中に宣言しましょう

CmTestQueue.m

#import "CmTestQueue.h"

@interface CmTestQueue ()
// Private Variable
@property NSMutableArray *queue;
@end

@implementation CmTestQueue
...
@end

実装ファイル内にもinterfaceを宣言してしまいます。このセクション内に変数を宣言すれば外から見えてしまうことはなくなるようです

初期化

ここは半分覚える事柄のようです。initというメソッド内でNSMutableArrayの初期化を行います

- (id)init
{
    self = [super init];
    if(self) {
        // Initialize Queue
        self.queue = [NSMutableArray array];
    }
    
    return self;
}

メソッドの詳細を実装

Queueの動きは「First In First Out」 です
そのため、Objectの挿入は常に最後尾。Objectの取得は常に先頭から取得するような機能を追加します

まずはObjectの挿入

- (void)push:(id)object
{
    // Add Object to Queue Last Position
    [self.queue addObject:object];
}

少し記述が慣れていませんが、self.queueというのは、先頭で宣言したNSMutableArrayの変数です。
NSMutableArrayに定義されているインスタンスメソッドaddObjectを、引数に「object」を渡して呼び出しています。まだ自分自身このメッセージという概念に慣れていませんが、Objective-Cではこのような形でメソッドを呼び出すようです

Objectの取得

- (id)pop
{
    // id型の時はアスタリスクいらない
    id object = nil;
    if(self.queue.count > 0) {
        object = [self.queue objectAtIndex:0];
        // 取り出したobjectを削除する
        [self.queue removeObjectAtIndex:0];
    }
    return object;
}

Objectの取得は、取得だけでなく取得したObjectの削除も行う必要があります。そして、返り値に「id」を返します。
これはObject自身のIDを示し、Objectを一意に判別するものだそうです。

サイズの取得

- (NSInteger)size
{
    // 引数なしメソッドはドットシンタックスでアクセス可
    return [self.queue count];
}

引数がない場合は、dot syntaxでのアクセスも可能だそうです。つまりはこれでも可

- (NSInteger)size
{
    // 引数なしメソッドはドットシンタックスでアクセス可
}

これでQueueクラスの作成は完了しました。動きを確かめるために簡単なGUIを作成してみます

動作確認のための簡易UIの作成

ストーリーボードで画面を作成します
スクリーンショット_2013-10-07_2.37.15

Inputした文字列をコード内で取得するために、画面のUI Componentを変数に結びつけてあげる必要があります。

Ctrlキーを押しながら、ViewControllerの実装クラスへドラッグします。名前をつけるダイアログが表示されるので入力します。これでUI Componentと変数が結びつきます。
スクリーンショット_2013-10-07_2.41.30

@property (weak, nonatomic) IBOutlet UITextField *inputObject;
@property (weak, nonatomic) IBOutlet UITextField *outputSize;
@property (weak, nonatomic) IBOutlet UITextField *outputObject;

ViewController内で、CmTestQueueを呼び出したいのでヘッダファイルをimportします

#import "CmViewController.h"
#import "CmTestQueue.h"

@interface CmViewController ()
@property CmTestQueue *queue;
...
@end

CmTestQueueの初期化処理を記述します。場所はViewControllerの「- (void)viewDidLoad」内です

// Queueの初期化
self.queue = [[CmTestQueue alloc] init];

Pushボタンを押したら、CmTestQueueのpushを呼び出すように、Popボタンを押したら、CmTestQueueのpopを呼び出すようにしてみます。同じくボタンをCtrlキーを押しながらViewControllerへドラッグし、ボタンのPushされた時のイベントハンドラを作成します
スクリーンショット_2013-10-07_2.51.57

ここに中の処理を実装していきます。

Pushの動作は、Inputに記述されたTextをQueueに保存した後、Queueのサイズを出力するようにしてみます

NSString *str = self.inputObject.text;
if(str && str.length > 0) {
    [self.queue push:str];
}

self.outputSize.text = [NSString stringWithFormat:@"%d", self.queue.size];

Popの動作は、Queueから取り出したObjectを表示するとともに、取り出した後のQueueのサイズを出力するようにしてみます

NSString *obj =  (NSString *)[self.queue pop];
self.outputObject.text = obj;

self.outputSize.text = [NSString stringWithFormat:@"%d", self.queue.size];

これでひと通りのCmTestQueueの機能を使った簡易UIが完成しました。実行してみます

実行結果

スクリーンショット 2013-10-07 2.58.43

スクリーンショット 2013-10-07 2.59.29

キーボードを閉じるにはまた一工夫必要だそうです。今回は簡易UIということでご勘弁を・・・

所感

クラスを定義して実際に使ってみるところまでやってみました。元々C++(MFC)を少しだけ経験していたところもあるため、そこまで違和感はありませんでしたが、やはりプログラムの記述に関しては色々と独自の文化が多いように感じました。
ただ、正直なところ同じ目的に対して複数の記述方法があるというのは、初心者にとっては困る点かと思われます。結局どれが良いのだろうか、と迷ってしまうところでもあり、これが正しいと思うものを探すのがなかなか大変ですね

参照