我是靠谱客的博主 无语芒果,最近开发中收集的这篇文章主要介绍method-swizzling 详解 和使用,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

简介

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP,
通过上边的方法,可以把类的调度表(dispatch table)中选择器到最终函数间的映射关系 替换。就相当于把IMP 的只想替换了。

  • swizzling 需要在 + (void)load{}中使用
  • swizzling 需要保证只执行一次。 需要使用 dispatch_once;
  • load 和initialize区别:load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。

使用场景 (1)

需要统计事件,或者需要输出Log的时候,可以使用。比如在delloc中,输出log。告诉我们哪个类释放了。

+ (void)swizzWithClass:(Class)class originSel:(SEL)originSel newSel:(SEL)newSel{

Method originM = class_getInstanceMethod(class, originSel);
Method newM = class_getInstanceMethod(class, newSel);

IMP newImp =  method_getImplementation(newM);

BOOL addMethodSucess = class_addMethod(class, newSel, newImp, method_getTypeEncoding(newM));

if (addMethodSucess) {
    class_replaceMethod(class, originSel, newImp, method_getTypeEncoding(newM));
}else{
    method_exchangeImplementations(originM, newM);
  }
}  

上边这段代码是 封装了,替换,可以直接copy 使用。下面我就进行。delloc的替换,这块,我要说明下,有个小问题。替换 方法的实现很多,这里如果你要替换delloc你,只能这么写。

 Method originalMethod = class_getClassMethod(self, @selector(dealloc));

dealloc 是系统关键字,上边的这种获取方式会报错。 使用封装的方法没事

代码如下

+ (void)load{

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

    [self swizzWithClass:[self class] originSel:NSSelectorFromString(@"dealloc") newSel:@selector(swizz_dealloc)];
});

}

需要保证只执行一次,多次,容易出现不可预知的错误。

- (void)swizz_dealloc{

NSLog(@" ** %@ 释放了 %s",NSStringFromClass([self class]),__func__);

[self swizz_dealloc];
}

这样你控制器的释放一目了然。 思路方法如上,你可以根据你自己的需求修改,来达到目的。

使用场景(2)

数组越界问题 ,空值 。导致程序崩溃的问题。

方法一 通过分类添加安全的索引方法 :

@implementation UIView (safe)
- (BOOL)containsObjectAtIndex:(NSInteger)index {
    return index >= 0 && index <self.count;
}
- (id)objectNilAtIndex:(NSInteger)index{
    return [self containsObjectAtIndex:index] ? [self objectAtIndex:index] : nil;
}
@end

第二种方法。使用Method sizzling

+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    @autoreleasepool {
        [objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectIndex:)];
        [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(arrObjectIndex:)];
        [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectIndex:)];
        [objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)];
    }
});
}

写在 NSArray的类目里边

- (id)emptyObjectIndex:(NSInteger)index{
return nil;}

- (id)arrObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
    return nil;
return [self arrObjectIndex:index];
}

- (id)mutableObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
    return nil;
}
    return [self mutableObjectIndex:index];
}

- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
if (object) {
    [self mutableInsertObject:object atIndex:index];
   }
 }


@implementation NSDictionary (FlyElephant)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    @autoreleasepool {
        [objc_getClass("__NSDictionaryM") swizzleMethod:@selector(setObject:forKey:) swizzledSelector:@selector(mutableSetObject:forKey:)];
        }
    });
}
- (void)mutableSetObject:(id)obj forKey:(NSString *)key{
if (obj && key) {
    [self mutableSetObject:obj forKey:key];
    }
}@end

*这里NSArray0表示一般空数组,NSArrayI表示一般数组,__NSArrayM可变数组,NSArray和NSMutableArray相当于工厂,最终实现通过上面三个类进行实现的,有兴趣的可以自行了解一下类簇.

- (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{
Class class = [self class];

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);
    }
}

上边是 交换方法的封装,可以直接copy拿走直接使用。

通过Runtime进行对数组字典进行方法交换之后,有可能出现不明所以的崩溃 message sent to deallocated instance 0x1459e0600**

通过Runtime进行对数组字典进行方法交换之后,之前对键盘输入的场景没有完全弄清楚,完整过程应该是,文本框输入文字→Home键进入后台→点击App图标重新进入→崩溃,有的时候前两步就直接崩溃了,因此Runtime的这个文件需要通过mrc管理:

一般项目都是 ARC 模式,需要单独问Runtime的这个问题添加MRC支持,
Build Phases -> Compile Sources找到文件设置 -fno-objc-arc 标签。

如果项目是MRC 模式,则为 ARC 模式的代码文件加入 -fobjc-arc 标签.
同时可以为相应的代码块添加autoreleasepool:

@autoreleasepool {
    if (index >= self.count || index < 0) {
        return nil;
    }
    return [self arrObjectIndex:index];
}   

特别说明

*第一种方法。小团队使用, 人少,新员工 入职需要写说明文档。对苹果原生支持不够。
*第二种方法。 多人团队使用。新入职员工,无需说明文档。可以直接调用原生API 。对苹果支持 度高。可扩展性高

使用场景 (3)

还有一种错误,是unrecognized selector sent to class会导致APP崩溃。 要想APP 健壮。需要处理、

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSMethodSignature *signature = [super methodSignatureForSelector:sel];

if (!signature) {
    NSLog(@"*******%@ - %@ ***un reconize selector %s!",NSStringFromClass([self class]),NSStringFromSelector(sel),__func__);
    signature = [NSMethodSignature signatureWithObjCTypes:@encode(void)];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
- }

这里知识设计 。动态方法决议(Dynamic Method Resolution)。和消息转发机制(Message Forwarding)。 有兴趣的同学可以去看看。

最后

以上就是无语芒果为你收集整理的method-swizzling 详解 和使用的全部内容,希望文章能够帮你解决method-swizzling 详解 和使用所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部