1.runtime的作用
- 字典轉(zhuǎn)模型
- 動(dòng)態(tài)修改成員變量
- 方法交換
- 給分類添加屬性
2.字典轉(zhuǎn)模型
#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];
//處理服務(wù)器字段與本地model字段不匹配問題
if ([propertyName isEqualToString:@"age3"]) {//處理本地與網(wǎng)絡(luò)字符不匹配的問題
propertyValue = dic[@"age"];
}
if (propertyValue != nil) {
[myObj setValue:propertyValue forKey:propertyName];
}
}
//注意在runtime獲取屬性的時(shí)候淑蔚,并不是ARC Objective-C的對(duì)象所有需要釋放
free(arrPropertys);
return myObj;
}
@end
3.動(dòng)態(tài)修改成員變量
- 需求1:修改UITextView的PlaceHolder的TextColor(http://www.reibang.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 風(fēng)險(xiǎn),謹(jǐn)慎使用!并不是所有KVC都會(huì)Crash,要嘗試!
if ([[UIDevice currentDevice].systemVersion floatValue] > 13.0) {
//獲取一個(gè)成員變量,根據(jù)名稱獲取
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調(diào)用一次,在APP運(yùn)行時(shí)調(diào)用.+ (void)load. 方法只要加入了工程種蜜自,進(jìn)行了編譯,且.m中實(shí)現(xiàn)了這個(gè)方法卢佣,都會(huì)調(diào)用一次重荠,值得注意的時(shí)沒實(shí)現(xiàn)的子類是不會(huì)調(diào)用的,就算父類實(shí)現(xiàn)了也不行
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 類簇:NSString珠漂、NSArray晚缩、NSDictionary,真實(shí)類型是其他類型
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;//如果是空對(duì)象媳危,就不添加荞彼,防止奔潰
//攔截之后,再調(diào)用系統(tǒng)的實(shí)現(xiàn)待笑,由于不知道底層實(shí)現(xiàn)邏輯鸣皂,自己實(shí)現(xiàn)系統(tǒng)方法可能會(huì)出各種意想不到的錯(cuò)
[self jf_insertObject:anObject atIndex:index];//這兒看似死循環(huán),細(xì)理邏輯暮蹂,方法實(shí)現(xiàn)已經(jīng)交換過了的
}
@end
- 需求二 打印控制器的訪問次數(shù)
#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: 要求項(xiàng)目中的按鈕,不可以連續(xù)點(diǎn)擊多次
#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>
@interface UIButton()
// 重復(fù)點(diǎn)擊間隔
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
// 上一次點(diǎn)擊時(shí)間戳
@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 {
// 如果想要設(shè)置統(tǒng)一的間隔時(shí)間寞缝,可以在此處加上以下幾句
if (self.xxx_acceptEventInterval <= 0) {
// 如果沒有自定義時(shí)間間隔,則默認(rèn)為 0.4 秒
self.xxx_acceptEventInterval = 0.4;
}
// 是否小于設(shè)定的時(shí)間間隔
BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
// 更新上一次點(diǎn)擊時(shí)間戳
if (self.xxx_acceptEventInterval > 0) {
self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
}
// 兩次點(diǎn)擊的時(shí)間間隔小于設(shè)定的時(shí)間間隔時(shí)仰泻,才執(zhí)行響應(yīng)事件
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
{
//注意最后一個(gè)參數(shù)的類型
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_COPY);
}
- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd);
}
@end
需求二: 給UIButton添加一個(gè)block處理點(diǎn)擊事件
(1)寫法1
#import <UIKit/UIKit.h>
#import <objc/runtime.h> // 導(dǎo)入頭文件
// 聲明一個(gè)button點(diǎn)擊事件的回調(diào)block
typedef void(^ButtonClickCallBack)(UIButton *button);
@interface UIButton (Handle)
// 為UIButton增加的回調(diào)方法
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;
@end
#import "UIButton+Handle.h"
// 聲明一個(gè)靜態(tài)的索引key荆陆,用于獲取被關(guān)聯(lián)對(duì)象的值
static char *buttonClickKey;
@implementation UIButton (Handle)
- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
// 將button的實(shí)例與回調(diào)的block通過索引key進(jìn)行關(guān)聯(lián):
objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 設(shè)置button執(zhí)行的方法
[self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonClicked {
// 通過靜態(tài)的索引key,獲取被關(guān)聯(lián)對(duì)象(這里就是回調(diào)的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