9

I know there are countless resources on method swizzling. However is it possible to swizzle a method from a private API? The problem is that there are no header files. I would like to swizzle a method from a private class in a PrivateFramework such as (random example) Message.framework methods

This is for personal testing, I understand that it will get rejected to oblivion by Apple.

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
John Smith
  • 307
  • 3
  • 14
  • 1
    If you're ever trying to do anything with Private APIs, where you feel like you need header files, you can just reverse engineer them. Look into tools like `class-dump` or `class-dump-z`. Lots of people have also posted the reverse-engineered headers online themselves. Then, just include that header as a source file in your project. If you get into problems with reverse-engineered headers not compiling, often times you just need to manually inspect them, and remove `#imports` that you don't need, or just prune down the header file to only include the function you're interested in. – Nate Jun 28 '14 at 22:25
  • That's what I was doing initially and thought there must be an easier way. Anyhow, I am able to swizzle this private API thanks to Bryan (see below). The problem is that other frameworks are not able to get swizzled. i.e. if I call that method directly from my test code I get the swizzled function, but if that method is called in some other framework it does not call my swizzled function. – John Smith Jun 29 '14 at 01:14
  • You might need to post a new, follow-up question. Also, please be specific. Which API are you swizzling would be useful to know, and are you saying that when that API is called from within your app's process, but invoked directly by a framework (not your code), then the swizzling doesn't work? – Nate Jun 29 '14 at 21:38

2 Answers2

11

You can use NSClassFromString to get Class and use runtime library to perform method swizzling. No header files required. You just need to know class name and method signature.

sel_getUid can be used when @selector(somePrivateMethod) give your error about somePrivateMethod is not valid selector (because header is not available)

Code taken from my Xcode plugin

SEL sel = sel_getUid("codeDiagnosticsAtLocation:withCurrentFileContentDictionary:forIndex:");
Class IDEIndexClangQueryProviderClass = NSClassFromString(@"IDEIndexClangQueryProvider");

Method method = class_getInstanceMethod(IDEIndexClangQueryProviderClass, sel);
IMP originalImp = method_getImplementation(method);

IMP imp = imp_implementationWithBlock(^id(id me, id loc, id dict, IDEIndex *idx) {
    id ret = ((id (*)(id,SEL,id,id,id))originalImp)(me, sel, loc, dict, idx);

    // do work

    return ret;
});

method_setImplementation(method, imp);
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • what happened? also make sure these code did run... put it in `+[load]` – Bryan Chen Jun 27 '14 at 09:24
  • The code is definitely running. However, to verify this, I can call the private API and see whether it calls the original method or the swizzled one. I tried this and I get: "unrecognized selector sent to class" which means my private api is not being called at all. I used the declared class for example: [IDEIndexClangQueryProviderClass codeDiagnosticsAtLocation:0 withCurrentFileContentDictionary:0 forIndex:NULL] – John Smith Jun 28 '14 at 09:40
  • wait... why are you trying to call `[IDEIndexClangQueryProviderClass codeDiagnosticsAtLocation:0 withCurrentFileContentDictionary:0 forIndex:NULL]`? it just example. unless you are also writing Xcode plugin, it won't work. you suppose to change the class name, selector name, and method signatures to something exists on ios. – Bryan Chen Jun 28 '14 at 10:59
  • Update- No, I was calling my own swizzled private API and it worked. Anyhow- I realized that the method I was trying to swizzle is an instance method so instead of [Class_I_chose_above args:NULL] I should use allocate/init an instance which worked :) Thanks! – John Smith Jun 28 '14 at 12:06
  • Adding to your answer: You can also use `NSSelectorFromString()` instead of `sel_getUid` to grab a selector while still bypassing compiler checks. I find `NSSelectorFromString` a little more accessible for interoperability with Swift – Charlton Provatas Apr 07 '18 at 15:29
0

Create a category on the class and add the declaration for the method you want to call. Then you can just instantiate an instance of the class and call the method.

This also works for unit testing private methods in your code.

zaph
  • 111,848
  • 21
  • 189
  • 228
  • I am not trying to add a method, I am trying to replace a method.. Are you implying declaring that method would replace the existing method? – John Smith Jun 27 '14 at 08:57
  • Declaring the method will provide access to the method to swizzle it. In the question the complaint was that there was no header method. By creating a Category you can effectively create a header method for the method you want to swizzle. – zaph Jun 27 '14 at 11:05
  • There's no reason to use categories here, and as this isn't really what categories are for, it's a confusing technique that should be avoided. If the problem is that the framework is private, and you have no headers, just reverse engineer the header (or the individual methods you're interested in) and include that header in your project. There's no reason for it to be a *category*. The OP is missing a fundamental technique in private API development, which is to reverse engineer headers you need. That's the standard pattern for this class of problem. – Nate Jun 28 '14 at 22:29
  • Could be that the class is public and has a public header but the method is private. As for the use of Categories I agree that they are to be avoided. The whole swizzling a private class is generally a bad idea but in this case the OP states: "for personal testing". – zaph Jun 28 '14 at 22:38