存放着selector的名字和方法实现的映射关系,Sw

作者: 编程  发布:2019-09-11

相信iOS开发的同学如果使用了AFNetworking这个第三方框架,可能会碰到以下Bug:

Objective-C的hook方案: Method Swizzling

Method Swizzling是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

转载:南峰子的技术博客

本文转载自:

Error Domain=com.alamofire.error.serialization.responseCode=-1016"Request failed: unacceptable content-type: text/html"UserInfo={com.alamofire.serialization.response.error.response= { URL: http://c.m.163.com/nc/article/headline/T1348647853363/0-140.html }{ statuscode:200, headers { .....}......22222c22626f6172646964223a226e6577735f736865687569375f626273222c227074696d65223a22323031362d30332d30332031313a30323a3435227d5d7d>, NSLocalizedDescription=Request failed: unacceptablecontent-type: text/html}

Method Swizzling 原理

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。

  • 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP;
  • 我们可以利用 class_replaceMethod 来修改类;
  • 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
    …… 归根结底,就是偷换了selector的IMP

理解Method Swizzling是学习runtime机制的一个很好的机会。在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文。

理解Method Swizzling是学习runtime机制的一个很好的机会。在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文。

众所周知,这是AFNetworking不支持解析text/html格式的数据.

Method Swizzling 实践

举个例子:我们想跟踪在程序中每一个 viewController 展示给用户的次数:当然,我们可以在每个viewController 的 viewDidAppear 中添加跟踪代码;但是这太过麻烦,需要在每个view controller中写重复的代码。创建一个子类可能是一种实现方式,但需要同时创建 UIViewController, UITableViewController, UINavigationController 及其它 UIKit中viewController 的子类,这同样会产生许多重复的代码。 这种情况下,我们就可以使用Method Swizzling,如在代码所示:

import <objc/runtime.h>

@implementation HYHMainViewController

  (void)load {

       static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{

       Class class = [self class];    

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

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

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

       if (didAddMethod) {/ 判断是否已经有这个方法了

           class_replaceMethod(class,
           swizzledSelector,
           method_getImplementation(originalMethod),
           method_getTypeEncoding(originalMethod));

       } else {

           method_exchangeImplementations(originalMethod, swizzledMethod);

      }
   });
}


-(void)viewWillAppear:(BOOL)animated{

   [super viewWillAppear:animated];
   NSLog(@"我在swiz_viewWillAppear执行之后执行的这段代码");

}
@end


- (void)my_viewWillAppear:(BOOL)animated{

   //插入需要执行的代码
   NSLog(@"我在viewWillAppear执行前偷偷插入了一段代码");
   //不能干扰原来的代码流程,插入代码结束后要让本来该执行的代码继续执行
   [self my_viewWillAppear:animated];

}
 @end

打印的Log:

2016-05-25 11:22:27.028 xiaoHong[1340:58547] 我在viewWillAppear执行前偷偷插入了一段代码   
2016-05-25 11:22:27.028 xiaoHong[1340:58547] 我在swiz_viewWillAppear执行之后执行的这段代码  

在这里,我们通过 method swizzling 修改了 UIViewController 的@selector(viewWillAppear:)对应的函数指针,使其实现指向了我们自定义的 my_viewWillAppear 的实现。这样,当 UIViewController 及其子类的对象调用 viewWillAppear 时,都会打印一条日志信息。

上面的例子很好地展示了使用method swizzling在一个类中注入一些我们新的操作。当然,还有许多场景可以使用method swizzling,在此不多举例。

使用 Method Swizzling 编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全。 Method swizzling 可以帮助我们写出更好的,更高效的,易维护的代码。但是如果滥用它,也将会导致难以排查的bug。 在此我们说说使用method swizzling需要注意的一些问题:

Method Swizzling是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

Method Swizzling是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

按道理讲:我们只要把

Swizzling应该总是在 load中执行

在Objective-C中,运行时会自动调用每个类的两个方法。

  • load会在类初始加载时调用, initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。

  • load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下, initialize在其执行时不提供这种保证—事实上,如果在应用中没有给这个类发送消息,则它可能永远不会被调用。

多个有继承关系的类的对象swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被swizzle的实现。在 (void)load中swizzle不会出错,就是因为load类方法会默认从父类开始调用。

例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的viewDidAppear中添加跟踪代码;但是这太过麻烦,需要在每个view controller中写重复的代码。创建一个子类可能是一种实现方式,但需要同时创建UIViewController, UITableViewController, UINavigationController及其它UIKit中view controller的子类,这同样会产生许多重复的代码。

例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的viewDidAppear中添加跟踪代码;但是这太过麻烦,需要在每个view controller中写重复的代码。创建一个子类可能是一种实现方式,但需要同时创建UIViewController, UITableViewController, UINavigationController及其它UIKit中view controller的子类,这同样会产生许多重复的代码。

self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",nil];

Swizzling应该总是在dispatch_once中执行

与上面相同,因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。

这种情况下,我们就可以使用Method Swizzling,如在代码所示:

这种情况下,我们就可以使用Method Swizzling,如在代码所示:

变成
self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];

思路是完全正确的,但是我们真正处理起来就会有麻烦:

我们一般是用cocoapods来管理第三方库的,方便第三方库的版本更新。既然是pod管理的,我们就无法去更改源代码,即便更改了,下次pod更新了,也会把修改好的代码覆盖掉。这样我们就需要其他方法来彻底解决这个问题了。

@implementation AFJSONResponseSerializer- (instancetype)init { self= [super init]; if { return nil; } self.acceptableContentTypes= [NSSet setWithObjects:@"application/json",@"text/json",@"text/javascript",nil]; return self;}

上面这个方法是在AFJSONResponseSerializer 的init方法了设置acceptableContentTypes。如果我们能够复写AFJSONResponseSerializer的init方法或者替换掉这个方法,改成我们想实现的,应该就可以实现了!

首先,大家想到的肯定是通过子类继承的方法去复写此方法,但是继承肯定不方便了。那么,通过类目去复写此方法,能不能实现呢?答案是否定的

1.类目中不能super

2.类目的本质是给原始类增加方法,而不是修改修改原始类的方法.要覆盖原始类的方法可以通过子类继承的方式,这里用继承肯定不方便了。

重要的事情说三遍:不要类目在覆盖原始类的方法不要类目在覆盖原始类的方法不要类目在覆盖原始类的方法

原因是:纵观苹果的api,以NSString为例,有NSStringExtensionMethods,NSStringEncodingDetection等各种分类方法,如果我们再写一个分类覆盖原分类里的方法,那么系统就无法区分是A分类方法覆盖B分类的方法,还是B分类方法覆盖A分类的方法。

最后,只有最后一个办法了,通过类目去替换掉AFJSONResponseSerializer的init方法.原理就是Method Swizzle

代码实现如下:

#import <AFNetworking/AFNetworking.h>@interface AFJSONResponseSerializer @end#import" AFJSONResponseSerializer Category.h"@implementation AFJSONResponseSerializer   load { staticdispatch_once_tonceToken; dispatch_once(&onceToken, ^{ Method orignalMethod =class_getClassMethod([selfclass],@selector; Method swizzledMethod =class_getClassMethod([selfclass],@selector(dev4mobile_init)); method_exchangeImplementations(orignalMethod, swizzledMethod); });}- (instancetype)dev4mobile_init { [self dev4mobile_init]; self.acceptableContentTypes = [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil]; return self;}@end

此处要注意的有几点:

1.load是在程序的main函数之前就调用的,当程序开始运行,而不是编译的时候,调用load方法。未了保证全局的去交换AFJSONResponseSerializer的init方法和dev4mobile_init的IMP。

2.用dispatch_once去保证两个方法的指针只交换一次。为了避免多线程出现多次调用的结果。

3.有的人可能觉得调用[self dev4mobile_init];方法时会产生递归。其实不然,正确的顺序是这样的,AFJSONResponseSerializer先调用自身init方法,但是指向init方法的selector已经指向了dev4mobile_init方法了,所以会调到分类方法中,而调用[self dev4mobile_init];方法是,指向dev4mobile_init的selector指向的是AFJSONResponseSerializer的init方法,走完init方法,就会走

self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];

这行代码了。

4.Method swizzledMethod原理就是交换两个selector所指向的IMP.还有很多其他的实际用途:例如你不想调用UITextField一个的代理方法,想调到自定义的方法,此时就可以用她来hook一下。

5.把dev4mobile_init换成xxx_init(xxx为自己命名的前缀)。

这里是关于Method Swizzle的几个陷阱:Method swizzling is not atomicChanges behavior of un-ownedcodePossible naming conflictsSwizzling changes the method's argumentsThe order of swizzles mattersDifficult to understand (looks recursive)Difficult to debug

大家自己体会一下。

推荐念茜的一篇Method Swizzle博客给大家

选择器、方法与实现

在Objective-C中,选择器(selector)、方法(method)和实现(implementation)是运行时中一个特殊点,虽然在一般情况下,这些术语更多的是用在消息发送的过程描述中。 以下是Objective-C Runtime Reference中的对这几个术语一些描述:

  1. Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。

  2. Method(typedef struct objc_method *Method):在类定义中表示方法的类型

  3. Implementation(typedef id (*IMP)(id, SEL, …)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。

理解这几个术语之间的关系最好的方式是:一个类维护一个运行时可接收的消息分发表;分发表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个实现(IMP),即指向底层C函数的指针。 为了swizzle一个方法,我们可以在分发表中将一个方法的现有的选择器映射到不同的实现,而将该选择器对应的原始实现关联到一个新的选择器中。

调用_cmd
我们回过头来看看前面新的方法的实现代码:

- (void)my_viewWillAppear:(BOOL)animated {

   [self my_viewWillAppear:animated];
   NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));

}

第一眼看上去是会导致无限循环的。但令人惊奇的是,并没有出现这种情况。在swizzling的过程中,方法中的[self my_viewWillAppear:animated]已经被重新指定到UIViewController类的-viewWillAppear:中。在这种情况下,不会产生无限循环。不过如果我们调用的是[self viewWillAppear:animated],则会产生无限循环,因为这个方法的实现在运行时已经被重新指定为my_viewWillAppear:了。

**注意事项**
Swizzling通常被称作是一种黑魔法,容易产生不可预知的行为和无法预见的后果。虽然它不是最安全的,但如果遵从以下几点预防措施的话,还是比较安全的:

  1. 总是调用方法的原始实现(除非有更好的理由不这么做):API提供了一个输入与输出约定,但其内部实现是一个黑盒。Swizzle一个方法而不调用原始实现可能会打破私有状态底层操作,从而影响到程序的其它部分。
  2. 避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。例如 - (void)my_viewWillAppear:(BOOL)animated;这样避免了selector的命名冲突。
  3. 明白是怎么回事:简单地拷贝粘贴swizzle代码而不理解它是如何工作的,不仅危险,而且会浪费学习Objective-C运行时的机会。阅读Objective-C Runtime Reference和查看<objc/runtime.h>头文件以了解事件是如何发生的。
  4. 小心操作:无论我们对Foundation, UIKit或其它内建框架执行Swizzle操作抱有多大信心,需要知道在下一版本中许多事可能会不一样。

结论
如果使用恰当,Method swizzling 还是很安全的.一个简单安全的方法是,仅在load中swizzle。 和许多其他东西一样,它也是有危险性的,但理解它了也就可以正确恰当的使用它了。

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

  (void)load {
        static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];         
        // When swizzling a class method, use the following:
                    // Class class = object_getClass((id)self);

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

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

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

        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 {
        [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

#import

Method Swizzling 的封装

//  NSObject HYHSwizzle.m
//  xiaoHong
//  Created by 黄艳红 on 16/5/23.
//  Copyright © 2016年 9fbank. All rights reserved.

#import "NSObject HYHSwizzle.h"
#import <objc/runtime.h>

@implementation NSObject (HYHSwizzle)

  (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

在这里,我们通过method swizzling修改了UIViewController的@selector(viewWillAppear:)对应的函数指针,使其实现指向了我们自定义的xxx_viewWillAppear的实现。这样,当UIViewController及其子类的对象调用viewWillAppear时,都会打印一条日志信息。

@implementation UIViewController(Tracking)

上面的例子很好地展示了使用method swizzling来一个类中注入一些我们新的操作。当然,还有许多场景可以使用method swizzling,在此不多举例。在此我们说说使用method swizzling需要注意的一些问题:

(void)load {

Swizzling应该总是在 load中执行

在Objective-C中,运行时会自动调用每个类的两个方法。 load会在类初始加载时调用, initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。 load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下, initialize在其执行时不提供这种保证—事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用。

staticdispatch_once_tonceToken;

Swizzling应该总是在dispatch_once中执行

与上面相同,因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。

dispatch_once(&onceToken, ^{

选择器、方法与实现

在Objective-C中,选择器(selector)、方法(method)和实现(implementation)是运行时中一个特殊点,虽然在一般情况下,这些术语更多的是用在消息发送的过程描述中。

以下是Objective-C Runtime Reference中的对这几个术语一些描述:

  • Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。
  • Method(typedef struct objc_method *Method):在类定义中表示方法的类型
  • Implementation(typedef id (*IMP)(id, SEL, …)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。

理解这几个术语之间的关系最好的方式是:一个类维护一个运行时可接收的消息分发表;分发表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个实现(IMP),即指向底层C函数的指针。

为了swizzle一个方法,我们可以在分发表中将一个方法的现有的选择器映射到不同的实现,而将该选择器对应的原始实现关联到一个新的选择器中。

Classclass= [selfclass];

调用_cmd

我们回过头来看看前面新的方法的实现代码:

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
}

咋看上去是会导致无限循环的。但令人惊奇的是,并没有出现这种情况。在swizzling的过程中,方法中的[self xxx_viewWillAppear:animated]已经被重新指定到UIViewController类的-viewWillAppear:中。在这种情况下,不会产生无限循环。不过如果我们调用的是[self viewWillAppear:animated],则会产生无限循环,因为这个方法的实现在运行时已经被重新指定为xxx_viewWillAppear:了。

注意事项

Swizzling通常被称作是一种黑魔法,容易产生不可预知的行为和无法预见的后果。虽然它不是最安全的,但如果遵从以下几点预防措施的话,还是比较安全的:

  1. 总是调用方法的原始实现(除非有更好的理由不这么做):API提供了一个输入与输出约定,但其内部实现是一个黑盒。Swizzle一个方法而不调用原始实现可能会打破私有状态底层操作,从而影响到程序的其它部分。
  1. 避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。
  2. 明白是怎么回事:简单地拷贝粘贴swizzle代码而不理解它是如何工作的,不仅危险,而且会浪费学习Objective-C运行时的机会。阅读Objective-C Runtime Reference和查看<objc/runtime.h>头文件以了解事件是如何发生的。
  3. 小心操作:无论我们对Foundation, UIKit或其它内建框架执行Swizzle操作抱有多大信心,需要知道在下一版本中许多事可能会不一样。

// When swizzling a class method, use the following:

为什么不直接替换方法,而是要添加判断

查看了原博主评论中的解析:

添加是为了重写父类的 viewWillAppear 方法,如果你自己已经手动重写了 viewWillAppear 方法,class_addMethod() 就会添加失败,返回 NO。

// Class class = object_getClass((id)self);

如果返回 NO ,说明已经重写,就直接交换。

SEL originalSelector =@selector(viewWillAppear:);

如果返回 YES, 说明添加成功,也就是你之前没有重写 viewWillAppear 方法。因为在添加方法里面是将 originalSelector 与 swizzledMethod 的 IMP 绑定在一起了,所以接下来只用将 swizzledSelector 与 originalMethod 的 IMP 绑定就可以了。也就实现了交换。

SEL swizzledSelector =@selector(xxx_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);

Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod = class_addMethod(class,

originalSelector,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));

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 {

[selfxxx_viewWillAppear:animated];

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

}

@end

在这里,我们通过method swizzling修改了UIViewController的@selector(viewWillAppear:)对应的函数指针,使其实现指向了我们自定义的xxx_viewWillAppear的实现。这样,当UIViewController及其子类的对象调用viewWillAppear时,都会打印一条日志信息。

上面的例子很好地展示了使用method swizzling来一个类中注入一些我们新的操作。当然,还有许多场景可以使用method swizzling,在此不多举例。在此我们说说使用method swizzling需要注意的一些问题:

Swizzling应该总是在 load中执行

在Objective-C中,运行时会自动调用每个类的两个方法。 load会在类初始加载时调用, initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。 load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下, initialize在其执行时不提供这种保证–事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用。

Swizzling应该总是在dispatch_once中执行

与上面相同,因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。

选择器、方法与实现

在Objective-C中,选择器(selector)、方法(method)和实现(implementation)是运行时中一个特殊点,虽然在一般情况下,这些术语更多的是用在消息发送的过程描述中。

以下是Objective-C Runtime Reference中的对这几个术语一些描述:

Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。

Method(typedef struct objc_method *Method):在类定义中表示方法的类型

Implementation(typedef id (*IMP)(id, SEL, ...)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。

理解这几个术语之间的关系最好的方式是:一个类维护一个运行时可接收的消息分发表;分发表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个实现(IMP),即指向底层C函数的指针。

为了swizzle一个方法,我们可以在分发表中将一个方法的现有的选择器映射到不同的实现,而将该选择器对应的原始实现关联到一个新的选择器中。

调用_cmd

我们回过头来看看前面新的方法的实现代码:

- (void)xxx_viewWillAppear:(BOOL)animated {

[selfxxx_viewWillAppear:animated];

NSLog(@"viewWillAppear: %@",NSStringFromClass([selfclass]));

}

咋看上去是会导致无限循环的。但令人惊奇的是,并没有出现这种情况。在swizzling的过程中,方法中的[self xxx_viewWillAppear:animated]已经被重新指定到UIViewController类的-viewWillAppear:中。在这种情况下,不会产生无限循环。不过如果我们调用的是[self viewWillAppear:animated],则会产生无限循环,因为这个方法的实现在运行时已经被重新指定为xxx_viewWillAppear:了。

注意事项

Swizzling通常被称作是一种黑魔法,容易产生不可预知的行为和无法预见的后果。虽然它不是最安全的,但如果遵从以下几点预防措施的话,还是比较安全的:

总是调用方法的原始实现(除非有更好的理由不这么做):API提供了一个输入与输出约定,但其内部实现是一个黑盒。Swizzle一个方法而不调用原始实现可能会打破私有状态底层操作,从而影响到程序的其它部分。

避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。

明白是怎么回事:简单地拷贝粘贴swizzle代码而不理解它是如何工作的,不仅危险,而且会浪费学习Objective-C运行时的机会。阅读Objective-C Runtime Reference和查看头文件以了解事件是如何发生的。

小心操作:无论我们对Foundation, UIKit或其它内建框架执行Swizzle操作抱有多大信心,需要知道在下一版本中许多事可能会不一样。

本文由9159.com发布于编程,转载请注明出处:存放着selector的名字和方法实现的映射关系,Sw

关键词: 9159.com