如何通过类层次结构避免污染方法?

时间:2017-06-07 07:00:44

标签: ios objective-c runtime

@interface TestView: UIView
@end

出于某些特殊原因,我想替换TestView(继承自UIView)layoutSubviews方法。但是,不想影响原始UIView的生命周期,只是希望它在课程TestView内工作。

我只是这样实现它:

@implementation TestView

+(void)load {
    Method a = class_getInstanceMethod(TestView.class, @selector(layoutSubviews));
    Method b = class_getInstanceMethod(TestView.class, @selector(customLayout));
    method_exchangeImplementations(a, b);
}

但出了点问题。 UIWindow似乎也调用了customLayout。我猜它已经被污染了。我怎么能避免这种情况?

  

IDE:xCode 8.2.1

     

操作系统:10.12.4

这是测试代码:

#import "ViewController.h"
#import <objc/runtime.h>

@interface TestView: UIView

@end

@implementation TestView

+(void)load {
    Method a = class_getInstanceMethod(TestView.class, @selector(layoutSubviews));
    Method b = class_getInstanceMethod(TestView.class, @selector(customLayout));
    method_exchangeImplementations(a, b);
}


-(void) customLayout {
    [self customLayout];
}

-(instancetype) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    self.backgroundColor = UIColor.grayColor;
    return self;
}

@end


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    TestView* view = [[TestView alloc] initWithFrame: CGRectMake(0, 0, 300, 300)];
    [self.view addSubview:view];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

这是运行时日志:

2017-06-07 14:51:38.240 swizzleTest[37908:28470016] -[UIWindow customLayout]: unrecognized selector sent to instance 0x7fc3d7009d00
2017-06-07 14:51:38.242 swizzleTest[37908:28470016] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIWindow customLayout]: unrecognized selector sent to instance 0x7fc3d7009d00'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000111450d4b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010f00021e objc_exception_throw + 48
    2   CoreFoundation                      0x00000001114c0f04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    3   CoreFoundation                      0x00000001113d6005 ___forwarding___ + 1013
    4   CoreFoundation                      0x00000001113d5b88 _CF_forwarding_prep_0 + 120
    5   swizzleTest                         0x000000010ea2b3fb -[TestView customLayout] + 43
    6   UIKit                               0x000000010f594ab8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1237
    7   QuartzCore                          0x0000000114296bf8 -[CALayer layoutSublayers] + 146
    8   QuartzCore                          0x000000011428a440 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
    9   QuartzCore                          0x000000011428a2be _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
    10  QuartzCore                          0x0000000114218318 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 280
    11  QuartzCore                          0x00000001142453ff _ZN2CA11Transaction6commitEv + 475
    12  QuartzCore                          0x0000000114245d6f _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 113
    13  CoreFoundation                      0x00000001113f5267 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    14  CoreFoundation                      0x00000001113f51d7 __CFRunLoopDoObservers + 391
    15  CoreFoundation                      0x00000001113d98a6 CFRunLoopRunSpecific + 454
    16  UIKit                               0x000000010f4c9aea -[UIApplication _run] + 434
    17  UIKit                               0x000000010f4cfc68 UIApplicationMain + 159
    18  swizzleTest                         0x000000010ea2b9af main + 111
    19  libdyld.dylib                       0x000000011240068d start + 1
    20  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

2 个答案:

答案 0 :(得分:0)

我在layoutSubviews课程中明确覆盖TestView之后就可以了。 否则,class_getInstanceMethod(TestView.class, @selector(layoutSubviews));只能获得-[UIView layoutSubviews]而不是-[TestView layoutSubviews]

修改后的代码如下:

@implementation TestView

+(void)load {
    Method a = class_getInstanceMethod(TestView.class, @selector(layoutSubviews));
    Method b = class_getInstanceMethod(TestView.class, @selector(customLayout));
    method_exchangeImplementations(a, b);
}


-(void) customLayout {
    [self customLayout];
}

-(void) layoutSubviews {
    [super layoutSubviews];
}

-(instancetype) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    self.backgroundColor = UIColor.grayColor;
    return self;
}

@end

答案 1 :(得分:0)

在文档中发现的常见模式是:

  1. 在子类中查找方法
  2. 2a上。如果方法存在,则调动

    2B。如果子类中不存在该方法(从超类继承),则将该方法添加为新方法。