前言
最近公司在做一個用戶行為統(tǒng)計(jì),現(xiàn)在這家公司是一家金融公司,想統(tǒng)計(jì)一些用戶行為,比如用戶輸入身份證的時間,次數(shù),是否拷貝等等,通過這個使用相應(yīng)的算法,結(jié)合用戶的一些信用資料來對用戶的額度以及還款能力做一些決策,目前有兩種方案:1,通過子父類實(shí)現(xiàn);2,通過分類實(shí)現(xiàn).這二者各有優(yōu)點(diǎn),也有缺點(diǎn),最后選擇的方案應(yīng)該是子父類繼承的方案,這篇文章編寫的目的主要在于記錄一下使用分類時用到的runtime的一些技巧,方便日后學(xué)習(xí)和使用.
好文章記載
1方法替換/攔截,給分類(對象)動態(tài)綁定屬性,獲得一個類的所有屬性,方法,實(shí)現(xiàn)NSCoding自動歸檔解檔,模型和字典的自動轉(zhuǎn)換:鏈接.
2Runtime一些數(shù)據(jù)類型名稱的解釋:鏈接
3使用Runtime做到根據(jù)后臺返回做到跳轉(zhuǎn)不同界面:鏈接
4冰大的Runtime文章,偏底層:鏈接
用戶行為統(tǒng)計(jì)分類的一個簡單實(shí)現(xiàn)
基本上看文章一就好了,基本能滿足日常使用的一般需求了.其他的有需求的話可以仔細(xì)看看
代碼:
頭文件
#import <UIKit/UIKit.h>
@interface UIButton (category)
//獲取button的點(diǎn)擊次數(shù)
@property (nonatomic, assign) NSInteger btnClickedCount;
@property (nonatomic, copy) void (^currentActionBlock)() ;
@end
實(shí)現(xiàn)文件
#import "UIButton+category.h"
#import <objc/runtime.h>
#import <objc/message.h>
NSString * const fy_btnClickedCountKey = nil;
NSString * const fy_btnCurrentActionBlockKey = nil;
@implementation UIButton (category)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//SEL是selector在Objc中的表示類型
SEL origilaSEL = @selector(addTarget:action:forControlEvents:);
SEL hookSEL = @selector(fy_addTarget:action:forControlEvents:);
//交換方法
//class_getInstanceMethod獲取對象方法,class_getClassMethod獲取類方法
Method origilalMethod = class_getInstanceMethod(self, origilaSEL);
Method hookMethod = class_getInstanceMethod(self, hookSEL);
//可以直接用這一句交換不用下面的方法
// method_exchangeImplementations(origilalMethod, hookMethod);
/**
動態(tài)添加方法
@param self 給哪個類添加方法
@param origilaSEL 添加方法的方法編號(方法名字)
@param self 實(shí)現(xiàn)這個方法的函數(shù)
@param origilaSEL 一個定義該函數(shù)返回值類型和參數(shù)類型的字符串
@return <#return value description#>
*/
class_addMethod(self, origilaSEL, class_getMethodImplementation(self, origilaSEL), method_getTypeEncoding(origilalMethod));
class_addMethod(self, hookSEL, class_getMethodImplementation(self, hookSEL), method_getTypeEncoding(hookMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origilaSEL), class_getInstanceMethod(self, hookSEL));
});
}
- (void)fy_addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents{
__weak typeof(target) weakTarget = target;
__weak typeof(self) weakSelf = self;
//利用 關(guān)聯(lián)對象 給UIButton 增加了一個 block
if (action) {
[self setCurrentActionBlock:^{
@try {
((void (*)(void *, SEL, typeof(weakSelf) ))objc_msgSend)((__bridge void *)(weakTarget), action , weakSelf);
} @catch (NSException *exception) {
} @finally {
}
}];
}
//發(fā)送消息 其實(shí)是本身 要執(zhí)行的action 先執(zhí)行,寫下來的 fy_clicked:方法
//為什么這么寫而不是用官方的方法呢?因?yàn)榻?jīng)過上面的方法交換,下面的這個方法已經(jīng)相當(dāng)于系統(tǒng)的addTarget方法了,如果再使用addTarget那么是無法使用系統(tǒng)的方法了
[self fy_addTarget:self action:@selector(fy_clicked:) forControlEvents:controlEvents];
}
//攔截了按鈕點(diǎn)擊后要執(zhí)行的代碼
- (void)fy_clicked:(UIButton *)sender{
//統(tǒng)計(jì) 在這個方法中執(zhí)行想要操作的
self.btnClickedCount++;
NSLog(@"%@ 點(diǎn)擊 %ld次 ",[sender titleForState:UIControlStateNormal], self.btnClickedCount);
//執(zhí)行原來要執(zhí)行的方法
sender.currentActionBlock();
}
//增加一個 block 關(guān)聯(lián)UIButton
- (void)setCurrentActionBlock:(void (^)())currentActionBlock{
objc_setAssociatedObject(self, &fy_btnCurrentActionBlockKey, currentActionBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^)())currentActionBlock{
return objc_getAssociatedObject(self, &fy_btnCurrentActionBlockKey);
}
#pragma mark -統(tǒng)計(jì)
//在分類中增加了 btnClickedCount的 (setter 和 getter)方法殖演,使用關(guān)聯(lián)對象增加了相關(guān)的成員空間,注意這里的名字必須符合蘋果那一套命名規(guī)則
- (NSInteger)btnClickedCount{
//根據(jù)關(guān)聯(lián)的key獲取到關(guān)聯(lián)的值
id tmp = objc_getAssociatedObject(self, &fy_btnClickedCountKey);
NSNumber *number = tmp;
return number.integerValue;
}
- (void)setBtnClickedCount:(NSInteger)btnClickedCount{
/**
添加關(guān)聯(lián)
@param self 給哪個對象添加關(guān)聯(lián)
@param fy_btnClickedCountKey 關(guān)聯(lián)的key,通過這個key獲取
@param btnClickedCount 關(guān)聯(lián)的值
@param OBJC_ASSOCIATION_ASSIGN關(guān)聯(lián)的策略
@return <#return value description#>
*/
objc_setAssociatedObject(self, &fy_btnClickedCountKey, @(btnClickedCount), OBJC_ASSOCIATION_ASSIGN);
}
@end
關(guān)聯(lián)策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
OBJC_ASSOCIATION_ASSIGN 指定關(guān)聯(lián)對象的弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC 指定關(guān)聯(lián)對象的強(qiáng)引用,這個關(guān)聯(lián)不是原子性的
OBJC_ASSOCIATION_COPY_NONATOMIC 指定復(fù)制關(guān)聯(lián)的對象,這個關(guān)聯(lián)不是原子性的
OBJC_ASSOCIATION_RETAIN 指定關(guān)聯(lián)對象的強(qiáng)引用,這個關(guān)聯(lián)是原子性的
OBJC_ASSOCIATION_COPY 指定復(fù)制關(guān)聯(lián)的對象,這個關(guān)聯(lián)是原子性的