248

I have heard people state that method swizzling is a dangerous practice. Even the name swizzling suggests that it is a bit of a cheat.

Method Swizzling is modifying the mapping so that calling selector A will actually invoke implementation B. One use of this is to extend behavior of closed source classes.

Can we formalise the risks so that anyone who is deciding whether to use swizzling can make an informed decision whether it is worth it for what they are trying to do.

E.g.

  • Naming Collisions: If the class later extends its functionality to include the method name that you have added, it will cause a huge manner of problems. Reduce the risk by sensibly naming swizzled methods.
2
  • It's already not very good to get something else you expect from code you've read Mar 17, 2011 at 13:15
  • this sounds like an unclean way to do what python decorators do very cleanly
    – Claudiu
    Sep 11, 2013 at 21:36

8 Answers 8

461

I think this is a really great question, and it's a shame that rather than tackling the real question, most answers have skirted the issue and simply said not to use swizzling.

Using method sizzling is like using sharp knives in the kitchen. Some people are scared of sharp knives because they think they'll cut themselves badly, but the truth is that sharp knives are safer.

Method swizzling can be used to write better, more efficient, more maintainable code. It can also be abused and lead to horrible bugs.

Background

As with all design patterns, if we are fully aware of the consequences of the pattern, we are able to make more informed decisions about whether or not to use it. Singletons are a good example of something that's pretty controversial, and for good reason — they're really hard to implement properly. Many people still choose to use singletons, though. The same can be said about swizzling. You should form your own opinion once you fully understand both the good and the bad.

Discussion

Here are some of the pitfalls of method swizzling:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

These points are all valid, and in addressing them we can improve both our understanding of method swizzling as well as the methodology used to achieve the result. I'll take each one at a time.

Method swizzling is not atomic

I have yet to see an implementation of method swizzling that is safe to use concurrently1. This is actually not a problem in 95% of cases that you'd want to use method swizzling. Usually, you simply want to replace the implementation of a method, and you want that implementation to be used for the entire lifetime of your program. This means that you should do your method swizzling in +(void)load. The load class method is executed serially at the start of your application. You won't have any issues with concurrency if you do your swizzling here. If you were to swizzle in +(void)initialize, however, you could end up with a race condition in your swizzling implementation and the runtime could end up in a weird state.

Changes behavior of un-owned code

This is an issue with swizzling, but it's kind of the whole point. The goal is to be able to change that code. The reason that people point this out as being a big deal is because you're not just changing things for the one instance of NSButton that you want to change things for, but instead for all NSButton instances in your application. For this reason, you should be cautious when you swizzle, but you don't need to avoid it altogether.

Think of it this way... if you override a method in a class and you don't call the super class method, you may cause problems to arise. In most cases, the super class is expecting that method to be called (unless documented otherwise). If you apply this same thought to swizzling, you've covered most issues. Always call the original implementation. If you don't, you're probably changing too much to be safe.

Possible naming conflicts

Naming conflicts are an issue all throughout Cocoa. We frequently prefix class names and method names in categories. Unfortunately, naming conflicts are a plague in our language. In the case of swizzling, though, they don't have to be. We just need to change the way that we think about method swizzling slightly. Most swizzling is done like this:

@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end

@implementation NSView (MyViewAdditions)

- (void)my_setFrame:(NSRect)frame {
    // do custom work
    [self my_setFrame:frame];
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}

@end

This works just fine, but what would happen if my_setFrame: was defined somewhere else? This problem isn't unique to swizzling, but we can work around it anyway. The workaround has an added benefit of addressing other pitfalls as well. Here's what we do instead:

@implementation NSView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

@end

While this looks a little less like Objective-C (since it's using function pointers), it avoids any naming conflicts. In principle, it's doing the exact same thing as standard swizzling. This may be a bit of a change for people who have been using swizzling as it has been defined for a while, but in the end, I think that it's better. The swizzling method is defined thusly:

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

Swizzling by renaming methods changes the method's arguments

This is the big one in my mind. This is the reason that standard method swizzling should not be done. You are changing the arguments passed to the original method's implementation. This is where it happens:

[self my_setFrame:frame];

What this line does is:

objc_msgSend(self, @selector(my_setFrame:), frame);

Which will use the runtime to look up the implementation of my_setFrame:. Once the implementation is found, it invokes the implementation with the same arguments that were given. The implementation it finds is the original implementation of setFrame:, so it goes ahead and calls that, but the _cmd argument isn't setFrame: like it should be. It's now my_setFrame:. The original implementation is being called with an argument it never expected it would receive. This is no good.

There's a simple solution — use the alternative swizzling technique defined above. The arguments will remain unchanged!

The order of swizzles matters

The order in which methods get swizzled matters. Assuming setFrame: is only defined on NSView, imagine this order of things:

[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];

What happens when the method on NSButton is swizzled? Well most swizzling will ensure that it's not replacing the implementation of setFrame: for all views, so it will pull up the instance method. This will use the existing implementation to re-define setFrame: in the NSButton class so that exchanging implementations doesn't affect all views. The existing implementation is the one defined on NSView. The same thing will happen when swizzling on NSControl (again using the NSView implementation).

When you call setFrame: on a button, it will therefore call your swizzled method, and then jump straight to the setFrame: method originally defined on NSView. The NSControl and NSView swizzled implementations will not be called.

But what if the order were:

[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

Since the view swizzling takes place first, the control swizzling will be able to pull up the right method. Likewise, since the control swizzling was before the button swizzling, the button will pull up the control's swizzled implementation of setFrame:. This is a bit confusing, but this is the correct order. How can we ensure this order of things?

Again, just use load to swizzle things. If you swizzle in load and you only make changes to the class being loaded, you'll be safe. The load method guarantees that the super class load method will be called before any subclasses. We'll get the exact right order!

Difficult to understand (looks recursive)

Looking at a traditionally defined swizzled method, I think it's really hard to tell what's going on. But looking at the alternative way we've done swizzling above, it's pretty easy to understand. This one's already been solved!

Difficult to debug

One of the confusions during debugging is seeing a strange backtrace where the swizzled names are mixed up and everything gets jumbled in your head. Again, the alternative implementation addresses this. You'll see clearly named functions in backtraces. Still, swizzling can be difficult to debug because it's hard to remember what impact the swizzling is having. Document your code well (even if you think you're the only one who will ever see it). Follow good practices, and you'll be alright. It's not harder to debug than multi-threaded code.

Conclusion

Method swizzling is safe if used properly. A simple safety measure you can take is to only swizzle in load. Like many things in programming, it can be dangerous, but understanding the consequences will allow you use it properly.


1 Using the above defined swizzling method, you could make things thread safe if you were to use trampolines. You would need two trampolines. At the start of the method, you would have to assign the function pointer, store, to a function that spun until the address to which store pointed to changed. This would avoid any race condition in which the swizzled method was called before you were able to set the store function pointer. You would then need to use a trampoline in the case where the implementation isn't already defined in the class and have the trampoline lookup and call the super class method properly. Defining the method so it dynamically looks up the super implementation will ensure that the order of swizzling calls does not matter.

6
  • 27
    Amazingly informative and technically sound answer. The discussion is really interesting. Thanks for taking the time to write this. Jun 27, 2013 at 21:45
  • Could you point out if swizzling in your experience is acceptable on app stores. I am directing you to stackoverflow.com/questions/8834294 as well. Nov 15, 2013 at 3:14
  • 3
    Swizzling can be used on the App store. Many apps and frameworks do (ours included). Everything above still holds, and you can't swizzle private methods (actually, you technically can, but you'll risk rejection & the dangers of this are for a different thread).
    – wbyoung
    Nov 15, 2013 at 22:17
  • Amazing answer. But why make the swizzling in class_swizzleMethodAndStore() instead of directly in +swizzle:with:store:? Why this additional function?
    – Frizlab
    Feb 25, 2014 at 17:21
  • 2
    @Frizlab good question! It's really just a style thing. If you're writing a bunch of code that's working directly with the Objective-C runtime API (in C), then it's nice to be able to call it C style for consistency. The only reason I can think of besides this is if you wrote something in pure C, then it's more still callable. There's no reason you can't do it all in the Objective-C method, though.
    – wbyoung
    Feb 25, 2014 at 18:05
12

First I will define exactly what I mean by method swizzling:

  • Re-routing all calls that were originally sent to a method (called A) to a new method (called B).
  • We own Method B
  • We dont own method A
  • Method B does some work then calls method A.

Method swizzling is more general than this, but this is the case I am interested in.

Dangers:

  • Changes in the original class. We dont own the class that we are swizzling. If the class changes our swizzle may stop working.

  • Hard to maintain. Not only have you got to write and maintain the swizzled method. you have to write and maintain the code that preforms the swizzle

  • Hard to debug. It is hard to follow the flow of a swizzle, some people may not even realise the swizzle has been preformed. If there are bugs introduced from the swizzle (perhaps dues to changes in the original class) they will be hard to resolve.

In summary, you should keep swizzling to a minimum and consider how changes in the original class might effect your swizzle. Also you should clearly comment and document what you are doing (or just avoid it entirely).

2
  • @everyone I have combined your answers into a nice format. Feel free to edit / add to it. Thanks for your input.
    – Robert
    Mar 18, 2011 at 20:39
  • I am a fan of your psychology. "With great swizzling power comes great swizzling responsibility."
    – Jacksonkr
    Aug 18, 2017 at 0:24
7

It's not the swizzling itself that's really dangerous. The problem is, as you say, that it's often used to modify the behavior of framework classes. It's assuming that you know something about how those private classes work that's "dangerous." Even if your modifications work today, there's always a chance that Apple will change the class in the future and cause your modification to break. Also, if many different apps do it, it makes it that much harder for Apple to change the framework without breaking a lot of existing software.

5
  • I'd say such Devs should reap what they sow - they tightly coupled their code to a particular implementation. It's therefore their fault if that implementation changes in a subtle way that should not have broken their code if it wasn't for their explicit decision to couple their code as tightly as they did.
    – Arafangion
    Mar 17, 2011 at 13:18
  • Sure, but say a very popular app breaks under the next version of iOS. It's easy to say it's the developer's fault and they should know better, but it makes Apple look bad in the user's eyes. I don't see any harm in swizzling your own methods or even classes if you want to do that, aside from your point that it can make code somewhat confusing. It starts getting a little more risky when you swizzle code that's controlled by someone else -- and that's exactly the situation where swizzling seems most tempting.
    – Caleb
    Mar 17, 2011 at 13:29
  • Apple is not the only provider of base classes that can be modified via siwzzling. Your answer ought to be edited to include everyone.
    – A.R.
    Mar 17, 2011 at 13:56
  • @A.R. Perhaps, but the question is tagged iOS and iPhone, so we should consider it in that context. Given that iOS apps must statically link any libraries and frameworks other than those provided by iOS, Apple is in fact the only entity that can change the implementation of framework classes without some participation from app developers. But I do agree with the point that similar issues affect other platforms, and I'd say the advice can be generalized beyond swizzling as well. In general form, the advice is: Keep your hands to yourself. Avoid modifying components that other people maintain.
    – Caleb
    Mar 17, 2011 at 15:05
  • Method swizzling can be that dangerous. but as the frameworks come out they dont completely omit the previous ones. you still have to update the base framework your app expects. and that update would break your app. it is likely not to be as big a problem when the ios is updated. My usage pattern was to override the default reuseIdentifier. Since I had no access to the _reuseIdentifier variable and did not want to replace it unless it was not provided my only solution was to subclass every one and provide the reuse identifier, or swizzle and override if nil. The implementation type is key... Jun 5, 2012 at 23:39
5

Used carefully and wisely, it can lead to elegant code, but usually, it just leads to confusing code.

I say that it should be banned, unless you happen to know that it presents a very elegant opportunity for a particular design task, but you need to clearly know why it applies well to the situation, and why alternatives do not work elegantly for the situation.

Eg, one good application of method swizzling is isa swizzling, which is how ObjC implements Key Value Observing.

A bad example might be relying on method swizzling as a means of extending your classes, which leads to extremely high coupling.

3
  • 1
    -1 for the mention of KVO—in the context of method swizzling. isa swizzling is just something else. Jan 7, 2013 at 14:20
  • @NikolaiRuhe: Please feel free to provide references so that I can be enlightened.
    – Arafangion
    Jan 8, 2013 at 0:58
  • Here's a reference to the implementation details of KVO. Method swizzling is described on CocoaDev. Jan 14, 2013 at 10:58
5

Although I have used this technique, I would like to point out that:

  • It obfuscates your code because it can cause un-documented, though desired, side effects. When one reads the code he/she may be unaware of the side effect behavior that is required unless he/she remembers to search the code base to see if it has been swizzled. I'm not sure how to alleviate this problem because it is not always possible to document every place where the code is dependent upon the side effect swizzled behavior.
  • It can make your code less reusable because someone who finds a segment of code which depends upon the swizzled behavior that they would like to use elsewhere cannot simply cut and paste it into some other code base without also finding and copying the swizzled method.
0
4

I feel that the biggest danger is in creating many unwanted side effects, completely by accident. These side effects may present themselves as 'bugs' which in turn lead you down the wrong path to find the solution. In my experience, the danger is illegible, confusing, and frustrating code. Kind of like when someone overuses function pointers in C++.

2
  • Ok, so the problem is that it is hard to debug. The issues is caused because reviewers dont realise / forget that the code has been swizzled and are looking in the wrong place when debugging.
    – Robert
    Mar 17, 2011 at 13:13
  • 3
    Yes pretty much. Also, when overused you can get into an endless chain or web of swizzles. Imagine what might happen when you change just one of them at some point it the future? I liken the experience to stacking teacups or playing 'code jenga'
    – A.R.
    Mar 17, 2011 at 13:52
4

You may end up with odd looking code like

- (void)addSubview:(UIView *)view atIndex:(NSInteger)index {
    //this looks like an infinite loop but we're swizzling so default will get called
    [self addSubview:view atIndex:index];

from actual production code related to some UI magic.

3

Method swizzling can be very helpful is in unit testing.

It allows you to write a mock object and have that mock object used instead of the real object. Your code to remain clean and your unit test has predictable behavior. Let's say you want to test some code that uses CLLocationManager. Your unit test could swizzle startUpdatingLocation so that it would feed a predetermined set of locations to your delegate and your code would not have to change.

1
  • isa-swizzling would be a better choice. Aug 20, 2014 at 16:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.