AOP (Aspect-oriented programming) 譯爲 「面向切面編程」,是經過預編譯方式和運行期動態代理實現程序功能統一維護的一種技術。利用 AOP 能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。git
Emmmmm...AOP 目前是較爲熱門的一個話題,儘管你也許沒有據說過它,可是你的項目中可能已經滲入了它,例如:用戶統計(不添加一行代碼即實現對全部 ViewController 的跟蹤日誌)。github
對於 iOS 開發者而言,無外乎 Swift 和 Objective-C 兩種主流開發語言:算法
dynamic
修飾符,在 Runtime 沒有留給咱們太多的發揮空間(前幾日新增了 swift-5.0-branch
分支,寫這篇文章時看了一眼 181 commits behind master
😂)。Aspects 做爲 Objective-C 語言編寫的 AOP 庫,適用於 iOS 和 Mac OS X,使用體驗簡單愉快,已經在 GitHub 摘得 5k+ Star。Aspects 內部實現比較健全,考慮到了 Hook 安全方面可能發生的種種問題,很是值得咱們學習。編程
Note: 本文內引用 Aspects 源碼版本爲 v1.4.2,要求讀者具有必定的 Runtime 知識。swift
在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。安全
AOP (Aspect-oriented programming),即 「面向切面編程」 是一種編程範式,或者說是一種編程思想,它解決了 OOP (Object-oriented programming) 的延伸問題。微信
光是給個概念可能初次接觸 AOP 的人仍是沒法 Get 到其中微妙,拿咱們前言中舉的例子🌰,假設隨着咱們所在的公司逐步發展,以前第三方的用戶頁面統計已經不能知足需求了,公司要求實現一個咱們本身的用戶頁面統計。app
嘛~ 咱們來理一下 OOP 思想下該怎麼辦?框架
viewWillAppear:
和 viewWillDisappear:
方法加入時間統計代碼,記錄 ViewController 以及 Router 傳參。你會想,明明 OOP 也能解決問題是否是?不要急,再假設大家公司有多個 App,你被抽調至基礎技術組專門給這些 App 寫通用組件,要把以前實現過的用戶頁面統計從新以通用的形式實現,提供給大家公司全部的 App 使用。ide
MMP,使用標準 OOP 思想貌似無解啊...這個時候就是 AOP 的用武之地了。
這裏簡單給個思路:Hook UIViewController 的 viewWillAppear:
和 viewWillDisappear:
方法,在原方法執行以後記錄須要統計的信息上報便可。
Note: 簡單經過 Method Swizzling 來 Hook 不是不能夠,可是有不少安全隱患!
Aspects 是一個使用起來簡單愉快的 AOP 庫,使用 Objective-C 編寫,適用於 iOS 與 Mac OS X。
Aspects 內部實現考慮到了不少 Hook 可能引起的問題,筆者在看源碼的過程當中摳的比較細,真的是受益不淺。
Aspects 簡單易用,做者經過在 NSObject (Aspects)
分類中暴露出的兩個接口分別提供了對實例和 Class 的 Hook 實現:
@interface NSObject (Aspects)
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
@end
複製代碼
Aspects 支持實例 Hook,相較其餘 Objective-C AOP 庫而言可操做粒度更小,適合的場景更加多樣化。做爲使用者無需進行更多的操做便可 Hook 指定實例或者 Class 的指定 SEL,AspectOptions 參數能夠指定 Hook 的點,以及是否執行一次以後就撤銷 Hook。
Emmmmm...儘管 Aspects 只有不到千行的源碼,可是其內部實現考慮到了不少 Hook 相關的安全問題和其餘細節,對比其餘 Objective-C AOP 開源項目來講 Aspects 更爲健全,因此我本身在扒 Aspects 源碼時也看的比較仔細。
Aspects 內部定義了兩個協議:
此外 Aspects 內部還定義了 4 個類:
以及一個結構體:
_AspectBlock
,充當內部 Block若是你扒一遍源碼,還會發現兩個內部靜態全局變量:
static NSMutableDictionary *swizzledClassesDict;
static NSMutableSet *swizzledClasses;
如今你也許還不能理解爲何要定義這麼多東西,別急~ 咱們後面都會分析到。
按照上面列出的順序,先來介紹一些 Aspects 聲明的協議。
AspectToken 協議旨在讓使用者能夠靈活的註銷以前添加過的 Hook,內部規定遵照此協議的對象須實現 remove
方法。
/// 不透明的 Aspect Token,用於註銷 Hook
@protocol AspectToken <NSObject>
/// 註銷一個 aspect.
/// 返回 YES 表示註銷成功,不然返回 NO
- (BOOL)remove;
@end
複製代碼
AspectInfo 協議旨在規範對一個切面,即 aspect 的 Hook 內部信息的紕漏,咱們在 Hook 時添加切面的 Block 第一個參數就遵照此協議。
/// AspectInfo 協議是咱們塊語法的第一個參數。
@protocol AspectInfo <NSObject>
/// 當前被 Hook 的實例
- (id)instance;
/// 被 Hook 方法的原始 invocation
- (NSInvocation *)originalInvocation;
/// 全部方法參數(裝箱以後的)惰性執行
- (NSArray *)arguments;
@end
複製代碼
Note: 裝箱是一個開銷昂貴操做,因此用到再去執行。
接着協議,咱們下面詳細介紹一下 Aspects 的內部類。
Note: AspectInfo 在這裏是一個 Class,其遵照上文中講到的 AspectInfo 協議,不要混淆。
AspectInfo 類定義:
@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end
複製代碼
Note: 關於裝箱,對於提供一個 NSInvocation 就能夠拿到其
arguments
這一點上,ReactiveCocoa 團隊提供了很大貢獻(細節見 Aspects 內部 NSInvocation 分類)。
AspectInfo 比較簡單,參考 ReactiveCocoa 團隊提供的 NSInvocation 參數通用方法可將參數裝箱爲 NSValue,簡單來講 AspectInfo 扮演了一個提供 Hook 信息的角色。
AspectIdentifier 類定義:
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
複製代碼
Note: AspectIdentifier 其實是添加切面的 Block 的第一個參數,其應該遵循 AspectToken 協議,事實上也的確如此,其提供了
remove
方法的實現。
AspectIdentifier 內部須要注意的是因爲使用 Block 來寫 Hook 中咱們加的料,這裏生成了 blockSignature
,在 AspectIdentifier 初始化的過程當中會去判斷 blockSignature
與入參 object
的 selector
獲得的 methodSignature
的兼容性,兼容性判斷成功纔會順利初始化。
AspectsContainer 類定義:
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end
複製代碼
AspectsContainer 做爲切面的容器類,關聯指定對象的指定方法,內部有三個切面隊列,分別容納關聯指定對象的指定方法中相對應 AspectOption 的 Hook:
NSArray *beforeAspects;
- AspectPositionBeforeNSArray *insteadAspects;
- AspectPositionInsteadNSArray *afterAspects;
- AspectPositionAfter爲何要說關聯呢?由於 AspectsContainer 是在 NSObject 分類中經過 AssociatedObject 方法與當前要 Hook 的目標關聯在一塊兒的。
Note: 關聯目標是 Hook 以後的 Selector,即
aliasSelector
(原始 SEL 名稱加aspects_
前綴對應的 SEL)。
AspectTracker 類定義:
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, weak) AspectTracker *parentEntry;
@end
複製代碼
AspectTracker 做爲切面追蹤器,原理大體以下:
// Add the selector as being modified.
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
複製代碼
Note: 聰明的你應該已經注意到了全局變量
swizzledClassesDict
中的value
對應着 AspectTracker 指針。
嘛~ 就是說 AspectTracker 是從下而上追蹤,最底層的 parentEntry
爲 nil
,父類的 parentEntry
爲子類的 tracker
。
AspectBlockRef,即 struct _AspectBlock
,其定義以下:
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
複製代碼
Emmmmm...沒什麼特別的,你們應該比較眼熟吧。
Note:
__unused
宏定義其實是__attribute__((unused))
GCC 定語,旨在告訴編譯器「若是我沒有在後面使用到這個變量也別警告我」。
嘛~ 想起以前本身挖的坑尚未填,事實上本身也不知道何時填(笑):
不要急~ 你瞧倫家不是都記得嘛(至於何時填坑嘛就...咳咳)
static NSMutableDictionary *swizzledClassesDict;
static NSMutableDictionary *swizzledClassesDict;
在 Aspects 中扮演着已混寫類字典的角色,其內部結構應該是這樣的:
<Class : AspectTracker *>
複製代碼
Aspects 內部提供了專門訪問這個全局字典的方法:
static NSMutableDictionary *aspect_getSwizzledClassesDict() {
static NSMutableDictionary *swizzledClassesDict;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
swizzledClassesDict = [NSMutableDictionary new];
});
return swizzledClassesDict;
}
複製代碼
這個全局變量能夠簡單理解爲記錄整個 Hook 影響的 Class 包含其 SuperClass 的追蹤記錄的全局字典。
static NSMutableSet *swizzledClasses;
static NSMutableSet *swizzledClasses;
在 Aspects 中擔當記錄已混寫類的角色,其內部結構以下:
<NSStringFromClass(Class)>
複製代碼
Aspects 內部提供一個用於修改這個全局變量內容的方法:
static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
static NSMutableSet *swizzledClasses;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
swizzledClasses = [NSMutableSet new];
});
@synchronized(swizzledClasses) {
block(swizzledClasses);
}
}
複製代碼
Note: 注意
@synchronized(swizzledClasses)
。
這個全局變量記錄了 forwardInvocation:
被混寫的的類名稱。
Note: 注意在用途上與
static NSMutableDictionary *swizzledClassesDict;
區分理解。
嘛~ Aspects 的總體實現代碼不超過一千行,並且考慮的狀況也比較全面,很是值得你們花時間去讀一下,這裏我只準備給出本身對其核心代碼的理解。
Aspects 不光支持 Hook Class 還支持 Hook Instance,這提供了更小粒度的控制,配合 Hook 的撤銷功能能夠更加靈活精準的作咱們想作的事~
Aspects 爲了能區別 Class 和 Instance 的邏輯,實現了名爲 aspect_hookClass
的方法,我認爲其中的實現值得我用一部分篇幅來單獨講解,也以爲讀者們有必要花點時間理解這裏的實現邏輯。
static Class aspect_hookClass(NSObject *self, NSError **error) {
// 斷言 self
NSCParameterAssert(self);
// class
Class statedClass = self.class;
// isa
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// 已經子類化過了
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// 咱們混寫了一個 class 對象,而非一個單獨的 object
}else if (class_isMetaClass(baseClass)) {
// baseClass 是元類,則 self 是 Class 或 MetaClass,混寫 self
return aspect_swizzleClassInPlace((Class)self);
// 多是一個 KVO'ed class。混寫就位。也要混寫 meta classes。
}else if (statedClass != baseClass) {
// 當 .class 和 isa 指向不一樣的狀況,混寫 baseClass
return aspect_swizzleClassInPlace(baseClass);
}
// 默認狀況下,動態建立子類
// 拼接子類後綴 AspectsSubclassSuffix
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
// 嘗試用拼接後綴的名稱獲取 isa
Class subclass = objc_getClass(subclassName);
// 找不到 isa,表明尚未動態建立過這個子類
if (subclass == nil) {
// 建立一個 class pair,baseClass 做爲新類的 superClass,類名爲 subclassName
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) { // 返回 nil,即建立失敗
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
// 混寫 forwardInvocation:
aspect_swizzleForwardInvocation(subclass);
// subClass.class = statedClass
aspect_hookedGetClass(subclass, statedClass);
// subClass.isa.class = statedClass
aspect_hookedGetClass(object_getClass(subclass), statedClass);
// 註冊新類
objc_registerClassPair(subclass);
}
// 覆蓋 isa
object_setClass(self, subclass);
return subclass;
}
複製代碼
Note: 其實這裏的難點就在於對
.class
和object_getClass
的區分。
.class
當 target 是 Instance 則返回 Class,當 target 是 Class 則返回自身object_getClass
返回 isa
指針的指向Note: 動態建立一個 Class 的完整步驟也是咱們應該注意的。
嘛~ 難點和重點都講完了,你們結合註釋理解其中的邏輯應該沒什麼困難了,有什麼問題能夠找我一塊兒交流~
在上面 aspect_hookClass
方法中,不只僅是返回一個要 Hook 的 Class,期間還作了一些細節操做,不管是 Class 仍是 Instance,都會調用 aspect_swizzleForwardInvocation
方法,這個方法沒什麼難點,簡單貼一下代碼讓你們有個印象:
static void aspect_swizzleForwardInvocation(Class klass) {
// 斷言 klass
NSCParameterAssert(klass);
// 若是沒有 method,replace 實際上會像是 class_addMethod 同樣
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
// 拿到 originalImplementation 證實是 replace 而不是 add,狀況少見
if (originalImplementation) {
// 添加 AspectsForwardInvocationSelectorName 的方法,IMP 爲原生 forwardInvocation:
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
複製代碼
上面的方法就是把要 Hook 的目標 Class 的 forwardInvocation:
混寫了,混寫以後 forwardInvocation:
的具體實如今 __ASPECTS_ARE_BEING_CALLED__
中,裏面能看到 invoke 標識位的不一樣是如何實現的,還有一些其餘的實現細節:
// 宏定義,以便於咱們有一個更明晰的 stack trace
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
// __unsafe_unretained NSObject *self 不解釋了
// 斷言 self, invocation
NSCParameterAssert(self);
NSCParameterAssert(invocation);
// 從 invocation 能夠拿到不少東西,好比 originalSelector
SEL originalSelector = invocation.selector;
// originalSelector 加前綴獲得 aliasSelector
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
// 用 aliasSelector 替換 invocation.selector
invocation.selector = aliasSelector;
// Instance 的容器
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
// Class 的容器
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks.
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
// 若是有任何 insteadAspects 就直接替換了
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else { // 不然正常執行
// 遍歷 invocation.target 及其 superClass 找到實例能夠響應 aliasSelector 的點 invoke
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// 若是沒有 hook,則執行原始實現(一般會拋出異常)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
// 若是能夠響應 originalForwardInvocationSEL,表示以前是 replace method 而非 add method
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
// 移除 aspectsToRemove 隊列中的 AspectIdentifier,執行 remove
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
#undef aspect_invoke
複製代碼
Note:
aspect_invoke
宏定義的做用域。
aspect_invoke
中 aspectsToRemove
是一個 NSArray,裏面容納着須要被銷戶的 Hook,即 AspectIdentifier(以後會調用 remove
移除)。Aspects 讓咱們在指定 Class 或 Instance 的特定 Selector 執行時,根據 AspectOptions 插入咱們本身的 Block 作 Hook,而這個 Block 內部有咱們想要的有關於當前 Target 和 Selector 的信息,咱們來看一下 Aspects 是怎麼辦到的:
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// 偏執。咱們已經在 hook 註冊的時候檢查過了,(不過這裏咱們還要檢查)。
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
// block 的 `self` 將會是 AspectInfo。可選的。
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
// 簡歷參數分配內存 argBuf 而後從 originalInvocation 取 argument 賦值給 blockInvocation
void *argBuf = NULL;
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
// reallocf 優勢,若是建立內存失敗會自動釋放以前的內存,講究
if (!(argBuf = reallocf(argBuf, argSize))) {
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
[originalInvocation getArgument:argBuf atIndex:idx];
[blockInvocation setArgument:argBuf atIndex:idx];
}
// 執行
[blockInvocation invokeWithTarget:self.block];
// 釋放 argBuf
if (argBuf != NULL) {
free(argBuf);
}
return YES;
}
複製代碼
考慮兩個問題:
[blockInvocation setArgument:&info atIndex:1];
爲何要在索引 1 處插入呢?for (NSUInteger idx = 2; idx < numberOfArguments; idx++)
爲何要從索引 2 開始遍歷參數呢?嘛~ 若是你對 Block 的 Runtime 結構以及執行過程下斷點研究一下就全都明白了,感興趣的同窗有疑問能夠聯繫我(與真正勤奮好學的人交流又有誰會不樂意呢?笑~)
Aspects 使用 NSObject + Categroy 的方式提供接口,很是巧妙的涵蓋了 Instance 和 Class。
Aspects 提供的接口保持高度一致(本着易用,簡單,方便的原則設計接口和整個框架的實現會讓你的開源項目更容易被人們接納和使用):
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
複製代碼
Note: 其實接口這裏對於
block
的參數自動補全能夠更進一步,不過 Aspects 當初是沒有辦法作到的,單從接口設計這塊已經很優秀了。
Aspects 不只支持大部分 AOP 框架應該作到的對於 Class 的 Hook,還支持粒度更小的 Instance Hook,而其在內部實現中爲了支持 Instance Hook 所作的代碼也很是值得咱們參考和學習(已在上文 Aspects 核心代碼剖析 處單獨分析)。
爲使用者提供更爲自由的 Hook 方式以達到更加精準的控制是每一個使用者樂於見到的事。
Aspects 使用 Block 來作 Hook 應該考慮到了不少東西,支持使用者經過在 Block 中獲取到相關的信息,書寫本身額外的操做就能夠實現 Hook 需求。
Aspects 還支持撤銷以前作的 Hook 以及已混寫的 Method,爲了實現這個功能 Aspects 設計了全局容器,把 Hook 和混寫用全局容器作記錄,讓一切均可以復原,這不正是咱們想要的嗎?
嘛~ 咱們在學習 Runtime 的時候,就應該看到過很多文章講解 Method Swizzling 要注意的安全性問題,因爲用到了大量 Runtime 方法,加上 AOP 是面向整個切面的,因此一單發現問題就會比較嚴重,設計的面會比較廣,並且難以調試。
Note: 咱們不能由於容易形成問題就能夠迴避 Method Swizzling,就比如大學老師講到遞歸時強調容易引發循環調用,不少人就在心裏迴避使用遞歸,甚至於很是適合使用遞歸來寫的算法題(這裏指遞歸來寫會易讀寫、易維護)只會用複雜的方式來思考。
文章寫得比較用心(是我我的的原創文章,轉載請註明 lision.me/),若是發現錯誤會優先在個人 我的博客 中更新。若是有任何問題歡迎在個人微博 @Lision 聯繫我~
補充~ 我建了一個技術交流微信羣,想在裏面認識更多的朋友!若是各位同窗對文章有什麼疑問或者工做之中遇到一些小問題均可以在羣裏找到我或者其餘羣友交流討論,期待你的加入喲~
Emmmmm..因爲微信羣人數過百致使不能夠掃碼入羣,因此請掃描上面的二維碼關注公衆號進羣。