Swizzlingについては前に書いているのでそちらを参照ください。
デバッグに活用する
Swizzlingの強力なところは、既存の処理にあとから処理を追加できる点です。
これを応用すれば、特定のプロパティに対してどこからアクセスされているかといったこともチェックできます。
(KVOなどで予期しないところからアクセスされていたり、ということがあるので)
そして今回書くのは、CALayerクラスのレンダリング処理に差し込んで、すべてのCALayer経由でレンダリングされる要素に対してborderを表示する、というものです。
ざっくりとレイアウトが今どうなっているのか、見えるはずの要素が見えないのはなぜか、といったことを視覚的に確認することができます。
UIViewの範囲をborderで示す
// 差し込み処理の定義部分。カテゴリで実装。
# import <objc/runtime.h>
@interface CALayer (Debugger)
@end
@implementation CALayer (Debugger)
- (void) prefix_swapDrawInContext:(CGContextRef)ctx
{
// 既存処理の前にborderの幅と色を設定する
self.borderWidth = 1.0;
self.borderColor = [UIColor redColor].CGColor;
// 元の処理を実行する。この時点ではすでに処理自体が差し替わっているため、メソッド名は同一。(再帰呼び出しではなくなる)
[self prefix_swapDrawInContext:ctx];
}
@end
/////////////////////////////////////////////////////////////////////
// 実装をスワップする関数
void swap() {
Method method1 = class_getInstanceMethod(CALayer.class, @selector(drawInContext:));
Method method2 = class_getInstanceMethod(CALayer.class, @selector(prefix_swapDrawInContext:));
method_exchangeImplementations(method1, method2);
}
// スワップ処理を実行。複数回実行しないようにしておく。
// 実行する場所はデバッグを始めたい箇所で。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swap();
});
UIViewのsetFrameを差し替える
# import <objc/runtime.h>
@interface UIView(debug)
- (void)swapSetFrame:(CGRect)frame;
@end
@implementation UIView(debug)
- (void)swapSetFrame:(CGRect)frame
{
NSLog(@"Called setFrame: %@", NSStringFromCGRect(frame));
[self swapSetFrame:frame];
}
@end
void swapSetFrame() {
Method setFrame = class_getInstanceMethod(UIView.class, @selector(setFrame:));
Method swapSetFrame = class_getInstanceMethod(UIView.class, @selector(swapSetFrame:));
method_exchangeImplementations(setFrame, swapSetFrame);
}