Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 :)

like image 295
AyBayBay Avatar asked Dec 28 '15 23:12

AyBayBay


People also ask

What is method swizzling in Objective-C?

Method swizzling is the process of replacing the implementation of a function at runtime. Swift, as a static, strongly typed language, did not previously have any built-in mechanism that would allow to dynamically change the implementation of a function.

How does method swizzling work?

Method swizzling is the process of changing the implementation of an existing selector. It's a technique made possible by the fact that method invocations in Objective-C can be changed at runtime, by changing how selectors are mapped to underlying functions in a class's dispatch table.

What is method swizzling in Swift?

iOS Swift Tips. Swizzling (other languages call this “monkey patching”) is the process of replacing a certain functionality or adding custom code before the original code is called. For example, you could swizzle UIViewController.


2 Answers

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:

  1. 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.

  2. 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

like image 147
CRD Avatar answered Sep 22 '22 11:09

CRD


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/

like image 38
Anand Avatar answered Sep 19 '22 11:09

Anand