Skip to content

Commit

Permalink
Merge pull request steipete#16 from steipete/typed-arguments
Browse files Browse the repository at this point in the history
Typed arguments
  • Loading branch information
steipete committed May 10, 2014
2 parents 00f043f + c220b3b commit 560638e
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 736 deletions.
35 changes: 28 additions & 7 deletions Aspects.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,53 @@ typedef NS_OPTIONS(NSUInteger, AspectOptions) {
};

/// Opaque Aspect Token that allows to deregister the hook.
@protocol Aspect <NSObject>
@protocol AspectToken <NSObject>

/// Deregisters an aspect.
/// @return YES if deregistration is successful, otherwise NO.
- (BOOL)remove;

@end

/// The AspectInfo protocol is the first parameter of our block syntax.
@protocol AspectInfo <NSObject>

/// The instance that is currently hooked.
- (id)instance;

/// The original invocation of the hooked method.
- (NSInvocation *)originalInvocation;

/// All method arguments, boxed. This is lazily evaluated.
- (NSArray *)arguments;

@end

/**
Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second.
Aspects collects all arguments in the `arguments` array. Primitive values will be boxed.
Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe.
*/
@interface NSObject (Aspects)

/// Adds a block of code before/instead/after the current `selector` for a specific class.
/// If you choose `AspectPositionInstead`, the `arguments` array will contain the original invocation as last argument.
///
/// @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<Aspect>)aspect_hookSelector:(SEL)selector
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(void (^)(id instance, NSArray *args))block
usingBlock:(id)block
error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<Aspect>)aspect_hookSelector:(SEL)selector
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(void (^)(id instance, NSArray *args))block
usingBlock:(id)block
error:(NSError **)error;

@end
Expand All @@ -56,6 +74,9 @@ typedef NS_ENUM(NSUInteger, AspectsErrorCode) {
AspectsErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed.
AspectsErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
AspectsErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair.
AspectsErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called.
AspectsErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large.

AspectsErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.
};

Expand Down
Loading

0 comments on commit 560638e

Please sign in to comment.