[Objective-C] 任意のクラスのサブクラスをランタイムで列挙する方法

objective-c

プラグイン的なアーキテクチャでファクトリ部が肥大化する問題

複数種類のファイルを取り扱うようなアプリを思い浮かべてみて下さい。
対応するファイル形式を柔軟に増やせるようなアーキテクチャを、Objective-Cではどう構築するのが良いでしょう?
各形式を取り扱うモジュールがあって、それらは定型化された仕様に則って振る舞うような一般的なsubclassingモデルと、ファクトリパターンが良さそうです。

しかし、取り扱える種類が増えると、モジュールを統括する部分(ファクトリ部)の実装が肥大化する問題が発生します。
例えば、指定した拡張子に対応するクラスのインスタンスを返す関数とか。
更に、新しい形式を追加した時に、ファクトリ部への追加実装の手間も後々に負担となったり、実装漏れが生ずる可能性があります。

モジュールをランタイムで列挙してスッキリさせる

ファクトリ部をスッキリさせるには、通常のsubclassingモデルよりももう少し粗結合な実装方法を検討してみると解決するかもしれません!
それは、基底クラスのサブクラスの列挙部をハードコーディングするのではなく、ランタイムで列挙する方法です。
サブクラスに、ファクトリ部で取り扱いが必要な情報を提供するGetterメソッドやプロパティを持たせます。

これなら、新モジュールの登録忘れや一貫性を崩す心配を軽減できます!

ランタイムAPIを使って実現

NSObjectに対してメソッドを追加するカテゴリを紹介します。
追加されるメソッドの+ classNamesForSubclasses を、あるクラスをレシーバにして呼び出すと、その子クラスの名前がNSArrayで返ります。
チョー簡単ですね!

ヘッダファイル(NSObject+AutomaticFactory.h):

#!objectivec
#import <Foundation/Foundation.h>

@interface NSObject (AutomaticFactory)

+ (NSArray*) classNamesForSubclasses;

@end

ソースファイル(NSObject+AutomaticFactory.m):

#!objectivec

#import "NSObject+AutomaticFactory.h"
#import <objc/runtime.h>

@implementation NSObject (AutomaticFactory)

+ (NSArray*) classNamesForSubclasses;
{

    int numClasses = 0, newNumClasses = objc_getClassList(NULL, 0);
    Class *classList = NULL;

    while (numClasses < newNumClasses) {
        numClasses = newNumClasses;
        classList = (Class*)realloc(classList, sizeof(Class) * numClasses);
        newNumClasses = objc_getClassList(classList, numClasses);
    }

    NSMutableArray *classesArray = [NSMutableArray array];

    for (int i = 0; i < numClasses; i++) {
        Class superClass = classList[i];
        do {
            // recursively walk the inheritance hierarchy
            superClass = class_getSuperclass(superClass);
            if (superClass == [self class]) {
                [classesArray addObject:NSStringFromClass(classList[i])];
                break;
            }
        } while (superClass);
    }

    free(classList);

    return classesArray;
}
@end

プラグイン的な実装をしている場合は、検討してみてはいかがでしょうか?

参考ページ: Automagic Factories in Objective-C

[XCode] 見づらいコードをショートカットキー一発で綺麗に整形する方法

xcode

コードの整形は手間がかかる

代入文って10行とか20行とかになると、一気に見づらくなりますよね。
特に、Interface Builder使わないでコードでUIを組み立てたりしてると、プロパティの設定処理で平気で100行になります。
例えばこんなコード・・

#!objectivec
cell.backgroundView.layer.shadowColor = [UIColor blackColor].CGColor;
cell.backgroundView.layer.shadowOffset = CGSizeMake(0, 4);
cell.backgroundView.layer.shadowOpacity = 0.8;
cell.backgroundView.layer.shadowRadius = 10;
cell.backgroundView.layer.masksToBounds = NO;
cell.backgroundView.layer.shouldRasterize = YES;
cell.backgroundView.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.backgroundView.layer.contentsScale = [[UIScreen mainScreen] scale];
cell.selectedBackgroundView.layer.shadowColor = [UIColor blackColor].CGColor;
cell.selectedBackgroundView.layer.shadowOffset = CGSizeMake(0, 4);
cell.selectedBackgroundView.layer.shadowOpacity = 0.8;
cell.selectedBackgroundView.layer.shadowRadius = 10;
cell.selectedBackgroundView.layer.masksToBounds = NO;
cell.selectedBackgroundView.layer.shouldRasterize = YES;
cell.selectedBackgroundView.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.selectedBackgroundView.layer.contentsScale = [[UIScreen mainScreen] scale];

はい、とっても汚いですね。理想は・・

#!objectivec
cell.backgroundView.layer.shadowColor                = [UIColor blackColor].CGColor;
cell.backgroundView.layer.shadowOffset               = CGSizeMake(0, 4);
cell.backgroundView.layer.shadowOpacity              = 0.8;
cell.backgroundView.layer.shadowRadius               = 10;
cell.backgroundView.layer.masksToBounds              = NO;
cell.backgroundView.layer.shouldRasterize            = YES;
cell.backgroundView.layer.rasterizationScale         = [UIScreen mainScreen].scale;
cell.backgroundView.layer.contentsScale              = [[UIScreen mainScreen] scale];
cell.selectedBackgroundView.layer.shadowColor        = [UIColor blackColor].CGColor;
cell.selectedBackgroundView.layer.shadowOffset       = CGSizeMake(0, 4);
cell.selectedBackgroundView.layer.shadowOpacity      = 0.8;
cell.selectedBackgroundView.layer.shadowRadius       = 10;
cell.selectedBackgroundView.layer.masksToBounds      = NO;
cell.selectedBackgroundView.layer.shouldRasterize    = YES;
cell.selectedBackgroundView.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.selectedBackgroundView.layer.contentsScale      = [[UIScreen mainScreen] scale];

非常に綺麗です。
でも、スペース打つの面倒くさい。。

そこで、上記のようなコードの整形をショートカットキー一発で出来るXCodeプラグインをご紹介します!

XCodeプラグイン「XAlign」が便利

XAlignは、文字通りAligningを手伝ってくれるプラグインです。

インストール方法

コマンド一発で即完了。

#!bash
$ curl github.so/XAlign/build/install.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   896  100   896    0     0   1805      0 --:--:-- --:--:-- --:--:--  1851
Password:
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 34311  100 34311    0     0  52104      0 --:--:-- --:--:-- --:--:-- 52543

XAlign is installed. Please Restart Your Xcode.

More info: https://github.com/qfish/XAlign/

To uninstall XAlign, `curl github.so/XAlign/build/uninstall.sh | sh`
.

XCodeを再起動しましょう。

整形の実行

以下のデモンストレーションのように、「シフト+?+X」で自動整形を実行します。

XAlign

すごいですねww 今までの苦労は一体ww
#defineプリプロセッサ文や、@property構文などにも有効なようです。

みなさんもぜひ試してみてください!