概述
简介
在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 详解 和使用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复