涉及到的知識(shí)點(diǎn)有:消息轉(zhuǎn)發(fā)TODO,Method-SwizzlingTODO,動(dòng)態(tài)方法TODO,KVO(暫無(wú)),AOP等。
下載地址
概述
Aspects是一個(gè)輕量級(jí)的、支持Objective-C&Swift語(yǔ)言的AOP實(shí)現(xiàn)。可以把Aspects當(dāng)做Method-SwizzlingTODO的一種應(yīng)用。它允許你在每個(gè)類或每個(gè)實(shí)例已有方法中添加代碼,可以設(shè)置切入點(diǎn)為after /instead/before等。比起常規(guī)的Method-SwizzlingTODO,Aspects會(huì)自動(dòng)調(diào)用super方法,并且使用起來(lái)更加簡(jiǎn)單、方便。
Aspects利用OC的消息轉(zhuǎn)發(fā)機(jī)制,hook消息。這樣會(huì)有一些性能開銷,因此不要把Aspects加到經(jīng)常被使用的方法里面。Aspects是用來(lái)設(shè)計(jì)給View/Controller代碼使用的,而不是用來(lái)hook每秒調(diào)用1000次的方法的。因此Aspects不應(yīng)該被用在for循環(huán)這些方法里面,會(huì)造成很大的性能損耗。
Aspects是不支持hook 靜態(tài)static方法的。
這里還有一些簡(jiǎn)介需要翻譯,后續(xù)補(bǔ)上。TODO
使用方法
Aspects是NSObject的一個(gè)extension,只要是NSObject,都可以使用這兩個(gè)方法。一個(gè)是用來(lái)hook類方法的,一個(gè)是hook實(shí)例方法的。
/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
添加Aspects后,會(huì)返回一個(gè)AspectToken,用來(lái)注銷hook方法。所有的調(diào)用都是線程安全的。
/// Deregister an aspect.
/// @return YES if deregistration is successful, otherwise NO.
id<AspectToken> aspect = ...;
[aspect remove];
這里的AspectToken是隱式的,允許我們調(diào)用remove去撤銷一個(gè)hook。remove方法返回YES代表撤銷成功,返回NO就撤銷失敗。
Hook方法的參數(shù)
| 參數(shù)名 | 類型 | 含義 |
|---|---|---|
| selector | SEL | 要Hook的方法,即增加切面的原方法 |
| options | AspectOptions | 切入點(diǎn) |
| block | id | 這個(gè)block復(fù)制了正在被hook的方法的簽名signature類型。 |
| error | NSError ** | 返回的錯(cuò)誤 |
block第一個(gè)參數(shù)遵循AspectInfo協(xié)議。我們甚至可以使用一個(gè)空的block。AspectInfo協(xié)議里面的參數(shù)是可選的,主要是用來(lái)匹配block簽名的。
AspectOptions類型:
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default);默認(rèn)值,在原方法執(zhí)行完之后調(diào)用block
AspectPositionInstead = 1, /// Will replace the original implementation.替換原方法
AspectPositionBefore = 2, /// Called before the original implementation.在原方法之前調(diào)用block
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.在hook執(zhí)行完后自動(dòng)移除。
};
AspectInfo對(duì)象:
/// The AspectInfo protocol is the first parameter of our block syntax.
@protocol AspectInfo <NSObject>
/// The instance that is currently hooked.
/// 返回當(dāng)前被hook的實(shí)例。
- (id)instance;
/// The original invocation of the hooked method.
/// 返回被hooked方法的原始的Invocation。
- (NSInvocation *)originalInvocation;
/// All method arguments, boxed. This is lazily evaluated.
/// 返回selector方法的所有參數(shù)。是懶加載實(shí)現(xiàn)的。
- (NSArray *)arguments;
@end
錯(cuò)誤碼類型:
typedef NS_ENUM(NSUInteger, AspectErrorCode) {
AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted.
AspectErrorDoesNotRespondToSelector, /// Selector could not be found.
AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed.
AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair.
AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called.
AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large.
AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.
};
extern NSString *const AspectErrorDomain;