iOS开发 runtime应用
2020-12-14 本文已影响0人
喜剧收尾_XWX
1.runtime的作用
- 字典转模型
- 动态修改成员变量
- 方法交换
- 给分类添加属性
2.字典转模型
#import "NSObject+Json.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (Json)
+ (instancetype)xwx_initWithDictionaryForModel:(NSDictionary *)dic{
id myObj = [[self alloc] init];
unsigned int outCount;
//获取类中的所有成员属性
objc_property_t *arrPropertys = class_copyPropertyList([self class], &outCount);
for (NSInteger i = 0; i < outCount; i ++) {
//获取属性名字符串
objc_property_t property = arrPropertys[i];
//model中的属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
id propertyValue = dic[propertyName];
//处理服务器字段与本地model字段不匹配问题
if ([propertyName isEqualToString:@"age3"]) {//处理本地与网络字符不匹配的问题
propertyValue = dic[@"age"];
}
if (propertyValue != nil) {
[myObj setValue:propertyValue forKey:propertyName];
}
}
//注意在runtime获取属性的时候,并不是ARC Objective-C的对象所有需要释放
free(arrPropertys);
return myObj;
}
@end
3.动态修改成员变量
- 需求1:修改UITextView的PlaceHolder的TextColor(https://www.jianshu.com/p/e0d46032b27f)
#import "UITextField+PlaceHolder.h"
#import <objc/runtime.h>
@implementation UITextField (PlaceHolder)
-(void)changePlaceHolderTextColor:(UIColor *)color{
//这块就是为了打印出所有的成员变量,获取成员变量的名
unsigned int count;
//获取UITextField中的所有成员变量
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
// NSLog(@"成员变量%s", ivar_getName(ivar));
}
free(ivars);
// iOS 13 通过 KVC 方式修改私有属性,有 Crash 风险,谨慎使用!并不是所有KVC都会Crash,要尝试!
if ([[UIDevice currentDevice].systemVersion floatValue] > 13.0) {
//获取一个成员变量,根据名称获取
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
//object_getIvar获取成员变量的值
UILabel *placeholderLabel = object_getIvar(self, ivar);
placeholderLabel.textColor = color;
}else{
//kvc赋值,iOS13之前用kvc赋值就可以哦
[self setValue:color forKeyPath:@"_placeholderLabel.textColor"];
}
}
@end
- 需求二:给UITextView添加PlaceHolder
#import "UITextView+PlaceHolder.h"
#import <objc/runtime.h>
@implementation UITextView (PlaceHolder)
-(void)SetPlaceHolderTextColor:(UIColor *)color fontSize:(CGFloat)font textContent:(NSString *)text{
UILabel *placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];
placeHolderLabel.text = text;
placeHolderLabel.numberOfLines = 0;
placeHolderLabel.textColor = color;
placeHolderLabel.font = [UIFont systemFontOfSize:font];
[placeHolderLabel sizeToFit];
if (self.text.length == 0) {
[self addSubview:placeHolderLabel];//这句很重要不要忘了
}
[self setValue:placeHolderLabel forKey:@"_placeholderLabel"];
}
@end
值得注意的是,在iOS13之后使用KVC给成员变量赋值有可能崩溃,要试
4. 方法交换
- 需求1 NSMutableArray *array [array insertObject:nil atIndex:0]防止崩溃
//
// NSMutableArray+Extension.m
// runtime
//
// Created by eport on 2020/12/13.
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>
//方法交换
@implementation NSMutableArray (Extension)
//类方法,只在runtime调用一次,在APP运行时调用.+ (void)load. 方法只要加入了工程种,进行了编译,且.m中实现了这个方法,都会调用一次,值得注意的时没实现的子类是不会调用的,就算父类实现了也不行
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(jf_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)jf_insertObject:(id)anObject atIndex:(NSUInteger)index{
if (anObject == nil) return;//如果是空对象,就不添加,防止奔溃
//拦截之后,再调用系统的实现,由于不知道底层实现逻辑,自己实现系统方法可能会出各种意想不到的错
[self jf_insertObject:anObject atIndex:index];//这儿看似死循环,细理逻辑,方法实现已经交换过了的
}
@end
- 需求二 打印控制器的访问次数
#import "UIViewController+Logging.h"
#import <objc/runtime.h>
@implementation UIViewController (Logging)
+ (void)load
{
swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}
- (void)swizzled_viewDidAppear:(BOOL)animated
{
// call original implementation
[self swizzled_viewDidAppear:animated];
// Logging
NSLog(@"%@", NSStringFromClass([self class]));
}
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
- 需求3: 要求项目中的按钮,不可以连续点击多次
#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>
@interface UIButton()
// 重复点击间隔
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
// 上一次点击时间戳
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventTime;
@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
5.给分类添加属性
- 需求1 :给Catagory 添加属性
//.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (AssociatedObject)
@property(nonatomic,copy)NSString *associatedObject;
@end
NS_ASSUME_NONNULL_END
//.m文件
import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>
@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)associatedObject
{
//注意最后一个参数的类型
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_COPY);
}
- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd);
}
@end
需求二: 给UIButton添加一个block处理点击事件
(1)写法1
#import <UIKit/UIKit.h>
#import <objc/runtime.h> // 导入头文件
// 声明一个button点击事件的回调block
typedef void(^ButtonClickCallBack)(UIButton *button);
@interface UIButton (Handle)
// 为UIButton增加的回调方法
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;
@end
#import "UIButton+Handle.h"
// 声明一个静态的索引key,用于获取被关联对象的值
static char *buttonClickKey;
@implementation UIButton (Handle)
- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
// 将button的实例与回调的block通过索引key进行关联:
objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 设置button执行的方法
[self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonClicked {
// 通过静态的索引key,获取被关联对象(这里就是回调的block)
ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);
if (callBack) {
callBack(self);
}
}
@end
(2)写法二
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
typedef void(^ButtonClickCallBack)(UIButton * _Nullable button);
NS_ASSUME_NONNULL_BEGIN
@interface UIButton (Handler)
@property(nonatomic,copy)ButtonClickCallBack bottonCallBack;
@end
NS_ASSUME_NONNULL_END
#import "UIButton+Handler.h"
@implementation UIButton (Handler)
- (void)setCallBlock:(ButtonClickCallBack)bottonCallBack
{
objc_setAssociatedObject(self, @selector(callBlock), bottonCallBack, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (ButtonClickCallBack )callBlock
{
return objc_getAssociatedObject(self, _cmd);
// return objc_getAssociatedObject(self, @selector(callBlock));
}
@end