Help us understand the problem. What is going on with this article?

Objective-CでAOP (アスペクト指向) ができるライブラリ

More than 5 years have passed since last update.

Aspects

Objective-Cでインターセプター入れて横断的に処理入れてみたいなことってしたいなと思う場面はあったけど、共通処理を行うクラスを使ってそれを継承するみたいなやり方してた。共通の処理ってどんなものがあるかといえば、例えば、ログ出力とか、GAのトラッキングの送信とかですね。

JavaとかだとFrameworkでだいたい用意されててObjective-Cでもあったらなーとは思ってたけど、今回、AOPができるめっちゃいいライブラリを知ったので紹介します。

このライブラリはEvernoteやDropboxでも使われているPDFのライブラリ PSPDFKit の作者である @steipeteさんが作ったライブラリなので、信頼もおけます。

しかも、ライブラリの中身はAspects.hAspects.mだけだからすごく軽量です。

使い方は、READMEで十分分かりやすいですが、一応試してみたので、紹介します。
(Aspects ver 1.4.1 2014/5/21時点)

使い方

インストールは、cocoapodsに登録されてるので簡単。それか実際のファイル自体は、Aspects.hAspects.mの2つだからそれだけ持ってきてもOK。

例えば、AppDelegate.mapplication:didFinishLaunchingWithOptions:あたりで、登録する。こんな感じ。

AppDelegate.m
#import "AppDelegate.h"
#import "Aspects.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSError *error = nil;
    [UIViewController aspect_hookSelector:@selector(viewWillAppear:)
                              withOptions:AspectPositionBefore
                               usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
                                   UIViewController *vc = [aspectInfo instance];
                                   NSArray *args = [aspectInfo arguments];

                                   NSLog(@"viewWillAppearが呼ばれる前にインターセプト");
                                   NSLog(@"呼ばれたインスタンス:%@", vc);
                                   NSLog(@"呼ばれたメソッドの引数の数:%ld", [args count]);
                                   NSLog(@"配列の中身はボクシングされている:%@", [[args firstObject] class]);
                                   NSLog(@"引数が分かっていれば直接ブロックの引数でも取得可能:%@", animated ? @"YES":@"NO");
                               }
                                    error:&error];
    return YES;
}

実行すると、こんな感じで全てのUIViewControllerviewWillAppear:が呼ばれるところでログが出力される。

2014-05-22 01:04:36.524 AspectsSample[26879:60b] viewWillAppearが呼ばれる前にインターセプト
2014-05-22 01:04:36.530 AspectsSample[26879:60b] 呼ばれたインスタンス:<UIViewController: 0x109273210>
2014-05-22 01:04:36.533 AspectsSample[26879:60b] 呼ばれたメソッドの引数の数:1
2014-05-22 01:04:36.533 AspectsSample[26879:60b] 配列の中身はボクシングされている:__NSCFBoolean
2014-05-22 01:04:36.535 AspectsSample[26879:60b] 引数が分かっていれば直接ブロックの引数でも取得可能:NO

まぁ、これくらいだと、別に基底クラス作って継承すれば、まぁいいんだけど次のような構成になったとたん破滅します。

こんな風に、途中でUIViewController以外のUITableViewControllerなんかが入ってくると、BaseViewControllerUIViewControllerのサブクラスなので、ThirdTableViewControllerBaseViewControllerを継承できない!
どうしよう、基底クラスがまた増えちゃう。。

こんなときにUIViewControllerクラス自体に、インターセプタ入れれば、わざわざ基底クラスなんて作らなくてもOKになります。すごく便利ですね!

Aspectsなら、最後のThirdTableViewControllerのviewWillAppear:も引っ掛けられます。結果はこんな感じ。

2014-05-22 01:12:15.521 AspectsSample[27046:60b] viewWillAppearが呼ばれる前にインターセプト
2014-05-22 01:12:15.547 AspectsSample[27046:60b] 呼ばれたインスタンス:<UINavigationController: 0x109278b90>

2014-05-22 01:12:15.579 AspectsSample[27046:60b] viewWillAppearが呼ばれる前にインターセプト
2014-05-22 01:12:15.580 AspectsSample[27046:60b] 呼ばれたインスタンス:<RootViewController: 0x109279210>

2014-05-22 01:12:30.231 AspectsSample[27046:60b] viewWillAppearが呼ばれる前にインターセプト
2014-05-22 01:12:30.232 AspectsSample[27046:60b] 呼ばれたインスタンス:<SecondViewController: 0x1092a7660>

2014-05-22 01:12:31.705 AspectsSample[27046:60b] viewWillAppearが呼ばれる前にインターセプト
2014-05-22 01:12:31.706 AspectsSample[27046:60b] 呼ばれたインスタンス:<ThirdTableViewController: 0x1092ac880>

一つ想定外でしたが、UIWindowrootViewControllerとして設定してるUINavigationControllerUIViewControllerのサブクラスだからログが出力されてますね。

応用編

今回は、クラスに対してインターセプタを入れましたが、インスタンスに対しても設定できます。

Hogeインスタンスのfugaプロパティに設定した値がいつのまにか上書きされてしまってなぜだー!!みたいなときも、これで、setFuga:にインターセプタを入れておけば、上書きされる場所がデバッグできますね。

RootViewController.m
#import "RootViewController.h"
#import "Hoge.h"
#import "Aspects.h"

@interface RootViewController ()

@property (nonatomic) Hoge *hoge;

@end

@implementation RootViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.hoge = [[Hoge alloc] init];
    [self.hoge aspect_hookSelector:@selector(setFuga:)
                       withOptions:AspectPositionAfter
                        usingBlock:^(id<AspectInfo> info, NSString *fuga) {
                            Hoge *hoge = [info instance];
                            NSLog(@"setFuge:が呼ばれたときのスタックトレース");
                            NSLog(@"%@", [NSThread callStackSymbols]);
                            NSLog(@"インスタンス:%@", hoge);
                            NSLog(@"引数:%@", fuga);
                        }
                             error:nil];
    self.hoge.fuga = @"hogehoge";
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.hoge.fuga = @"fugafuga";
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.hoge.fuga = @"piyopiyo";
}

こうしておくと、こんな感じにログから呼び出しもとが分かります。

// viewDidLoadのところ
2014-05-11 03:06:07.152 AspectsSample[11868:60b] (
                
4   AspectsSample 0x0000000100001990 -[RootViewController viewDidLoad] + 336
                
)
<Hoge: 0x10929ce20> set: hogehoge

// viewWillAppearのところ
2014-05-11 03:06:07.174 AspectsSample[11868:60b] (
                
4   AspectsSample 0x0000000100001b5d -[RootViewController viewWillAppear:] + 125
                
)
<Hoge: 0x10929ce20> set: fugafuga

// viewDidAppearのところ
2014-05-11 03:06:07.561 AspectsSample[11868:60b] (
                
4   AspectsSample 0x0000000100001bed -[RootViewController viewDidAppear:] + 125
                
)
<Hoge: 0x10929ce20> set: piyopiyo

peterさんのREADME見ればもっと色々書いてありますので、興味を持った人は見てみて下さい。

ちなみに今回、試したサンプルも置いておきます
mpon/AspectsSample

recruitmp
結婚・カーライフ・進学の情報サイトや『スタディサプリ』などの学びを支援するサービスなど、ライフイベント領域に関わるサービスを提供するリクルートグループの中核企業
http://www.recruit-mp.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした