Copy a method IMP for multiple method swizzles - objective-c

I have a class set up that ideally will read the methods of any class passed in and then map all of them to on single selector at runtime before forwarding them off to their original selector.
This does work right now, but I can only do it for one method at a time. The issue seems to be that once I swizzle the first method, my IMP to catch and forward the method has now been swapped with that other methods IMP. Any further attempts at this screw up because they use newly swapped IMP to replace the others.
1)So I have MethodA, MethodB, and CustomCatchAllMethod.
2)I swap MethodA with CustomCatchAllMEthod. MethodA->CustomCatchAllMethod, CustomCatchAllMethod->MethodA
3)Now I try to swap to MethodB with CustomCatchAllMethod as well, but since CustomCatchAllMethod now = MethodA, MethodB becomes MethodA and MethodA->MethodB.
So how do I get/copy a new instance of my IMP for each new selector I want to intercept?
Here's a rough mockup of the above flow:
void swizzle(Class classImCopying, SEL orig){
SEL new = #selector(catchAll:);
Method origMethod = class_getInstanceMethod(classImCopying, orig);
Method newMethod = class_getInstanceMethod(catchAllClass,new);
method_exchangeImplementations(origMethod, newMethod);
}
//In some method elsewhere
//I want this to make both methodA and methodB point to catchAll:
swizzle(someClass, #selector(methodA:));
swizzle(someClass, #selector(methodB:));

That common method-swizzling pattern only works when you want to intercept one method with one other. In your case you are basically moving the implementation for catchAll: around instead of inserting it everywhere.
To properly to this you'd have to use:
IMP imp = method_getImplementation(newMethod);
method_setImplementation(origMethod, imp);
This leaves you with one problem though: how to forward to the original implementation?
That is what the original pattern used exchangeImplementations for.
In your case you could:
keep a table of the original IMPs around or
rename the original methods with some common prefix, so you can build a call to them from catchAll:
Note that you can only handle methods of the same arity when you want to forward everything through the same method.

You can capture original IMP with block, get block's IMP and set it as implementation of method.
Method method = class_getInstanceMethod(class, setterSelector);
SEL selector = method_getName(method);
IMP originalImp = method_getImplementation(method);
id(^block)(id self, id arg) = ^id(id self, id arg) {
return ((id(*)(id, SEL, id))originalImp)(self, selector, arg);
};
IMP newImp = imp_implementationWithBlock(block);
method_setImplementation(method, newImp);

Related

Too few arguments to function call, expected at least 2, have 0

I wrote the following code:
#interface TestClass : NSObject
//
-(void)testLog;
//
+(void)testLog;
#end
//===============================
SEL sel = #selector(testLog);
IMP imp = class_getMethodImplementation([TestClass class], sel);
imp();
//===============================
SEL testSel = #selector(testLog);
IMP testImp = class_getMethodImplementation(objc_getMetaClass(class_getName([TestClass class])), testSel);
testImp();
I set the Enable Strict Checking of objc_msgSend Calls to NO but still have this error。
Why is it wrong?
All Methods take a minimum of two arguments: a reference to the object (or class in the case of a class method) the method is being called on, this is the value of self within the method body; and the selector of the method. This is what is missing in your calls.
You can see this in definition of IMP given in the documentation is:
id (*IMP)(id, SEL, ...)
With the explanation:
This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
HTH

Calling original function from swizzled function

I am messing around with method swizzling and would like to call the original function after performing a method_exchangeImplementations. I have two projects I have setup for this.
The first project is the main project for the application. This project includes all of the logic for the application. Notice that originalMethodName is called when the view loads.
#implementation ViewController
- (void)originalMethodName
{
NSLog(#"REAL %s", __func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"REAL %s", __func__);
[self originalMethodName];
}
#end
The second project includes only the code for swizzling. I have a method swizzle_originalMethodName which includes the code I want to inject into the main application with the originalMethodName function is called.
#implementation swizzle_ViewController
- (void)swizzle_originalMethodName
{
NSLog(#"FAKE %s", __func__);
}
__attribute__((constructor)) static void initializer(void)
{
NSLog(#"FAKE %s", __func__);
Class c1 = objc_getClass("ViewController");
Class c2 = [swizzle_ViewController class];
Method m1 = class_getInstanceMethod(c1, #selector(originalMethodName));
Method m2 = class_getInstanceMethod(c2, #selector(swizzle_originalMethodName));
method_exchangeImplementations(m1, m2);
}
#end
The swizzle is working just fine (as seen in the output below), but now I want to be able to call originalMethodName from the swizzle_originalMethodName
2016-08-17 14:18:51.765 testMacOS[7295:1297055] FAKE initializer
2016-08-17 14:18:51.822 testMacOS[7295:1297055] REAL -[ViewController viewDidLoad]
2016-08-17 14:18:51.822 testMacOS[7295:1297055] FAKE -[swizzle_ViewController swizzle_originalMethodName]
I have tried to use NSInvocation but am not having any luck. Any ideas what I am doing wrong?
Class c1 = objc_getClass("ViewController");
Method m1 = class_getInstanceMethod(c1, #selector(originalMethodName));
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding( m1)];
NSInvocation *originalInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[originalInvocation invoke];
If you are swizzling within a class hierarchy, e.g. you have a subclass which swizzles one of its ancestors methods with one of its own, then you simply have the swizzled-in method apparently call itself – that call will actually call the swizzled-out method as the methods have been swapped. In your case you would have:
- (void)swizzle_originalMethodName
{
NSLog(#"FAKE %s", __func__);
[self swizzle_originalMethodName]; // call original
}
This does not work in your case as you are cross-class swizzling, so self doesn't reference the class with the swizzled-out method. And you don't have an instance of the swizzling class you can call the swizzled-out method on...
Here is one easy way to fix this, what your swizzle-in method needs to be able to do is call the original implementation, and you can get that when you setup the swizzling.
In Objective-C a method is implemented by a function whose first two arguments are the object reference the method is being called on and the selector and the remaining arguments are those of the method. For example the NSString method:
- (NSRange)rangeOfString:(NSString *)aString
is implemented by a function something like:
NSRange rangeOfStringImp(NSString *self, SEL cmd, NSString *aString)
You can obtain a function pointer to this implementation function using method_getImplementation.
To your code, first in your swizzle_ViewController declare a type for the implementation function of the method you are swizzling, and a global to store the function pointer:
typedef void (*OriginalImpType)(id self, SEL selector);
static OriginalImpType originalImp;
Now in your initializer method you need to save the method implementation, you can do this by adding the line shown:
Method m1 = class_getInstanceMethod(c1, #selector(originalMethodName));
originalImp = (OriginalImpType)method_getImplementation(m1); // save the IMP of originalMethodName
Finally have your swizzled-in method call the saved implementation:
- (void)swizzle_originalMethodName
{
NSLog(#"FAKE %s", __func__);
originalImp(self, #selector(originalMethodName)); // call the original IMP with the correct self & selector
}
Optional: The above works correctly, however it does a little more than is required – the method implementations are both exchanged and one is stored in a global variable, all you really need to do is save the original implementation of m1 and then set its implementation to that of m2. You can address this by replacing the call to method_exchangeImplementations with:
method_setImplementation(m1, method_getImplementation(m2));
It is a little more typing, but somewhat clearer as to what actually needs to be done.
HTH
There is a slightly easier option to call the original implementation that doesn't require you to store the method implementation directly. When you exchange implementations of the methods, the original implementation will be stored in the swizzler class. You can fetch the swizzled out implementation using the class_getMethodImplementation function. Here is a playground sample:
import Cocoa
let fooSelector = Selector("fooWithArg:")
let swizzledFooSelector = Selector("swizzled_fooWithArg:")
class A: NSObject {
#objc dynamic func foo(arg: String) {
print("Foo \(arg) in A")
}
}
class B: NSObject {
private typealias FooFunc = #convention(c) (AnyObject, Selector, String) -> Void
#objc func swizzled_foo(arg: String) {
print("Swizzled_foo \(arg) in B")
unsafeBitCast(
class_getMethodImplementation(B.self, swizzledFooSelector),
to: FooFunc.self
)(self, fooSelector, arg)
}
}
method_exchangeImplementations(
class_getInstanceMethod(A.self, fooSelector)!,
class_getInstanceMethod(B.self, swizzledFooSelector)!
)
A().foo(arg: "bar")

Proper way of method swizzling in objective-C

Currently experimenting with method swizzling in Objective-C and I have a question. I am trying to understand the proper way to method swizzle and after researching online I stumbled upon this NSHipster post:
http://nshipster.com/method-swizzling/
In the post the author has some method swizzling sample code. I am looking for someone to better explain to me what the author is doing.. In particular I am confused on the didAddMethod logic. Why is the author not just directly swapping/exchanging method implementations? My only theory on this is maybe there is some off chance that viewWillAppear: is not added to UIViewController's method dispatch_table yet. Particularly if maybe the category is loaded into memory first before UIViewController... Is this the reason why? It seems rather odd? Just looking for some more insight/clarity, thanks :)
In particular I am confused on the didAddMethod logic. Why is the author not just directly swapping/exchanging method implementations?
Your confusion is understandable as this logic is not explained clearly.
First ignore the fact that the example is a category on the specific class UIViewController and just consider the logic as though the category was on some arbitrary class, let's call that class TargetClass.
We'll call the existing method we wish to replace existingMethod.
The category, being on TargetClass, adds the swizzling method, which we'll call swizzlingMethod, to TargetClass.
Important: Note that the function to get an method, class_getInstanceMethod, will find the method in the supplied class or any of its superclasses. However the functions class_addMethod and class_replaceMethod only add/replace methods in the supplied class.
Now there are two cases to consider:
TargetClass itself directly contains an implementation of existingMethod. This is the easy case, all that needs to be done is exchange the implementations of existingMethod and swizzlingMethod, which can be done with method_exchangeImplementations. In the article the call to class_addMethod will fail, as there is already and existingMethod directly in TargetClass and the logic results in a call to method_exchangeImplementations.
TargetClass does not directly contain an implementation of existingMethod, rather that method is provided through inheritance from one of the ancestor classes of TargetClass. This is the trickier case. If you simply exchange the implementations of existingMethod and swizzlingMethod then you would be effecting (instances of) the ancestor class (and in a way which could cause a crash - why is left as an exercise). By calling class_addMethod the article's code makes sure there is an existingMethod in TargetClass - the implementation of which is the original implementation of swizzlingMethod. The logic then replaces the implementation of swizzlingMethod with the implementation of the ancestor's existingMethod (which has no effect on the ancestor).
Still here? I hope that makes sense and hasn't simply sent you cross-eyed!
Another exercise if you're terminally curious: Now you might ask what happens if the ancestor's existingMethod implementation contains a call to super... if the implementation is now also attached to swizzlingMethod in TargetClass where will that call to super end up? Will it be to implementation in ancestor, which would see the same method implementation executed twice, or to the ancestor's ancestor, as originally intended?
HTH
load is called when a class is added in obj-c runtime.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/#//apple_ref/occ/clm/NSObject/load
So let's say if a UIViewController gets added in obj-c runtime which already contains viewWillAppear: but you want it to be replaced by another implementation. So first you add a new method xxxWillAppear:.
Now once xxxWillAppear: has been added in ViewController class, only then you can replace it.
But the author also said :
For example, let’s say we wanted to track how many times each view controller is presented to a user in an iOS app
so he is trying to demonstrate a case where an app might have many view controllers but you do not want to keep replacing for each ViewController the viewWillAppear: implementation. Once the point of viewWillAppear: has been replaced, then instead of adding, only the exchange will need to be done.
Perhaps source code of Objective C runtime might help :
/**********************************************************************
* addMethod
* fixme
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, BOOL replace)
{
IMP result = nil;
rwlock_assert_writing(&runtimeLock);
assert(types);
assert(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = _method_getImplementation(m);
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)_calloc_internal(sizeof(*newlist), 1);
newlist->entsize_NEVER_USE = (uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdup(types);
if (!ignoreSelector(name)) {
newlist->first.imp = imp;
} else {
newlist->first.imp = (IMP)&_objc_ignored_method;
}
attachMethodLists(cls, &newlist, 1, NO, NO, YES);
result = nil;
}
return result;
}
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
rwlock_write(&runtimeLock);
IMP old = addMethod(cls, name, imp, types ?: "", NO);
rwlock_unlock_write(&runtimeLock);
return old ? NO : YES;
}
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
rwlock_write(&runtimeLock);
IMP old = addMethod(cls, name, imp, types ?: "", YES);
rwlock_unlock_write(&runtimeLock);
return old;
}
You can dig more if you want:
http://www.opensource.apple.com/source/objc4/objc4-437/

Objective-C, how can i hook up a method in another class

Objective-C keeps all its methods in a huge hashtable - so shouldn't it possible to patch this table and replace an existing method with my own patched method (which then calls the original)?
I need a way to hook up the NSWindow KeyUp method in a window which i can't subclass cause it's already created.
I need some code or at least some keywords i can use for further searching.
You should NOT swizzle methods for this. This is deprecated behavior. This will affect ALL windows in your app not just the one you wanted to change. However, what you should do instead is to subclass NSWindow already and then change the class of that window at runtime. This can be done using this runtime function:
Class object_setClass(id object, Class cls)
Reference is here: http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/doc/uid/TP40001418-CH1g-SW12
Your code should then look like this:
object_setClass(theWindow, [MyWindowSubclass class]);
On problem you might experience is that window already being a subclass of NSWindow. If that's the case there are more complicated ways to achieve this. You can construct a class dynamically at runtime. Here's some more code. Given that window is the target window:
Class newWindowClass = objc_allocateClassPair([window class], "MyHackyWindowSubclass", 0);
Method upMethod = class_getInstanceMethod(newWindowClass, #selector(keyUp:));
method_setImplementation(upMethod, new_NSWindow_keyUp_);
object_setClass(window, newWindowClass);
I'm not totally sure this does not change the implementation of the superclass. The documentation is a bit unspecific about it. However, you should still try it. If it does not work, replace the second and third line by this one:
class_replaceMethod(newWindowClass, #selector(keyUp:), new_NSWindow_keyUp_, "v#:#");
In any case you need to define the new Method implementation. It could look like that (partially by KennyTM):
void new_NSWindow_keyUp_(NSWindow* self, SEL _cmd, NSEvent* evt) {
[super keyUp: evt];
... // do your changes
}
Of course it is possible. In fact, you don't even need to look into the hash table — there's standard API for this.
For example:
typedef void (*NSWindow_keyUp__IMP)(NSWindow* self, SEL _cmd, NSEvent* evt);
static NSWindow_keyUp__IMP original_NSWindow_keyUp_;
void replaced_NSWindow_keyUp_(NSWindow* self, SEL _cmd, NSEvent* evt) {
NSLog(#"Entering keyUp:. self = %#, event = %#", self, evt);
original_NSWindow_keyUp_(self, _cmd, evt);
NSLog(#"Leaving keyUp:. self = %#, event = %#", self, evt);
}
...
Method m = class_getInstanceMethod([NSWindow class], #selector(keyUp:));
original_NSWindow_keyUp_ = method_setImplementation(m, replaced_NSWindow_keyUp_);

Overriding / Swizzling methods from an existing shared delegate

Is it possible to override ONLY CERTAIN functions from an exisiting delegate, without ourself being a delegate totally?
I tried replacing the target IMP with mine, didn't work :'(
More detail:
+[SomeClass sharedDelegate]
-[sharedDelegate targetMethodToBeOverridden:Arg:] //OUR method needs to be called, not this
Method *targetMethod; // targetMethodToBeOverridden identified by class_copymethodlist magic
targetMethod->method_imp = [self methodForSelector:#selector(overriddenDelegateMethod:Arg:)];
NOT WORKING! My Method is not being called :(
You probably shouldn't be manipulating the Method struct directly. Use the runtime function instead. You'll need to #import the runtime header, but there's a nice method in there called method_setImplementation. It'll work something like this:
id targetObject = [SomeClass sharedDelegate];
Method methodToModify = class_getInstanceMethod([targetObject class], #selector(replaceMe:argument:));
IMP newImplementation = [self methodForSelector:#selector(overriddenDelegateMethod:Arg:)];
method_setImplementation(methodToModify, newImplementation);
This may not work for your specific case, since class_getInstanceMethod might not return the Method for a method defined by an adopted protocol, but this is the "proper" way to swizzle Method IMPs.