[Objective-c]インスタンス変数の書き方(第2回 カテゴリーを使う)

[Objective-c]インスタンス変数の書き方
第1回 ヘッダーファイルに書かない
《今回》第2回 カテゴリーを使う
第3回 クラスを継承する


前回、ベースとなるクラスを作成し、「インスタンス変数をヘッダーファイルに書かない」ことを実践しました。
この方法は、Appleも推奨しているらしいぞっということですし、Frameworkを切り出したりする作業の中で、私自身も“良い方法”だと思っています。

ところが、“別の良い方法”である「カテゴリー」を使うと、この両者がぶつかり合うというのが、今回のテーマです。

1.そもそもカテゴリーとは?
「あるクラスにメソッドを追加したい!」という時に真っ先に思いつくのが“継承しろよ”ということなのですが、「継承しなくてもメソッドの追加ができるよ」というのがカテゴリーです。
これは逆に考えると、「大きなクラスを分割できるよ」ということにもなるでしょう。
私は実際に「大きくなりすぎるTableViewのクラス」をカテゴリーを使って分割しています。

  • TableView自体を作成するベースクラス
  • 表示するデータを扱うクラス(delegateも含めて)
  • セルなどをクリックした時のアクション

の3つに分けると、視認性が良くなるメリットがありますし、(コピペも含めて)使い回しが良くなります。
仕様変更なども、影響範囲の絞り込みが容易になるので、気が楽です。

2.(復習)BaseClass.h

@interface BaseClass : NSObject
@property (nonatomic) double BaseNumber;
-(void)setExponent:(double)exponentNumber;
-(double)calculate;
@end

 

3.(復習)BaseClass.m

#import "BaseClass.h"
@implementation BaseClass
{
    double ExponentNumber;
}
@synthesize BaseNumber;
-(void)setExponent:(double)exponentNumber
{
    ExponentNumber = exponentNumber;
}
-(double)calculate
{
    return pow(BaseNumber, ExponentNumber);
}
@end

 

4.(カテゴリーのヘッダーファイル)BaseClassExt.h

#import "BaseClass.h"
@interface BaseClass(Ext)
-(double)calculate2;
@end


ベースとなるクラスのヘッダを「#import」します。
次に「@interface」で「()」をつけて“拡張”します。ベースとなるクラスのinterface名を使わないとカテゴリーになりません。
なお、()の中はユニークな名称であれば何をつけても構いません。“空っぽ”にする方法もあるのですが、今回はその話はしません。
interface内に、“追加したいメソッド”を宣言します。
これで、カテゴリーのヘッダーファイルは終了です。

5.(カテゴリーの実装ファイル)BaseClassExt.m

#import "BaseClassExt.h"
@implementation BaseClass(Ext)
-(double)calculate2
{
    return pow(BaseNumber, ExponentNumber)*log(BaseNumber);
}
@end


importするのは、カテゴリーのヘッダファイルです。
implementetionするのは、カテゴリー宣言したinterfaceです。
宣言したメソッドを書けば、終了です。

ただし!このクラスはエラーが発生します。
以下に、その理由と対策を考えていきます。

6.プロパティを使うには
まず、「Use of undeclared identifier ‘BaseNumber’」と言われます。
「BaseNumberなんて使うって言ってねーだろ」という警告です。

対策は2つあります。
1つ目の対策は、BaseNumberはプロパティ宣言をしていますので、プロパティとして使えば問題ありません。

    return pow([self BaseNumber], ExponentNumber)*log([self BaseNumber]);


これで、BaseNumberに関しては怒られなくなります。
もう1つの対策は、下の方で説明します。

7.インスタンス変数を使うには
6の対策をしたら、次に「Use of undeclared identifier ‘ExponentNumber’」と言われます。
「ExponentNumberなんて使うって言ってねーだろ」という警告です。

ここで、困ります。
BaseNumberはプロパティ宣言をしていたので、“外に存在をアピールしている”わけですから、selfで呼び出せるのです。
しかし、ExponentNumberはわざわざヘッダーファイルから外してしまっているので、カテゴリーからは見えません。

「実装ファイルに書いてあるのを見えるようにしろ」という方法は、色々調べたのですが・・・やはり・・・ないですよね。
(ネット上でも「ヘッダーファイルに書くとOKダヨ」という外人さんのやりとりしかないので、そうなんでしょう。)

そこで、泣く泣く戻します。

8.(カテゴリーから使いたいインスタンス変数を書いた)BaseClass.h

@interface BaseClass : NSObject
{
    double ExponentNumber;
}
@property (nonatomic) double BaseNumber;
-(void)setExponent:(double)exponentNumber;
-(double)calculate;
@end

 

9.(カテゴリーから使いたいインスタンス変数を消した)BaseClass.m

#import "BaseClass.h"
@implementation BaseClass
@synthesize BaseNumber;
-(void)setExponent:(double)exponentNumber
{
    ExponentNumber = exponentNumber;
}
-(double)calculate
{
    return pow(BaseNumber, ExponentNumber);
}
@end

 

これで「Use of undeclared identifier ‘ExponentNumber’」が消えて、ビルドに成功します。

「あーあ」って感じなのですが、「カテゴリーでクラスを拡張したとき、カテゴリーで追加したメソッドが使うインスタンス変数は、ヘッダーファイルに書かなきゃダメ」ということです。ま、そうでしょ。

10.カテゴリーを実行する

#import "BaseClassExt.h"

-(void)doCalc
{
    BaseClass* baseClass = [[BaseClass alloc]init];
    baseClass.BaseNumber = 3;
    [baseClass setExponent:2];
    double dd= [baseClass calculate2];
    NSLog(@"calculate result = %f",dd);
}


importするのはカテゴリーのヘッダーファイルです。
インスタンスはBaseClassですね。BaseClassExtではありません。カテゴリーのメリットはこういう部分にもあります。

11.気になること

    return pow([self BaseNumber], ExponentNumber)*log([self BaseNumber]);


これが、微妙に落ち着かない気がします。

片方はプロパティ、片方はインスタンス変数・・・ですね。
すべての作業を短期間で、かつ自分一人で行うならこれでもいいのですが、半年後に他人が手を加えるとしたら・・・「うん?」となりますよね。
そこで、ヘッダーファイルを遡って「こっちはインスタンス変数ね。で、こっちがプロパティ。」と理解してくれることを期待するのですが、このような“解釈”を求めるのはあまり良いことではありません。

このような場合は、

  • 両方ともインスタンス変数を使えるようにする
  • 両方ともプロパティとする

のどちらかのルールを決めておいた方が良いと思います。
ただし、プロパティとは“外部から触れる”ということを意味しますので注意が必要です。
(ネット上で「何も考えることはないさ、プロパティにすれば解決だぜベイベー」という外人のアドバイスがあったのですが、そういうノリでいいのか?と・・・)

プロパティを使うという選択肢には、重要なメリットがあります。

  • インスタンス変数を持たないプロパティも扱える
  • readonlyにすることで用途を制限できる

これらのメリットは、無視できないくらい素晴らしいものです。

対して、デメリットとしては、

  • 毎回アクセッサ経由なのは負荷が掛かるのでは?
  • そもそも外部からのアクセスを全く考えていない変数もあるでしょう

ということなのですが、「負荷は・・・気にするほどのことではないし、繰り返しアクセスするなら、一旦内部変数に確保すれば良い」ということです。

なので、一般的には「プロパティで」が正しい選択肢のようです。

しかし、“現在私が開発しているアプリに関しては”、切り出されたFrameworkを“純粋に利用するだけ”を目標としていますので、外部からアクセスされるつもりのない変数をプロパティにすると「触っても良いプロパティであるという誤解が生じる」ことを懸念しています。

そこで、私のアプリに関しては「インスタンス変数の定義をちゃんとする」というルールにしています。
前回、「@propertyで暗黙のインスタンス変数の定義をする」と書きましたが、やはり“暗黙”であって、同一クラス内なら確かに定義されていますが、カテゴリーで拡張したところまでは“暗黙”が届かないようです。

そこで、BaseClass.hを変更します。

@interface BaseClass : NSObject
{
    double ExponentNumber;
    double BaseNumber;
}
@property (nonatomic) double BaseNumber;
-(void)setExponent:(double)exponentNumber;
-(double)calculate;
@end	


プロパティと同じ名称でインスタンス変数を定義します。
(こうするとBaseClase.mは変更なし)

これでBaseClassExt.mのメソッドを

-(double)calculate2
{
    return pow(BaseNumber, ExponentNumber)*log(BaseNumber);
}


のように多少シンプルに書けるようになりました。

今回のまとめとしては、

  • カテゴリーを使うときは、ヘッダーファイルにインスタンス変数を書かなきゃだめ
  • 将来のことを考えて、プロパティ宣言だけでなくインスタンス変数もセットで定義しておこうね

ということになります。

何も考えずにインスタンス変数をどんどん実装ファイルに移動したあとで、“使いまくっているカテゴリー”のせいで「エラーが出る箇所を一つ一つロールバックしたよ」というのが、今回の報告のトリガーです。

次回は、「クラスを継承する場合はどないやねん?」をお届けします。

The following two tabs change content below.

ロゴスウェア

ロゴスウェア株式会社は、インターネットや情報技術を使って学習に革新的進化をもたらす製品を開発することを目標に、2001年7月に設立されたテクノロジー系ベンチャー企業です。

Comments are closed.