I need to add additional behavior to methods I need to extend, i.e. implement method that looks like
- (void)extendMethod:(SEL)selector forClass:(Class)class withCompletionBlock:(void (^)(void))completionBlock;
So every time Class instance call a method with SEL selector in addition should be invoked my completion block.
I've tried method swizzling, but ran into some problems: I want original method implementation to be called.
What I need is very similar with subclassing, but this should be implemented without subclassing.
UPDATE:
For example I have subclass of UIViewController named MyViewController. MyViewController have - (void)viewDidLoad method. Somewhere in application I call method
[methodExtender extendMethod:#selector(viewDidLoad)
forClass:[MyViewController class]
withCompletionBlock:^{
NSLog(#"view did load called");
}];
So after viewDidLoad method of every instance of MyViewController my completion block invoked.
I'm not sure how you want to use selector, but you can try extend any class(even ones that you don't have implementation file) by using mechanism in Objective-C know as "Categories".
From Xcode click on File->New->File (command+n)
From Cocoa Touch choose Objective-C category
Type name of your category and choose class on which you want to make category (I choosed UIButton)
Then next and Create.
Xcode will create two files for example: UIButton+extendMethod.h
Declare your method in header file and implement it in *.m file.
Using
If you want to use in let's say your View Controller in *.h file import
#import "UIButton+extendMethod.h"
and then you can call your method like this:
UIButton *button = [[UIButton alloc] init];
[button extendMethod:#selector(yourMethod:)];
Swizzling does allow you to call the original implementation, though it is just a bit confusing. Because the implementations are swapped after swizzling, you call the original implementation using the selector of the swizzled method:
- (void)mySwizzledImplementation {
// do stuff
// now call original implementation (using swizzled selector!)
[self mySwizzledImplementation];
// do more stuff
}
See also http://cocoadev.com/wiki/MethodSwizzling
There is no way (anymore)to simulate inheritance without subclassing. There use to be Posing, method swizzling is all that is left (not as elegant as posing though). Here is one way to do method swizzling correctly while being able to call the original implementation.
int swizzle_instance_methods(Class class, SEL selector, IMP replacement, IMP *store) {
#synchronized(class) {
Method method = class_getInstanceMethod(class, selector);
IMP original_imp = NULL;
if (method != NULL) {
const char *type = method_getTypeEncoding(method);
IMP original_imp = class_replaceMethod(class, selector, replacement, type);
if (original_imp == NULL)
original_imp = method_getImplementation(method);
if (original_imp != NULL && store != NULL) {
*store = original_imp;
}
}
return (original_imp != NULL);
}
}
+ (void) load
{
static IMP originalMethodImpl = NULL;
IMP customMethodImpl = imp_implementationWithBlock(^(id self_) {
NSString *descr = ((NSString(*)(id,SEL))originalMethodImpl)(self_, #selector(description);
return [NSString stringWithFormat:#"<--- %# --->",descr];
});
swizzle_instance_methods([self class], #selector(description), customMethodImpl, &originalMethodImpl);
}
I might add that this is nice for debugging and I think that it can be greate for building excellent frameworks. Alas, Apple seems to think differently and using method swizzling can result in your app being excluded from the App store. If you are not aiming for the app store then all the power to you.
It is be possible with ObjC categories. For ex, you can extend hasPrefix method of NSString as follows,
-(BOOL)hasPrefixx:(NSString *)aString
{
NSLog(#"Checking has prefix");
return [self hasPrefix:aString];
}
If you import the category, you should be able to call this method. But his means you got change the method in your call.
By the way, Method swizzling should work. Bit of explanation here.
Related
So I'm plan to create a safe init function for NSDictionary like someone else did, but as I'm a SDK developer:
I want add a switch for it, the user can decide if he want open it or not;
I don't want to use Category to implement it.
So I create a totally new class named "ALDictionarySafeFunction.h", it has two functions, the first one is the switch function, like this:
+(void)enableSafeFunction{
[ALSwizzlingHelper swizzleSelector:#selector(initWithObjects:forKeys:count:)
ofClass:NSClassFromString(#"__NSPlaceholderDictionary")
withSwizzledSelector:#selector(safeInitWithObjects:forKeys:count:)
ofClass:[ALDictionarySafeFunction class]];
}
The ALSwizzlingHelper can help me to swizzle two functions.
The second is the safe init function, like this:
-(instancetype)safeInitWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt {
BOOL containNilObject = NO;
for (NSUInteger i = 0; i < cnt; i++) {
if (objects[i] == nil) {
containNilObject = YES;
NSLog(#"There is a nil object(key: %#)", keys[i]);
}
}
if (containNilObject) {
//Do something to make sure that it won't cause a crash even it has some nil value
}
//There is the problem, next line
[self safeInitWithObjects:objects forKeys:keys count:cnt];
}
For the normal situation(Write the swizzled method in the Category), I need to do like I wrote to invoke the original method.
But the problem is I cannot do it here, because that the "self" object is the instance of “__NSPlaceholderDictionary”, and the "__NSPlaceholderDictionary" class doesn't have the instance method "safeInitWithObjects:forKeys:count:".
So what should I do?
Is there a way to make it?
Any advice will be appreciated.
The title is the question's formulation - i.e. what are the patterns and anti-patterns of +initialize and +load class methods overriding?
Have you met particular examples? If yes - please describe.
P.S. There was some good Q&A on +load and +initialize here on StackOverflow but no one tells about the practical interest of these methods. Mechanisms were discussed.
+load is useful for setting up stuff needed for categories because all the +load methods are guaranteed to be called once each when the binary is loaded (even if there are multiple +load methods for the same class, which normally would replace one another). Inheritance is actually irrelevant to its functioning.
I almost never use +load, but +initialize is useful for all sorts of things... setting up static variables, dynamically loading libraries for plugin architectures... anything you want to do one time like printing version info, setting up a global instance to do something specialized, like for logging, crash reporter, signal handler, etc...
edit:
to prevent multiple initialize calls from messing stuff up (which happens to superclasses when the child class is used after a superclass): you can make it reentrant (this is a common pattern):
+(void) initialize {
static BOOL inited = NO;
if(!inited)
{
/*dostuff*/
inited=YES;
}
}
A good use of the load method is to initialize global variables that can't be initialized at compile-time.
Here is a made up example:
SomeClass.h
extern NSString *SomeGlobalConstant;
// Followed by some class interface stuff
SomeClass.m
#import "SomeClass.h"
NSString *SomeGlobalConstant = nil;
static NSArray *someFileStaticArray = nil;
#implementation SomeClass
+ (void)load {
if (self == [SomeClass class]) {
SomeGlobalConstant = #"SomeAppropriateValue";
someFileStaticArray = #[ #"A", #"B", #"C" ];
}
}
// and the rest of the class implementation
#end
Another possible use of +initialize is for Method Swizzling. Which you shouldn't really use unless you're sure you know what you're doing. Read this SO question for more details.
It allows you to substitute an existing method implementation with your own, and still be able to call the original one. For instance, for faking NSDate in unit tests you could write something like this (note, that there are other ways to do this (OCMock, etc.), this is just an example). This code allows you to set a program-wide fake NSDate, which will be returned whenever [NSDate date] is called. And if no fake date is set, then the original implementation is used.
#import "NSDate+UnitTest.h"
#import "MethodSwizzling.h"
#implementation NSDate(UnitTest)
static NSDate *fakeCurrentDate = nil;
+(void)setFakeCurrentDate:(NSDate *)date
{
fakeCurrentDate = date;
}
+(NSDate *)fakeCurrentDate
{
if (fakeCurrentDate) {
return fakeCurrentDate;
}
else {
NSDate *result = [self fakeCurrentDate];
return result;
}
}
+(void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(#"Swizzling...");
SwizzleClassMethod([self class], #selector(date), #selector(fakeCurrentDate));
});
}
#end
//MethodSwizzling.m:
void SwizzleMethod(Class c, SEL orig, SEL new, BOOL isClassMethod) {
NSLog(#"Swizzling %# method %# of class %# with fake selector %#.",
(isClassMethod ? #"a class" : #"an instance"),
NSStringFromSelector(orig),
NSStringFromClass(c),
NSStringFromSelector(new));
Method origMethod = isClassMethod ? class_getClassMethod(c, orig) : class_getInstanceMethod(c, orig);
Method newMethod = isClassMethod ? class_getClassMethod(c, new) : class_getInstanceMethod(c, new);
method_exchangeImplementations(origMethod, newMethod);
//Actually, it's better to do it using C-functions instead of Obj-C methods and
//methos_setImplementation instead of method_exchangeImplementation, but since this
//is not an open-source project and these components aren't going to be used by other people,
//it's fine. The problem is that method_exchangeImplementations will mess things up if
//the implementation relies on a fact that the selector passed as a _cmd parameter
//matches the function name.
//More about it: http://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
}
void SwizzleClassMethod(Class c, SEL orig, SEL new) {
SwizzleMethod(c, orig, new, YES);
}
void SwizzleInstanceMethod(Class c, SEL orig, SEL new) {
SwizzleMethod(c, orig, new, NO);
}
I'm working in a class, call it Module, and I need to implement this method in the AppDelegate:
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
...
}
I know which class is the AppDelegate (call it App), but I do not have access to edit that class. How can I implement the delegate method from my Module?
I thought about using a category to extend App but this line in the category docs is a concern:
"you’ll need to import the category header file in any source code file where you wish to use the additional methods, otherwise you’ll run into compiler warnings and errors."
The question is, how can I implement the protocol method in my module in a way so that iOS knows to call my protocol method at the appropriate time?
Ok Joe... If you want to IMPLEMENT the application:openURL:sourceApplication:annotation: from another module, you can do it in runtime.
WE NEED TO ASSUME THAT THE AppDelegate HAVE TEH METHOD IMPLEMENTED
First you need to import the class:
#import <objc/runtime.h>
Then the you need to declare the struct of an object method:
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
And finally you can change the implementation whit this:
//Create the selector of the method.
NSString * openURL = #"application:openURL:sourceApplication:annotation:";
SEL selectorOpenURL = NSSelectorFromString(openURL);
//Get the method of the intance.
Method openURLMethod = class_getInstanceMethod([[[UIApplication sharedApplication] delegate] class], selectorOpenURL);
//Get the current implementation.
IMP openURLIMP = openURLMethod->method_imp;
//Create your own implementation.
IMP myOpenURLIMP = imp_implementationWithBlock(^BOOL(id _s, UIApplication * app,NSURL *url,NSString *sourceApplication,id annotation) {
//Call the original implementation.
openURLIMP(_s,selectorOpenURL,app,url,sourceApplication,annotation);
//Here your implementation code.
NSLog(#"Handling the URL");
return YES;
});
BUT BE CAREFUL. If you look in my code, I'm calling the original implementation inside my implementation, so if I execute the code to change the implementation more than one, my implementation will be an inception (Like the film, my implementation inside my implementation, inside my implementation, inside my implementation and so on).
EDIT:
There is way to add your implementation to the class:
If class_getInstanceMethod return null you can alloc the memory for the method and add it to the class later:
//If the method dont exist. We need to create one.
if (!openURLMethod) {
existMethod = NO;
openURLMethod = malloc(sizeof(struct objc_method));
openURLMethod->method_types = "c24#0:4#8#12#16#20";
openURLMethod->method_name = selectorOpenURL;
}
Adding the method to the class:
if (!existMethod) {
class_addMethod([[[UIApplication sharedApplication] delegate] class], openURLMethod->method_name, openURLMethod->method_imp, openURLMethod->method_types);
}
But the problem is, I suppose, that the Operative System are registering the method when the app start, so if when the app start the method don't exist the OS never will call your method.
I will research about how the OS are management that events. With the applicationDidEnterBackground, if you don't have the implementation on the AppDelegate and you add it in runtime, the OS never call your implementation. This is why I assume that the operating system is registering the events when the application starts.
There is a library in my project that is adding audio property listeners. I really need to be able to block it from doing so, but I don't have the source code.
I've done a swizzle before for the addObserver method in NSNotificationCenter. Could you help me do the same for AudioSessionAddPropertyListener? If the method trying to be added in the call does not match my whitelist, I want to block it. Otherwise, I'll call the original method.
I don't know what class for which I should overload the load function. I'm looking inside of
Audio.h. I'm adding some pseudo/real/badlyNamed code so you can see what I'm trying to do.
#import <AudioToolbox/AudioToolbox.h>
#interface AuidoClassUmm (SOMETHING)
#end
+ (void) load
{
Method original, swizzled;
original = class_getInstanceMethod(self, #selector(AudioSessionAddPropertyListener:selector:name:object:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_AudioSessionAddPropertyListener:selector:name:object:));
method_exchangeImplementations(original, swizzled);
}
-(void) swizzled_AudioSessionAddPropertyListener:selector:name:object:
{
if(//funciton object I don't like)
{
return;
}
else
{
// Calls the original addObserver function
[self swizzled_AudioSessionAddPropertyListener::notificationObserver selector:notificationSelector name:notificationName object:notificationSender];
}
}
AudioSessionAddPropertyListener() is a C function, not an Objective-C method. You can't swizzle it.
(Even if you could swizzle it, that would almost certainly be a bad idea.)
I am accessing a dispatched notification like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUnpresent:) name:UNPRESENT_VIEW object:nil];
...
-(void)handleUnpresent:(NSNotification *)note;
{
NSLog(#"%#", note.object.footer);
//property 'footer' not found on object of type 'id'
}
Some of the incoming note.object objects have a "footer" and some don't. However, I don't want to go through to trouble of making a class that only has a property called footer just to make this work. I even tried ((NSObject *)note.object).footer) which works in some languages, but apparently not obj-c. What can I do?
Checking the isKindOfClass is certainly the more robust option. However, if you have multiple unrelated classes that return the property you need, there is another way: respondsToSelector. Just ask if the object has a footer method, and you can safely call it.
-(void)handleUnpresent:(NSNotification *)note;
{
id noteObject = [note object];
if ([note respondsToSelector:#selector(footer)])
{
NSLog(#"Footer = %#", [noteObject footer]);
}
}
That respondsToSelector method is powerful and handy in the right places, but don't go wild with it. Also, it can't tell you anything about the return type, so the footer you get may not be of the class you were expecting.
The syntax for noteObject.footer and [noteObject footer] are easy to treat as equivalent. However, when the class of noteObject is unknown, the compiler will accept the latter but not the former. If noteObject has a defined class that doesn't usually respond to footer, it will give a warning, but still compile and run. In these cases, it is your responsibility to guarantee that the method will indeed exist when needed, and therefore that the method call won't crash at run time.
If the object passed in the notification may be one of a number of classes and you don't want to cast the object to a specific class you can use performSelector: to call the footer method on the object. If you wrap this call with a respondsToSelector: you'll avoid an exception if the object turns out not to have a footer method.
-(void)handleUnpresent:(NSNotification *)note;
{
if ([[note object] respondsToSelector:#selector(footer)]) {
NSString *footer = [[note object] performSelector:#selector(footer)];
NSLog(#"%#", footer);
}
}
Using performSelector will stop the compiler complaining that the method "'footer' not found on object of type 'id'."
NSObject doesn't have any property named footer, which is why the compiler is complaining. Casting an id back to an NSObject doesn't help. If you know the object is always going to be some custom object you've created, you can cast back to that and then call footer and the compiler won't complain. It's best to actually check tho. See the example below (for the example, I named the class that has the footer property ViewWithFooter, so rename appropriately):
- (void)handleUnpresent:(NSNotification*)note
{
ViewWithFooter view = (ViewWithFooter*)[note object];
NSParameterAssert([view isKindOfClass:[ViewWithFooter class]]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}
If you have a bunch of unrelated classes (i.e., not in the same class hierarchy) that all present a footer property, you'd be best served creating a protocol with the required footer property and casting the object to the protocol in the code example above and asserting it responds to the -footer selector.
Here's an example using the protocol:
#protocol ViewWithFooter <NSObject>
- (UIView*)footer; // this could also be a readonly property, or whatever
#end
- (void)handleUnpresent:(NSNotification*)note
{
id<ViewWithFooter> view = (id<ViewWithFooter>)[note object];
NSParameterAssert([view respondsToSelector:#selector(footer)]);
UIView* footer = [view footer];
// Do something with the footer...
NSLog(#"Footer: %#", footer);
}