赞
踩
OC 的函数是属于动态调用,在编译的时候是不能决定真正去调用那个函数的,只有在运行的时候才能决定去调用哪一个函数 ,在编译阶段,OC可以调用任何的函数,即使这个函数没有实现,只要声明过也就不会报错。
Method Swizzle执行点击事件,我们利用runtime来方法交换,来达到防止重复点击的目的:
//UIButton添加一个Category
//.h文件
@interface UIButton (YY_FixMultiClick)
@property (nonatomic, assign) NSTimeInterval yy_acceptEventInterval; // 时间间隔
@property (nonatomic, assign) NSTimeInterval yy_acceptEventTime;
@end
注意:Category不能给类添加属性, 所以以上的yy_acceptEventInterval和yy_acceptEventTime只会有对应的getter和setter方法, 不会添加真正的成员变量.
如果我们不在实现文件中添加其getter和setter方法, 则采用*** btn.yy_acceptEventInterval = 0.5; *** 这种方法访问程序会崩溃.
2019-07-15 08:30:52.538 RuntimeDemo[17380:1387981] -[UIButton setYy_acceptEventInterval:]: unrecognized selector sent to instance 0x7fe8e198e880
因此:实现文件中通过runtime的关联对象的方式, 为UIButton添加以上两个属性.
#import "UIControl+YY_FixMultiClick"
#import <objc/runtime.h>
@implementation UIButton (YY_FixMultiClick)
// 因category不能添加属性,只能通过关联对象的方式。
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";
- (NSTimeInterval)yy_acceptEventInterval {
return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}
- (void)setYy_acceptEventInterval:(NSTimeInterval)yy_acceptEventInterval {
objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(yy_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";
- (NSTimeInterval)yy_acceptEventTime {
return [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
}
- (void)setYy_acceptEventTime:(NSTimeInterval)yy_acceptEventTime {
objc_setAssociatedObject(self, UIControl_acceptEventTime, @(yy_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
//文件:.m
// 在load时执行
+ (void)load {
[super load];
Method normal = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method newMethod = class_getInstanceMethod(self, @selector(yy_sendAction:to:forEvent:));
method_exchangeImplementations(normal, newMethod);
}
- (void)yy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if ([NSDate date].timeIntervalSince1970 - self.yy_acceptEventTime < self.yy_acceptEventInterval) {
return;
}
if (self.yy_acceptEventInterval > 0) {
self.yy_acceptEventTime = [NSDate date].timeIntervalSince1970;
}
[self yy_sendAction:action to:target forEvent:event];
}
btn.yy_acceptEventInterval = 0.5f; //UIButton指定了0.5s的时间间隔用于防止重复点击.
为NSArray添加Category代码如下:
//
// NSArray+Crash.m
// YMallShop
//
// Created by bruce on 2018/8/8.
// Copyright © 2018年 bruce. All rights reserved.
//
#import "NSArray+Crash.h"
@implementation NSArray (Crash)
+ (void)load{
[super load];
//替换不可变数组方法
Method oldObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method newObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtSafeIndex:));
method_exchangeImplementations(oldObjectAtIndex, newObjectAtIndex);
//替换可变数组方法
Method oldMutableObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndex:));
Method newMutableObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(mutableObjectAtSafeIndex:));
method_exchangeImplementations(oldMutableObjectAtIndex, newMutableObjectAtIndex);
}
- (id)objectAtSafeIndex:(NSUInteger)index {
if (index > self.count - 1 || !self.count) {
@try {
return [self objectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self objectAtSafeIndex:index];
}
}
- (id)mutableObjectAtSafeIndex:(NSUInteger)index{
if (index > self.count - 1 || !self.count) {
@try {
return [self mutableObjectAtSafeIndex:index];
}
@catch (NSException *exception) {
NSLog(@"exception: %@", exception.reason);
return nil;
}
}else {
return [self mutableObjectAtSafeIndex:index];
}
}
@end
//
// UIViewController+Swizzling.m
// qzy
//
// Created by BruceYao on 2017/1/17.
// Copyright © 2017年 BruceYao. All rights reserved.
//
#import "UIViewController+Swizzling.h"
@implementation UIViewController (Swizzling)
+ (void)load {
// 只在开发模式下,才会出现交换
#ifdef DEBUG
// 原来的viewWillAppear方法
Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
// 需要替换成,能够输出的日志的viewWillAppear
Method logViewWillAppear = class_getInstanceMethod(self, @selector(logViewWillAppear:));
// 两个方法进行交换
method_exchangeImplementations(viewWillAppear, logViewWillAppear);
// 原来的viewWillDisappear方法
Method viewWillDisappear = class_getInstanceMethod(self, @selector(viewWillDisappear:));
// 需要替换成,能够输出的日志的viewWillAppear
Method logViewWillDisAppear = class_getInstanceMethod(self, @selector(logViewWillDisAppear:));
// 两个方法进行交换
method_exchangeImplementations(viewWillDisappear, logViewWillDisAppear);
#endif
}
- (void)logViewWillAppear:(BOOL)animater {
NSString *className = NSStringFromClass([self class]);
//[MobClick beginLogPageView:className]; 统计的时候会用到、友盟统计
NSLog(@"%@ will appear", className);
}
- (void)logViewWillDisAppear:(BOOL)animater {
NSString *className = NSStringFromClass([self class]);
//[MobClick endLogPageView:className];统计的时候会用到
}
@end
###场景四:归档、反归档
#import "EncodeAndUnEncode.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation EncodeAndUnEncode
//解
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int count = 0;
Ivar * ivarlist = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar alvar = ivarlist[i];
const char * name = ivar_getName(alvar);
id value = [aDecoder decodeObjectForKey:[NSString stringWithUTF8String:name]];
if value != nil {
[self setValue:value forKey:[NSString stringWithUTF8String:name]];
}
}
free(ivarlist);
}
return self;
}
//归
-(void)encodeWithCoder:(NSCoder *)aCoder{
//获取某个类的成员变量
unsigned int count = 0;
Ivar * ivarlist = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i ++) {
Ivar alvar = ivarlist[i];
//获取成员变量的名称
const char * ivarname = ivar_getName(alvar);
id value = [self valueForKey:[NSString stringWithUTF8String:ivarname]];
if value != nil {
[aCoder encodeObject:value forKey:[NSString stringWithUTF8String:ivarname]];
}
}
free(ivarlist);
}
开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
调用:
Person *per = [[Person alloc] init];
per.name = [[NSString alloc] initWithFormat:@"张三"];
[per eat];
objc_msgSend(per, @selector(eat));
[per performSelector:@selector(sleeping)];
对应的类中:Person,或者在分类中添加也可以
void sleeping(id self, SEL sel) {
NSLog(@"%@ %@", self, NSStringFromSelector(sel));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sleeping)) {
// type: 方法类型:void用v来表示,id参数用@来表示,SEL用:来表示
class_addMethod(self, @selector(sleeping), sleeping, "v@:");
}
return [super resolveInstanceMethod:sel];
}
注意点:在工程配置页面中:Enable Strict Checking of objc_msgSend Calls 设置为NO即可,要不然会引起crash
Person *per = [[Person alloc] init];
per.name = [[NSString alloc] initWithFormat:@"张三"];
[per eat];
objc_msgSend(per, @selector(eat));
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。