runtime-您几岁了?

  • write by coderiding,erliucxy 中山 东区 广东 2018-02-23- 17:19:20
  • 开始之前,你可以直接查看图片文章图片链接

第一部分:runtime的第一神器-Associated Objects

  • Associated Objects的介绍:中文翻译为关联对象、对象关联、关联引用
  • 文字总结write by erliucxy
  • 可以实现什么:给系统的类增加属性对象。
  • 方便的地方是://TODO

1.1主要涉及3个方法

  • objc_setAssociatedObject 设置
    1
    2
    3
    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
    id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

  • objc_getAssociatedObject 获取
    1
    2
    OBJC_EXPORT id objc_getAssociatedObject ( id object , const void *key ) 
    OBJC_AVAILABLE ( 10.6 , 3.1 , 9.0 , 1.0 );

  • objc_removeAssociatedObjects 移除
    • 不应该自己手动调用这个函数,规范的方法是:调用 objc_setAssociatedObject 方法并传入一个 nil 值来清除一个关联;因为可能会导致其他客户对其添加的属性也被移除了

  • 基本参数说明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    object 【源对象—{一般是self}】

    key 【关键字{属性}】write by erliucxy
    1.属性最好是 static char 类型的,当然更推荐是指针型的。
    2.然而可以用更简单的方式实现:用selector}
    3.{Since SELs are guaranteed to be unique and constant, you can use _cmd as the key for objc_setAssociatedObject().采用隐藏参数_cmd作为key}

    value【被关联的对象】

    policy 【关联策略{关联对象的行为} objc_AssociationPolicy】

1.2关联策略objc_AssociationPolicy

  • 关联对象的行为,它可以指定Objc内存管理的引用计数机制。
  • 以 OBJC_ASSOCIATION_ASSIGN 类型关联在对象上的弱引用不代表0 retian的 weak 弱引用,行为上更像 unsafe_unretained 属性,所以当在你的视线中调用weak的关联对象时要相当小心。write by erliucxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Behavior
@property Equivalent
Description

OBJC_ASSOCIATION_ASSIGN
@property (assign) 或 @property (unsafe_unretained)
指定一个关联对象的弱引用。

OBJC_ASSOCIATION_RETAIN_NONATOMIC
@property (nonatomic, strong)
指定一个关联对象的强引用,不能被原子化使用。

OBJC_ASSOCIATION_COPY_NONATOMIC
@property (nonatomic, copy)
指定一个关联对象的copy引用,不能被原子化使用。

OBJC_ASSOCIATION_RETAIN
@property (atomic, strong)
指定一个关联对象的强引用,能被原子化使用。

OBJC_ASSOCIATION_COPY
@property (atomic, copy)
指定一个关联对象的copy引用,能被原子化使用。

1.3 Associated Objects常见的使用例子

  • 001–使用例子
  • 相当于加了一个 @property (nonatomic, strong)
1
2
3
4
5
6
7
8
9
@dynamic associatedObject;《OC-checklist》[※※]@synthesize和@dynamic分别有什么作用?

- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}

  • 002–使用例子
  • 相当于加了一个 @property (assign) 或 @property (unsafe_unretained)
1
2
3
4
5
6
7
8
static const int TagKey = 0;
- (void)setTag:(NSInteger)tag{
objc_setAssociatedObject(self, &TagKey, @(tag), OBJC_ASSOCIATION_ASSIGN);
}

- (NSInteger)tag{
return [objc_getAssociatedObject(self, &TagKey) integerValue];
}

  • 003—使用例子
  • 相当于加了一个 @property (nonatomic, strong)
1
2
3
4
5
6
7
8
static const NSString *ViewHudKey = @"ViewHud”;
- (void)setHUD:(MBProgressHUD *)HUD{
objc_setAssociatedObject(self, &ViewHudKey, HUD, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (MBProgressHUD *)HUD{
return objc_getAssociatedObject(self, &ViewHudKey);
}

  • —004 使用例子
  • 相当于加了一个 @property (atomic, strong)
1
2
3
4
5
6
7
8
static const NSString *ViewControllerInfo = @"ViewControllerInfo”;
- (void)setRouterUserInfo:(NSDictionary *)routerUserInfo{
objc_setAssociatedObject(self, &ViewControllerInfo, routerUserInfo, OBJC_ASSOCIATION_RETAIN);
}

- (NSDictionary *)routerUserInfo{
return objc_getAssociatedObject(self, &ViewControllerInfo);
}

  • —005 使用例子
  • 相当于加了一个 @property (nonatomic, copy)
1
2
3
4
5
6
7
8
9
- (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
{
objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
{
return objc_getAssociatedObject(self, _cmd);
}

  • —006 使用例子
  • 相当于加了一个 @property (nonatomic, strong)
1
2
3
4
5
6
7
8
9
- (void)setFd_prefersNavigationBarHidden:(BOOL)hidden
{
objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)fd_prefersNavigationBarHidden
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}

  • -007 使用例子
  • // 相当于加了一个 @property (nonatomic, strong)
1
2
3
4
5
6
7
8
9
10
11
static char RedTipViewKey;

- (void)setRedTipView:(UILabel *)redTipView
{
objc_setAssociatedObject(self, &RedTipViewKey, redTipView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UILabel *)redTipView
{
return objc_getAssociatedObject(self,&RedTipViewKey);
}

  • Associated Objects合体使用的例子
  • —008合体例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    - (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver
    {
    AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));

    if (notificationObserver == nil)
    {
    // 相当于加了一个 @property (nonatomic, strong)
    notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
    objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    return notificationObserver;
    }

  • —009 合体例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate
    {
    _FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd);

    if (!delegate) {
    // 相当于加了一个 @property (nonatomic, strong)
    delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init];
    delegate.navigationController = self;

    objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return delegate;
    }

  • —010 合体例子write by erliucxy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    - (void)bk_addEventHandler:(void (^)(id sender))handler forControlEvents:(UIControlEvents)controlEvents
    {
    NSParameterAssert(handler);
    NSMutableDictionary *events = objc_getAssociatedObject(self, BKControlHandlersKey);
    if (!events) {
    // 相当于加了一个 @property (nonatomic, strong)
    events = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, BKControlHandlersKey, events, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    NSNumber *key = @(controlEvents);
    NSMutableSet *handlers = events[key];
    if (!handlers) {
    handlers = [NSMutableSet set];
    events[key] = handlers;
    }

    BKControlWrapper *target = [[BKControlWrapper alloc] initWithHandler:handler forControlEvents:controlEvents];
    [handlers addObject:target];
    [self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents];
    }

第二部分:你在什么地方接触过runtime?

2.1 三个方面接触过:

  1. 通过 Objective-C 源代码 - 接触runtime
    • 在runtime中,通过数据结构来定义的一些类、方法、协议等;

  1. 通过 Foundation 框架的NSObject类定义的方法 - 接触runtime
    • 如isKindOfClass:
    • 如isMemberOfClass:
    • 如respondsToSelector:
    • 如methodForSelector:

  1. 通过对 runtime 函数的直接调用 - 接触runtime
    • objc_ 开头的方法
      1. 如objc_ivar_list
      2. 如objc_property_attribute_t
    • ivar_ 开头的方法
      1. 如ivar_getOffset
      2. 如ivar_list
    • protocol_ 开头的方法
      1. 如protocol_getName
      2. 如protocol_copyPropertyList
    • method_ 开头的方法
      1. 如method_getName
      2. 如method_setImplementation{第二神器用到}
      3. 如method_exchangeImplementations {第二神器用到}
      4. 如method_getTypeEncoding{第二神器用到}
    • sel_ 开头的方法
      1. 如sel_getName
      2. 如sel_registerName
    • imp_ 开头的方法
      1. 如imp_getBlock
      2. 如imp_removeBlock
      3. 如imp_implementationWithBlock
    • class_ 开头的方法
      1. 如class_addIvar
      2. 如class_getName
      3. 如class_addProperty
      4. 如class_getInstanceMethod {第二神器用到}write by erliucxy
      5. 如class_replaceMethod {第二神器用到}
      6. 如class_addMethod {第二神器用到}

第三部分:runtime的第二神器-Method Swizzling

3.1本质就是

  1. 交换IMP和SEL的对应关系,以达到替换方法实现的目的;
  2. Method Swizzling就是OC中的Hook

3.2必须明确的概念

  • IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。表示同一个id + 同一个SEL = 同一个IMP
1
2
3
4
5
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id

  • id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员;根据isa指针就可以顺藤摸瓜找到对象所属的类write by erliucxy
1
2
3
4
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
}

  • SEL的本质:SEL 是系统在编译过程中,根据 方法的名字 以及 参数序列 生成一个用来区分这个方法的唯一 ID 编号;这个 ID 就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
    1
    Selector(typedef struct objc_selector *SEL)

  • Method的本质:指向objc_method结构体指针;是一种代表类中的某个方法的类型。
  • method_name:方法名类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
  • method_types:方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
  • method_imp:指向了方法的实现,本质上是一个函数指针,后面会详细讲到
1
2
3
4
5
6
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE ; // 方法名,作为key
char *method_types OBJC2_UNAVAILABLE ; // 参数类型以及返回值类型编码
IMP method_imp OBJC2_UNAVAILABLE ; // 方法实现指针,作为value
}

  • // 疑问:什么时候调用class_addMethod?调用它有什么作用?
  • // 作用:官方解答:Adds a new method to a class with a given name and implementation.class_addMethod will add an override of a superclass’s implementation
  • // 作用:中文翻译:通过给的name和imp添加一个新的方法到类中。它将添加一个父类已经实现的imp。
  • // 注意:调用这个方法不能直接改变该类中已经存在的imp,如果要改变,使用method_setImplementation
    1
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

  • // 当需要替换的方法可能有不存在的情况时,可以考虑使用该方法。write by erliucxy
  • cls:The class you want to modify.
  • name:A selector that identifies the method whose implementation you want to replace.
  • imp:The new implementation for the method identified by name for the class identified by cls.
  • types:An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).

  • // 疑问:什么时候调用class_replaceMethod?调用它有什么作用?

  • // 作用:官方:Replaces the implementation of a method for a given class.
  • // 作用:解释:为给定的类替换imp
    1
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);

  • // 当仅仅需要为一个方法设置其实现方式时使用
    1
    IMP method_setImplementation(Method m, IMP imp);

  • // OC实现的编码类型
    1
    const char * method_getTypeEncoding(Method m);

3.3 用一个图来表示Method Swizzling在做什么?

  • 交换IMP和SEL的对应关系,以达到替换方法实现的目的;

3.4Method Swizzling的实现方案

  • Method Swizzling的实现方案一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+ (void)load
{
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Class aClass = [self class];

SEL originalSelector = @selector(method_original:);
SEL swizzledSelector = @selector(method_swizzle:);

Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

// 直接交换IMP{有风险,因为当前类可以没有实现要交换的方法,而是继承父类的}
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}

  • Method Swizzling的实现方案二:–最佳方案
  • 如果类中没有实现 Original selector 对应的方法,那就先添加 Method,并将其 IMP 映射为 Swizzle 的实现。然后替换 Swizzle selector 的 IMP 为 Original 的实现;否则交换二者 IMP。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
+ (void)load
{
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Class aClass = [self class];

SEL originalSelector = @selector(method_original:);
SEL swizzledSelector = @selector(method_swizzle:);

Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

// 通过class_addMethod:判断该类实现了原始方法还是父类实现了原始方法
* aClass,当前类
* originalSelector ,如果class_addMethod返回YES,originalSelector就是指向父类的方法;如果返回NO,originalSelector就是指向当前类的方法;
* method_getImplementation(swizzledMethod) ,自定义方法的IMP
* method_getTypeEncoding(swizzledMethod),自定义方法的类型

BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
// 如果添加成功,证明这个类没有实现要替换的方法,而是继承了父类的实现;接下来要做的是--使用class_replaceMethod交换IMP,下面就是将(swizzledSelector)与(method_getImplementation(originalMethod))的IMP交换;
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod)
);
} else {
// 【如果添加失败,证明这个类实现了原始方法;交换IMP,originalMethod—swizzledMethod】write by erliucxy
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
  • 有时为了避免方法命名冲突和参数 _cmd 被篡改,也会使用下面这种『静态方法版本』的 Method Swizzle。CaptainHook 中的宏定义也是采用这种方式,比较推荐:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    typedef IMP *IMPPointer;

    static void MethodSwizzle(id self, SEL _cmd, id arg1);
    static void (*MethodOriginal)(id self, SEL _cmd, id arg1);

    static void MethodSwizzle(id self, SEL _cmd, id arg1) {
    // do custom work
    MethodOriginal(self, _cmd, arg1);
    }

    BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store)
    {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
    const char *type = method_getTypeEncoding(method);
    imp = class_replaceMethod(class, original, replacement, type);
    if (!imp) {
    imp = method_getImplementation(method);
    }
    }

    if (imp && store) { *store = imp; }
    return (imp != NULL);
    }

    + (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store
    {
    return class_swizzleMethodAndStore(self, original, replacement, store);
    }

    + (void)load
    {
    [self swizzle:@selector(originalMethod:) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
    }

3.5Method Swizzling常见的使用例子

  • -001 使用例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#import "NSArray+Swizzle.h"  

@implementation NSArray (Swizzle)

- (id)myLastObject
{
id ret = [self myLastObject];
NSLog(@"********** myLastObject *********** ");
return ret;
}
@end



#import <objc/runtime.h>
#import "NSArray+Swizzle.h"

int main(int argc, char *argv[])
{
@autoreleasepool {

Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));
method_exchangeImplementations(ori_Method, my_Method);

NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"TEST RESULT : %@",string);

return 0;
}
}

结果输出:
:********** myLastObject ***********
:TEST RESULT : 3

  • -002 使用例子–RNSwizzle
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //  直接操作IMP
    // RNSwizzle.m
    // MethodSwizzle

    #import "RNSwizzle.h"
    #import <objc/runtime.h>
    @implementation NSObject (RNSwizzle)

    + (IMP)swizzleSelector:(SEL)origSelector
    withIMP:(IMP)newIMP {

    Class class = [self class];
    Method origMethod = class_getInstanceMethod(class,
    origSelector);
    IMP origIMP = method_getImplementation(origMethod);

    if(!class_addMethod(self, origSelector, newIMP,
    method_getTypeEncoding(origMethod)))
    {
    method_setImplementation(origMethod, newIMP);
    }

    return origIMP;
    }
    @end

  • -003 使用例子—猿题库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // ImagePickerReplaceMethodsHolder.h
    * @interface ImagePickerReplaceMethodsHolder : NSObject
    * - (BOOL)shouldAutorotate;
    * - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
    * @end

    // ImagePickerReplaceMethodsHolder.m
    @implementation ImagePickerReplaceMethodsHolder
    - (BOOL)shouldAutorotate {
    return NO;
    }
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
    }
    @end


    // 开始替换
    #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
    #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
    + (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    [self hackForImagePicker];
    });
    }

    + (void)hackForImagePicker {
    // fix bug of image picker under iOS 6.0
    // http://stackoverflow.com/questions/12522491/crash-on-presenting-uiimagepickercontroller-under-ios-6-0
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")
    && SYSTEM_VERSION_LESS_THAN(@"6.1"))
    {
    Method oldMethod1 = class_getInstanceMethod([UIImagePickerController class], @selector(shouldAutorotate));
    Method newMethod1 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class], @selector(shouldAutorotate));
    method_setImplementation(oldMethod1, method_getImplementation(newMethod1));

    Method oldMethod2 = class_getInstanceMethod([UIImagePickerController class], @selector(preferredInterfaceOrientationForPresentation));
    Method newMethod2 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class], @selector(preferredInterfaceOrientationForPresentation));
    method_setImplementation(oldMethod2, method_getImplementation(newMethod2));
    }
    }

  • -004 使用例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @interface UIViewController (MRCUMAnalytics)

    @end

    @implementation UIViewController (MRCUMAnalytics)

    + (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    Class class = [self class];

    SEL originalSelector = @selector(viewWillAppear:);
    SEL swizzledSelector = @selector(mrc_viewWillAppear:);

    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    // class_addMethod 判断能否添加将要替换的方法
    BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

    if (success) {
    class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
    // 主类已经实现,直接替换
    method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    });
    }

    #pragma mark - Method Swizzling
    - (void)mrc_viewWillAppear:(BOOL)animated {
    [self mrc_viewWillAppear:animated];
    [MobClick beginLogPageView:NSStringFromClass([self class])];
    }

    @end

-005 使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);

// class表示UIViewController
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));

// class_replaceMethod的最后结果和method_exchangeImplementations一样,
// 都可以实现交换方法

if (didAddMethod)
{
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
// 在交换了方法的实现后,xxx_viewWillAppear:方法的实现已经被替换为了 UIViewController -viewWillAppear:的原生实现,
// 所以这里并不是在递归调用
// 下面的方法实际上实在调用viewWillAppear
// 而调用UIViewController的viewWillAppear实际是调用xxx_viewWillAppear方法
[self xxx_viewWillAppear:animated];

NSLog(@"viewWillAppear: %@", self);
}

@end

  • 上面的代码简化
  • 这是因为class_replaceMethod方法其实能够覆盖到class_addMethod和method_setImplementation两种场景, 对于第一个class_replaceMethod来说, 如果viewWillAppear:实现在父类, 则执行class_addMethod, 否则就执行method_setImplementation将原方法的IMP指定新的代码块; 而第二个class_replaceMethod完成的工作便只是将新方法的IMP指向原来的代码.
  • 但此处需要特别注意交换的顺序,应该优先把新的方法指定原IMP,再修改原有的方法的IMP.write by erliucxy
  • 为什么呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+ (void)load {
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
if (!originalMethod || !swizzledMethod) {
return;
}

IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);

// 这儿的先后顺序是有讲究的,如果先执行后一句,那么在执行完瞬间方法被调用容易引发死循环
// 先将新方法指定给原来的IMP,这样调用原来的方法名,就会执行新的IMP
// 再将原方法指定给新的IMP,这样调用原来的方法名,就会执行新的IMP
class_replaceMethod(class,swizzledSelector,originalIMP,originalType);
class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);
}

3.6Method Swizzling常见的问题

  1. 多继承关系的类使用Method Swizzling时,应该注意的方面是?
    • 多个有继承关系的类的对象Swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被Swizzle的实现。
    • 在load中Swizzle不用担心这种问题,因为load类方法会默认从父类开始调用。
    • 深入了解//TODO
  2. 使用Method Swizzling时解决命名冲突
  3. 为什么Method Swizzling应该在+load方法中实现?write by erliucxy
    • 在 Objective-C 的运行时中,每个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。
    • +load在每个类被装载到Runtime的时候调用
    • +initialize 在每个类第一次被发送消息的时候调用。
    • 之所以要在+load中进行,是因为方法交叉影响的是全局状态,+load中能保证在class 装载的时候进行交叉,而initialize没办法做到。
    • Swizzling在+load中执行时,不要调用[super load];因为如果是多继承,并且对同一个方法都进行了Swizzling,那么调用[super load]以后,父类的Swizzling就失效了。
  4. 为什么Method Swizzling应该在dispatch_once中完成?
    • 由于 swizzling 改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once 满足了所需要的需求,并且应该被当做使用 swizzling 的初始化单例方法的标准。
    • dispatch_once保证方法交叉只交叉一次

write by coderiding,erliucxy 中山 东区 广东 2018-02-23- 17:19:20