換了一家新公司钳榨,用OC寫的,前期還沒安排任務特恬,自己學一些相關(guān)知識
Method Swizzling原理:
用于改變一個已經(jīng)存在的 selector 實現(xiàn)庄撮。我們可以在程序運行時,通過改變 selector 所在 Class(類)的 method list(方法列表)的映射從而改變方法的調(diào)用戳粒。其實質(zhì)就是交換兩個方法的 IMP(方法實現(xiàn))
一般寫在Category里路狮,下面寫示例:
#import <UIKit/UIKit.h>
//這里是.h
NS_ASSUME_NONNULL_BEGIN
@interface UIImage (Category)
@property (nonatomic,strong) UIColor * defaultColor;
+ (UIImage *)jg_imageNamed:(NSString *)name;
+ (Class)jg_class;
@end
NS_ASSUME_NONNULL_END
//這里是.m
#import "UIImage+Category.h"
#import <objc/runtime.h>
@implementation UIImage (Category)
static char kDefaultColorKey;
@dynamic defaultColor;
- (void) setDefaultColor:(UIColor *)defaultColor {
objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (UIColor *)defaultColor {
return objc_getAssociatedObject(self, &kDefaultColorKey);
}
+(void)load{
[super load];
/* swizzling應該只在+load中完成。 在 Objective-C 的運行時中蔚约,每個類有兩個方法都會自動調(diào)用奄妨。+load 是在一個類被初始裝載時調(diào)用,+initialize 是在應用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的苹祟。兩個方法都是可選的砸抛,并且只有在方法被實現(xiàn)的情況下才會被調(diào)用。
swizzling應該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態(tài)树枫,所以我們需要確保每個預防措施在運行時都是可用的直焙。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次砂轻。Grand Central Dispatch 的 dispatch_once滿足了所需要的需求奔誓,并且應該被當做使用swizzling 的初始化單例方法的標準。*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//當前類
Class class = object_getClass((id)self);
//原始類和替換類
SEL originalSelector = @selector(class);
SEL swizzleSelector = @selector(jg_class);
//原始方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
Method orginalMethod = class_getClassMethod(class, originalSelector);
Method swizzleMethod = class_getClassMethod(class, swizzleSelector);
/* 如果當前類沒有 原方法的 IMP搔涝,說明在從父類繼承過來的方法實現(xiàn)厨喂,
需要在當前類中添加一個 originalSelector 方法,
但是用 替換方法 swizzledMethod 去實現(xiàn)它
*/
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
// 原方法的 IMP 添加成功后庄呈,修改 替換方法的 IMP 為 原始方法的 IMP
class_replaceMethod(class, swizzleSelector, method_getImplementation(orginalMethod), method_getTypeEncoding(orginalMethod));
}else{
// 添加失斖苫汀(說明已包含原方法的 IMP),調(diào)用交換兩個方法的實現(xiàn)
method_exchangeImplementations(orginalMethod, swizzleMethod);
}
});
}
+(UIImage *)jg_imageNamed:(NSString *)name{
UIImage *image = [UIImage jg_imageNamed:name];
if (image) {
NSLog(@"runtime添加額外功能---加載成功");
}else{
NSLog(@"runtime添加額外功能---加載失敗");
}
return image;
}
+ (Class)jg_class {
NSLog(@"runtime Method Swizzling成功");
Class c = [UIImage jg_class];
return c;
}
@end
普通需要注意的點寫在的注釋里诬留,其他的再說一下
擴展類方法需要用
Method orginalMethod = class_getClassMethod(class, originalSelector);
擴展實例方法需要用
Method originalMethod = class_getInstanceMethod(class, originalSelector);
在如下代碼中幌绍,如果我們這么寫颁褂,且替換父類的方法比如UIImage父類的+(Class)class方法故响,可能會造成閃退
Class class = object_getClass((id)self);
//原始類和替換類
SEL originalSelector = @selector(class);
SEL swizzleSelector = @selector(jg_class);
//原始方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
Method orginalMethod = class_getClassMethod(class, originalSelector);
Method swizzleMethod = class_getClassMethod(class, swizzleSelector);
method_exchangeImplementations(orginalMethod, swizzleMethod);
Class class = object_getClass((id)self);
//原始類和替換類
SEL originalSelector = @selector(class);
SEL swizzleSelector = @selector(jg_class);
//原始方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
Method orginalMethod = class_getClassMethod(class, originalSelector);
Method swizzleMethod = class_getClassMethod(class, swizzleSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector,method_getImplementation(swizzleMethod),method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
// 原方法的 IMP 添加成功后傀广,修改 替換方法的 IMP 為 原始方法的 IMP
class_replaceMethod(class, swizzleSelector, method_getImplementation(orginalMethod), method_getTypeEncoding(orginalMethod));
}else{
// 添加失敗(說明已包含原方法的 IMP)彩届,調(diào)用交換兩個方法的實現(xiàn)
method_exchangeImplementations(orginalMethod, swizzleMethod);
}
首先伪冰,父類調(diào)換用class方法會執(zhí)行子類中的jg_class方法實現(xiàn)。
然后又調(diào)用了jg_class方法樟蠕,但是贮聂,此時的調(diào)用者是NSObject對象,父類NSObject中并沒有jg_class方法實現(xiàn)寨辩。所以因方法找不到而報錯閃退吓懈。
在如下代碼中
+(UIImage *)jg_imageNamed:(NSString *)name{
UIImage *image = [UIImage jg_imageNamed:name];
if (image) {
NSLog(@"runtime添加額外功能---加載成功");
}else{
NSLog(@"runtime添加額外功能---加載失敗");
}
return image;
}
+ (Class)jg_class {
NSLog(@"runtime Method Swizzling成功");
Class c = [UIImage jg_class];
return c;
}
看樣子都有循環(huán)調(diào)用的代碼:
+(UIImage *)jg_imageNamed:(NSString *)name{
UIImage *image = [UIImage jg_imageNamed:name];
和
+ (Class)jg_class {
NSLog(@"runtime Method Swizzling成功");
Class c = [UIImage jg_class];
但是為什么沒有遞歸調(diào)用導致閃退呢?
這是因為進行方法交換后靡狞,在執(zhí)行[UIImage imageNamed:name];時耻警,實際上找到的是jg_imageNamed:的方法實現(xiàn),而jg_imageNamed:方法實現(xiàn)中又執(zhí)行[UIImage jg_imageNamed:name];甸怕,同樣是因為方法交換甘穿,此時jg_imageNamed的方法實現(xiàn)也已經(jīng)指向了imageNamed:,所以并不會引起遞歸調(diào)用梢杭。相反温兼,如果我們在 jg_imageNamed方法中調(diào)用了[self imageNamed:name]才是會引起遞歸調(diào)用的,小伙伴們一定要注意N淦酢D寂小!