1、Method Swizzling(动态方法交换)
Method Swizzling用于改变一个已存在的selector实现。我们可以在程序运行时,通过改变selector所在Class(类)的method list(方法列表)的映射从而改变方法的调用。其实质就是交换两个方法的IMP(方法实现)。
2、Method Swizzling应用场景
2.1、全局页面统计功能
#import "UIViewController+Swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
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 {
if (![self isKindOfClass:[UIViewController class]]) { // 剔除系统 UIViewController
// 添加统计代码
NSLog(@"进入页面:%@", [self class]);
}
[self xxx_viewWillAppear:animated];
}
@end
2.2字体根据屏幕尺寸适配
#import "UIFont+AdjustSwizzling.h"
#import <objc/runtime.h>
#define XXX_UISCREEN_WIDTH 375
@implementation UIFont (AdjustSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(systemFontOfSize:);
SEL swizzledSelector = @selector(xxx_systemFontOfSize:);
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);
}
});
}
+ (UIFont *)xxx_systemFontOfSize:(CGFloat)fontSize {
UIFont *newFont = nil;
newFont = [UIFont xxx_systemFontOfSize:fontSize * [UIScreen mainScreen].bounds.size.width / XXX_UISCREEN_WIDTH];
return newFont;
}
@end
2.3处理按钮重复点击
#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>
@interface UIButton()
// 重复点击间隔
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
@end
@implementation UIButton (DelaySwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(sendAction:to:forEvent:);
SEL swizzledSelector = @selector(xxx_sendAction:to:forEvent:);
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)xxx_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 如果想要设置统一的间隔时间,可以在此处加上以下几句
if (self.xxx_acceptEventInterval <= 0) {
// 如果没有自定义时间间隔,则默认为 0.4 秒
self.xxx_acceptEventInterval = 0.4;
}
// 是否小于设定的时间间隔
BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
// 更新上一次点击时间戳
if (self.xxx_acceptEventInterval > 0) {
self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
}
// 两次点击的时间间隔小于设定的时间间隔时,才执行响应事件
if (needSendAction) {
[self xxx_sendAction:action to:target forEvent:event];
}
}
- (NSTimeInterval )xxx_acceptEventInterval{
return [objc_getAssociatedObject(self, "UIControl_acceptEventInterval") doubleValue];
}
- (void)setXxx_acceptEventInterval:(NSTimeInterval)xxx_acceptEventInterval{
objc_setAssociatedObject(self, "UIControl_acceptEventInterval", @(xxx_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimeInterval )xxx_acceptEventTime{
return [objc_getAssociatedObject(self, "UIControl_acceptEventTime") doubleValue];
}
- (void)setXxx_acceptEventTime:(NSTimeInterval)xxx_acceptEventTime{
objc_setAssociatedObject(self, "UIControl_acceptEventTime", @(xxx_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
2.4、TableView、CollectionView异常加载占位图
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UITableView (ReloadDataSwizzling)
@property (nonatomic, assign) BOOL firstReload;
@property (nonatomic, strong) UIView *placeholderView;
@property (nonatomic, copy) void(^reloadBlock)(void);
@end
/*--------------------------------------*/
#import "UITableView+ReloadDataSwizzling.h"
#import "XXXPlaceholderView.h"
#import <objc/runtime.h>
@implementation UITableView (ReloadDataSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(reloadData);
SEL swizzledSelector = @selector(xxx_reloadData);
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)xxx_reloadData {
if (!self.firstReload) {
[self checkEmpty];
}
self.firstReload = NO;
[self xxx_reloadData];
}
- (void)checkEmpty {
BOOL isEmpty = YES; // 判空 flag 标示
id <UITableViewDataSource> dataSource = self.dataSource;
NSInteger sections = 1; // 默认TableView 只有一组
if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
sections = [dataSource numberOfSectionsInTableView:self] - 1; // 获取当前TableView 组数
}
for (NSInteger i = 0; i <= sections; i++) {
NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i]; // 获取当前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;
}
}
- (void)makeDefaultPlaceholderView {
self.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
XXXPlaceholderView *placeholderView = [[XXXPlaceholderView alloc] initWithFrame:self.bounds];
__weak typeof(self) weakSelf = self;
[placeholderView setReloadClickBlock:^{
if (weakSelf.reloadBlock) {
weakSelf.reloadBlock();
}
}];
self.placeholderView = placeholderView;
}
- (BOOL)firstReload {
return [objc_getAssociatedObject(self, @selector(firstReload)) boolValue];
}
- (void)setFirstReload:(BOOL)firstReload {
objc_setAssociatedObject(self, @selector(firstReload), @(firstReload), OBJC_ASSOCIATION_ASSIGN);
}
- (UIView *)placeholderView {
return objc_getAssociatedObject(self, @selector(placeholderView));
}
- (void)setPlaceholderView:(UIView *)placeholderView {
objc_setAssociatedObject(self, @selector(placeholderView), placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void (^)(void))reloadBlock {
return objc_getAssociatedObject(self, @selector(reloadBlock));
}
- (void)setReloadBlock:(void (^)(void))reloadBlock {
objc_setAssociatedObject(self, @selector(reloadBlock), reloadBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
2.5、数组,字典赋值重写拦截防止nil奔溃
dic
#import "NSDictionary+Safe.h"
#import <objc/runtime.h>
@implementation NSDictionary (Safe)
+ (void)load {
Method originalMethod = class_getClassMethod(self, @selector(dictionaryWithObjects:forKeys:count:));
Method swizzledMethod = class_getClassMethod(self, @selector(na_dictionaryWithObjects:forKeys:count:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (instancetype)na_dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt {
id nObjects[cnt];
id nKeys[cnt];
int i=0, j=0;
for (; i<cnt && j<cnt; i++) {
if (objects[i] && keys[i]) {
nObjects[j] = objects[i];
nKeys[j] = keys[i];
j++;
}
}
return [self na_dictionaryWithObjects:nObjects forKeys:nKeys count:j];
}
@end
@implementation NSMutableDictionary (Safe)
+ (void)load {
Class dictCls = NSClassFromString(@"__NSDictionaryM");
Method originalMethod = class_getInstanceMethod(dictCls, @selector(setObject:forKey:));
Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(na_setObject:forKey:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)na_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
if (!anObject || !aKey)
return;
[self na_setObject:anObject forKey:aKey];
}
@end
array
#import "NSArray+Safe.h"
#import <objc/runtime.h>
@implementation NSArray (Safe)
+ (void)load {
Method originalMethod = class_getClassMethod(self, @selector(arrayWithObjects:count:));
Method swizzledMethod = class_getClassMethod(self, @selector(na_arrayWithObjects:count:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (instancetype)na_arrayWithObjects:(const id [])objects count:(NSUInteger)cnt {
id nObjects[cnt];
int i=0, j=0;
for (; i<cnt && j<cnt; i++) {
if (objects[i]) {
nObjects[j] = objects[i];
j++;
}
}
return [self na_arrayWithObjects:nObjects count:j];
}
@end
@implementation NSMutableArray (Safe)
+ (void)load {
Class arrayCls = NSClassFromString(@"__NSArrayM");
Method originalMethod1 = class_getInstanceMethod(arrayCls, @selector(insertObject:atIndex:));
Method swizzledMethod1 = class_getInstanceMethod(arrayCls, @selector(na_insertObject:atIndex:));
method_exchangeImplementations(originalMethod1, swizzledMethod1);
Method originalMethod2 = class_getInstanceMethod(arrayCls, @selector(setObject:atIndex:));
Method swizzledMethod2 = class_getInstanceMethod(arrayCls, @selector(na_setObject:atIndex:));
method_exchangeImplementations(originalMethod2, swizzledMethod2);
}
- (void)na_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (!anObject)
return;
[self na_insertObject:anObject atIndex:index];
}
- (void)na_setObject:(id)anObject atIndex:(NSUInteger)index {
if (!anObject)
return;
[self na_setObject:anObject atIndex:index];
}
@end
3、修改私有属性
3.1、更改UITextField占位文字的颜色和字号
// 打印 UITextfield 的所有属性和成员变量
- (void)printUITextFieldList {
unsigned int count;
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"ivar(%d) : %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
objc_property_t *propertyList = class_copyPropertyList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"propertyName(%d) : %@", i, [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
// 通过修改 UITextfield 的私有属性更改占位颜色和字体
- (void)createLoginTextField {
UITextField *loginTextField = [[UITextField alloc] init];
loginTextField.frame = CGRectMake(15,(self.view.bounds.size.height-52-50)/2, self.view.bounds.size.width-60-18,52);
loginTextField.delegate = self;
loginTextField.font = [UIFont systemFontOfSize:14];
loginTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
loginTextField.textColor = [UIColor blackColor];
loginTextField.placeholder = @"用户名/邮箱";
[loginTextField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
[loginTextField setValue:[UIColor lightGrayColor]forKeyPath:@"_placeholderLabel.textColor"];
[self.view addSubview:loginTextField];
}
3.2、万能控制器跳转
// 定义的规则
NSDictionary *params = @{
@"class" : @"XXViewController",
@"property" : @{
@"ID" : @"123",
@"type" : @"XXViewController1"
}
};
然后,添加一个工具类 XXJumpControllerTool,添加跳转相关的类方法。
/********************* XXJumpControllerTool.h 文件 *********************/
#import <Foundation/Foundation.h>
@interface XXJumpControllerTool : NSObject
+ (void)pushViewControllerWithParams:(NSDictionary *)params;
@end
/********************* XXJumpControllerTool.m 文件 *********************/
#import "XXJumpControllerTool.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@implementation XXJumpControllerTool
+ (void)pushViewControllerWithParams:(NSDictionary *)params {
// 取出控制器类名
NSString *classNameStr = [NSString stringWithFormat:@"%@", params[@"class"]];
const char *className = [classNameStr cStringUsingEncoding:NSASCIIStringEncoding];
// 根据字符串返回一个类
Class newClass = objc_getClass(className);
if (!newClass) {
// 创建一个类
Class superClass = [NSObject class];
newClass = objc_allocateClassPair(superClass, className, 0);
// 注册你创建的这个类
objc_registerClassPair(newClass);
}
// 创建对象(就是控制器对象)
id instance = [[newClass alloc] init];
NSDictionary *propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 检测这个对象是否存在该属性
if ([XXJumpControllerTool checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用 KVC 对控制器对象的属性赋值
[instance setValue:obj forKey:key];
}
}];
// 跳转到对应的控制器
[[XXJumpControllerTool topViewController].navigationController pushViewController:instance animated:YES];
}
// 检测对象是否存在该属性
+ (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName {
unsigned int count, i;
// 获取对象里的属性列表
objc_property_t *properties = class_copyPropertyList([instance class], &count);
for (i = 0; i < count; i++) {
objc_property_t property =properties[i];
// 属性名转成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判断该属性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}
// 获取当前显示在屏幕最顶层的 ViewController
+ (UIViewController *)topViewController {
UIViewController *resultVC = [XXJumpControllerTool _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
while (resultVC.presentedViewController) {
resultVC = [XXJumpControllerTool _topViewController:resultVC.presentedViewController];
}
return resultVC;
}
+ (UIViewController *)_topViewController:(UIViewController *)vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [XXJumpControllerTool _topViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [XXJumpControllerTool _topViewController:[(UITabBarController *)vc selectedViewController]];
} else {
return vc;
}
return nil;
}
@end
3.3改进iOS归档和解档
// 解档
- (instancetype)xx_modelInitWithCoder:(NSCoder *)aDecoder {
if (!aDecoder) return self;
if (!self) {
return self;
}
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id value = [aDecoder decodeObjectForKey:name];
[self setValue:value forKey:name];
}
free(propertyList);
return self;
}
// 归档
- (void)xx_modelEncodeWithCoder:(NSCoder *)aCoder {
if (!aCoder) return;
if (!self) {
return;
}
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id value = [self valueForKey:name];
[aCoder encodeObject:value forKey:name];
}
free(propertyList);
}
然后在需要实现归档解档的模型中,添加 -initWithCoder: 和 -encodeWithCoder: 方法。
#import "XXPerson.h"
#import "NSObject+XXModel.h"
@implementation XXPerson
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self xx_modelInitWithCoder:aDecoder];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[self xx_modelEncodeWithCoder:aCoder];
}
@end
3.4、按钮的重复点击
+ (void)load{
Method originalMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(User_SendAction:to:forEvent:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
#pragma mark -- 时间间隔 --
static const void *ButtonDurationTime = @"ButtonDurationTime";
- (NSTimeInterval)durationTime{
NSNumber *number = objc_getAssociatedObject(self, &ButtonDurationTime);
return number.doubleValue;
}
- (void)setDurationTime:(NSTimeInterval)durationTime{
NSNumber *number = [NSNumber numberWithDouble:durationTime];
objc_setAssociatedObject(self, &ButtonDurationTime, number, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void)User_SendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
self.userInteractionEnabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.durationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.userInteractionEnabled = YES;
});
[self User_SendAction:action to:target forEvent:event];
}
3.5、字典转模型
网友评论