赞
踩
Method(方法)对应的是objc_method结构体;而objc_method结构体中包含了SEL method_name(方法名),IMP method_imp(方法实现)
// objc_method 结构体
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name; // 方法名
char * _Nullable method_types; // 方法类型
IMP _Nonnull method_imp; // 方法实现
};
Method(方法),SEL(方法名),IMP(方法实现)三者的关系:
在运行中,class(类)
维护了一个method list(方法列表)
来确定消息的正确发送。OC中调用方法叫做发送消息,发送消息前会查找消息,查找过程就是通过SEL查找IMP的过程。method list (方法列表)
存放的元素就是Method(方法
)。而Method(方法)
中映射了一对键值对:SEL(方法名)
:IMP(方法实现)
。
原理:
Method swizzling修改了method list(方法列表),使不同Method(方法)中的键值对发生了交换。比如交换前两个键值对分别为SEL A:IMP A,SEL B:IMP B,交换之后就变为了SEL A : IMP B、SEL B : IMP A。
在当前类的+(void)load
方法中增加Method Swizzling
操作,交换(void)originalFunction
和(void)swizzledFunction
的方法实现。
#import "ViewController.h" #import <objc/runtime.h> @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self SwizzlingMethod]; [self originalFunction]; [self swizzledFunction]; } - (void)SwizzlingMethod { //当前类 Class class = [self class]; //方法名 SEL originalSeletor = @selector(originalFunction); SEL swizzledSeletor = @selector(swizzledFunction); //方法结构体 Method originalMethod = class_getInstanceMethod(class, originalSeletor); Method swizzledMethod = class_getInstanceMethod(class, swizzledSeletor); //调用交换两个方法的实现 method_exchangeImplementations(originalMethod, swizzledMethod); } //原始方法 - (void)originalFunction { NSLog(@"originalFunction"); } //替换方法 - (void)swizzledFunction { NSLog(@"swizzledFunction"); } @end
上面的代码简单的将两个方法进行了交换。但在实际应用中并不是那么简单,更多的是为当前类添加一个分类
,然后在分类中进行MethodSwimming操作,并且要考虑的东西要更多,且更复杂。
一般是在该类的分类中添加MethodSwizzling交换方法
@implementation UIViewController (Swizzling) // 交换 原方法 和 替换方法 的方法实现 + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 当前类 Class class = [self class]; // 原方法名 和 替换方法名 SEL originalSelector = @selector(originalFunction); SEL swizzledSelector = @selector(swizzledFunction); // 原方法结构体 和 替换方法结构体 Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); /* 如果当前类没有 原方法的 IMP,说明在从父类继承过来的方法实现, * 需要在当前类中添加一个 originalSelector 方法, * 但是用 替换方法 swizzledMethod 去实现它 */ BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { // 原方法的 IMP 添加成功后,修改 替换方法的 IMP 为 原始方法的 IMP class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { // 添加失败(说明已包含原方法的 IMP),调用交换两个方法的实现 method_exchangeImplementations(originalMethod, swizzledMethod); } }); } // 原始方法 - (void)originalFunction { NSLog(@"originalFunction"); } // 替换方法 - (void)swizzledFunction { NSLog(@"swizzledFunction"); } @end
一些用到的方法
通过SEL获取方法Method
// 获取实例方法
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
// 获取类方法
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
IMP的getter/setter方法
// 获取一个方法的实现
OBJC_EXPORT IMP _Nonnull
method_getImplementation(Method _Nonnull m);
// 设置一个方法的实现
OBJC_EXPORT IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp):
替换方法
// 获取方法实现的编码类型 OBJC_EXPORT const char * _Nullable method_getTypeEncoding(Method _Nonnull m); // 添加方法实现 OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types); // 替换方法的 IMP,如:A替换B(B指向A,A还是指向A) OBJC_EXPORT IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types); // 交换两个方法的 IMP,如:A交换B(B指向A,A指向B) OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
为了保证方法交换的代码可以优先交换,一般会将其写在+load方法中,但是+load的方法也能被主动调用,如果多次调用就会被还原,如果调用[super load] 方法也会造成这样的结果;所以我们要保证方法只交换一次,可选择在单例模式下。
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self lz_methodSwizzlingWithClass:self oriSEL:@selector(study) swizzledSEL:@selector(play)];
});
}
在子类中用子类的方法subFuntionA替换父类的方法function A。子类实例和父类实例分别调用function A,最终都实现的是subFuntionA。
如果我们在子类的方法subFuntionA1替换了父类中的方法functionA后想要继续调用functionA,同理应该这样写
- (void)subFunctionA {
[self subFunctionA];
NSLog(@"%s", __func__);
}
再用子类实例和父类实例分别调用function A。
父类调用时就会报错,子类调用就不会。
在上面的函数中调用subFuntionA,但父类本身方法列表中没subFuntionA,所以父类也就报了unrecognized selector 的错误。
出现上面找不到方法的原因是:子类用自己的实现直接替换了父类的方法。
如果我们能不能为子类添加一个和父类一样的方法,子类中进行替换就不会影响父类了。
+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel { if (!cls) { return; } Method oldM = class_getInstanceMethod(cls, oldSel); Method newM = class_getInstanceMethod(cls, newSel); // 先尝试给 cls 添加方法(SEL: oldSel IMP: newM),防止子类直接替换父类中的方法 BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM)); if (addSuccess) { // 添加成功即:原本没有 oldSel,成功为子类添加了一个 oldSel - newM 的方法 // 这里将原 newSel的imp替换为 oldM 的 IMP class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM)); } else { method_exchangeImplementations(oldM, newM); } }
class_addMethod
为当前类添加functionA方法,关联subFuntionA方法的impclass_replaceMethod
将sub functionA替换为functionA的imp再用子类实例和父类实例分别调用function A。
这时父类实例调用functionA没有受到子类方法交换的影响,实现的就是functionA。
而子类实例就会在实现subfunctionA中实现function A。
对于UITableView的异常加载情况分为无数据或网络异常。
对于检测tableView是否为空,借助tableView的代理dataSource即可。核心代码是,依次获取table View所具有的组数和行数,通过isEmpty这个flag标示最后确定是否添加占位图。
- (void)checkEmpty { BOOL isEmpty = YES;//flag标示 id dataSource = self.dataSource; NSInteger sections = 1;//默认一组 if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { sections = [dataSource numberOfSectionsInTableView:self];//获取当前TableView组数 } for (NSInteger i = 0; i < sections; i++) { NSInteger rows = [dataSource tableView:self numberOfRowsInSection:sections];//获取当前TableView各组行数 if (rows) { isEmpty = NO;//若行数存在,不为空 } } if (isEmpty) {//若为空,加载占位图 if (!self.placeholderView) {//若未自定义,展示默认占位图 [self makeDefaultPlaceholderView]; } self.placeholderView.hidden = NO; [self addSubview:self.placeholderView]; } else {//不为空,隐藏占位图 self.placeholderView.hidden = YES; } }
接下来实现如何添加占位图
如果可以让tableView在执行reloadData
时自动检查其行数就可以了。也就是我们在原有的reload Data
方法的基础上添加了checkEmpty
此方法。这里我们可以通过MethodSwizzling
替换reload Data方法,给予它新的实现。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//方法交换,将reloadData实现交换为sure_reloadData
[self methodSwizzlingWithOriginalSelector:@selector(reloadData) bySwizzledSelector:@selector(sure_reloadData)];
});
}
- (void)sure_reloadData {
[self checkEmpty];
[self sure_reloadData];
}
这样就可以在实现reloadData的同时检查行数从而判断我是否加载占位图的功能。
具体实现demo
tableView的异常加载占位图
避免一个按钮被快速点击多次。同样利用Method Swizzling
#import "UIButton+TBCustom.h" #import <objc/runtime.h> @interface UIButton() @property (nonatomic, assign) NSTimeInterval custom_acceptEventInterval; // 可以用这个给重复点击加间隔 @end @implementation UIButton (TBCustom) + (void)load{ Method systemMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:)); SEL sysSEL = @selector(sendAction:to:forEvent:); Method customMethod = class_getInstanceMethod(self, @selector(custom_sendAction:to:forEvent:)); SEL customSEL = @selector(custom_sendAction:to:forEvent:); //添加方法 语法:BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 若添加成功则返回No // cls:被添加方法的类 name:被添加方法方法名 imp:被添加方法的实现函数 types:被添加方法的实现函数的返回值类型和参数类型的字符串 BOOL didAddMethod = class_addMethod(self, sysSEL, method_getImplementation(customMethod), method_getTypeEncoding(customMethod)); //如果系统中该方法已经存在了,则替换系统的方法 语法:IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types) if (didAddMethod) { class_replaceMethod(self, customSEL, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod)); }else{ method_exchangeImplementations(systemMethod, customMethod); } } - (NSTimeInterval )custom_acceptEventInterval{ return [objc_getAssociatedObject(self, "UIControl_acceptEventInterval") doubleValue]; } - (void)setCustom_acceptEventInterval:(NSTimeInterval)custom_acceptEventInterval{ objc_setAssociatedObject(self, "UIControl_acceptEventInterval", @(custom_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSTimeInterval )custom_acceptEventTime{ return [objc_getAssociatedObject(self, "UIControl_acceptEventTime") doubleValue]; } - (void)setCustom_acceptEventTime:(NSTimeInterval)custom_acceptEventTime{ objc_setAssociatedObject(self, "UIControl_acceptEventTime", @(custom_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)custom_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{ // 如果想要设置统一的间隔时间,可以在此处加上以下几句 // 值得提醒一下:如果这里设置了统一的时间间隔,只会影响UIButton, 如果想统一设置,也想影响UISwitch,建议将UIButton分类,改成UIControl分类,实现方法是一样的 if (self.custom_acceptEventInterval <= 0) { // 如果没有自定义时间间隔,则默认为.4秒 self.custom_acceptEventInterval = .4; } // 是否小于设定的时间间隔 BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.custom_acceptEventTime >= self.custom_acceptEventInterval); // 更新上一次点击时间戳 if (self.custom_acceptEventInterval > 0) { self.custom_acceptEventTime = NSDate.date.timeIntervalSince1970; } // 两次点击的时间间隔小于设定的时间间隔时,才执行响应事件 if (needSendAction) { [self custom_sendAction:action to:target forEvent:event]; } }
addTarget:action:forControlEvents: 方法将 buttonTapped: 方法与按钮的点击事件关联起来。当用户点击按钮时,按钮会调用 sendAction:to:forEvent: 方法,并将 buttonTapped: 方法作为动作发送给指定的目标对象(在这里是 self,即当前对象)
Method Swizzling 可以用于解决数组越界导致的崩溃问题。通过交换 NSArray 或 NSMutableArray 的方法实现,我们可以在访问数组元素之前进行边界检查,以防止越界访问。
#import <objc/runtime.h> @implementation NSArray (SafeAccess) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(objectAtIndex:); SEL swizzledSelector = @selector(safe_objectAtIndex:); 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); } }); } - (id)safe_objectAtIndex:(NSUInteger)index { if (index < self.count) { return [self safe_objectAtIndex:index]; } else { NSLog(@"Array index out of bounds: %lu", (unsigned long)index); return nil; } } @end
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。