I'm new to iOS development and a bit stucked with such problem.
In my iphone app I'm using this awesome dropdown view controller https://github.com/nmattisson/DropdownMenu via Cocoapods.
I'm extending DropdownMenuController in my own MyDropdownMenuController:
MyDropdownMenuController.h
#import "DropdownMenuController.h"
#interface MyDropdownMenuController : DropdownMenuController
#end
I would like to override this drawOpenLayer (https://github.com/nmattisson/DropdownMenu/blob/master/DropdownMenu/DropdownMenuController.m#L126) method inside my controller instance, but unfortunately compiler says it's not possible:
MyDropdownMenuController.m
- (void)drawOpenLayer {
// compiler says
// "No visible #interface for "DropdownMenuController" declares the selector "drawOpenLayer"
[super drawOpenLayer];
}
Is it possible to override this method without actually updating external interface etc.?
You can create a category that defines the method
#interface DropdownMenuController (MichaelHacksIt)
- (void)drawOpenLayer;
#end
(no need to define the #implementation for this, because it's already implemented.)
Then you may just call that method.
Disclaimer:
Btw, that's the way to go if you want to call undocumented methods and don't care about Apples approval. In this case, there is nothing wrong with it, because you're not hacking Apple code, and Apple doesn't care if you hack some CocoaPods program. However, you are depending on internals of a third-party package, so there may be problems when you update that package next time...
Related
I'm working on an objective-c library project ('MyLib').
Let's say this library is then included in 'TestApp'. Now, 'TestApp' also includes another library called 'Xlib'. This Xlib has a class C1 which has a method m1.
//C1.h is part of Xlib
#interface C1
- (void) m1;
#end
Now, every time m1 is called, MyLib should execute a piece of code.
My approach was, if I create a category in MyLib:
#interface C1 (mycat)
#end
#implementation C1 (mycat)
+ (void) load{
//code to swizzle method in class C1 from: #selector(m1) to: #selector(mycat_m1)
}
- (void) mycat_m1{
/*
insert code that I want to execute
*/
[self mycat_m1]; //calling the original m1 implementation
}
#end
Problem:
MyLib doesn't have the class C1. So I can't build MyLib as I'm trying to create a category on a class which doesn't exist. Hence, compilation errors.
So, then I tried to wrap the above code inside:
#if defined(__has_include)
#if __has_include("C1.h")
/* above category code */
#endif
#endif
MyLib compiles and builds fine now, but since C1.h is not present in MyLib, the mylib.framework wouldn't have this category.
Now, I have two options:
1. Create this category dynamically, so that once the library is included in the app, and then when the app runs, this category will be created depending on whether TestApp includes Xlib or not.
2. Remove the file which has this category code from compile sources, and then somehow expose that file in my framework to TestApp.
I'm not able to solve either of the options. Any ideas on the existing options? or any new options?
Edit: Added details since the question wasn't quite explanatory
This code doesn't need to be in a category.
Categories are often used when swizzling because most of the time, people are trying to "wrap" a function with their own behavior in a specific class. Swizzling is often done in the +load class method. The load class method on a Category is a nice place to put it organizationally, because you know the main class's +load method has just been called.
Swizzling is still quite possible in your case. Implement a class in your framework with a +load method. The swizzling code will go there like usual. You just need to lookup the Class you want to swizzle, instead of assuming the target reference is to self like it would be if this was in a Category. That is, if you are referencing a blog post or using a favorite technique to swizzle that references self, know that it likely won't be. Make sure you watch out for what class you are trying to swizzle to/from.
I've had to do something similar in the past. In my case, I had to look up the instance of the class that implemented the AppDelegate protocol from a Framework. In that case, the code that triggered the swizzling wasn't called from +load (since the AppDelegate class might not have been loaded, and the instance of the class for the AppDelgate definitely wasn't instantiated). In that case, I invoked the code from my class's -init method, and protected it so it couldn't be called twice. However, I was guaranteed to be able to instantiate my class from the app code, so the technique worked; I'm not 100% sure of your use case. It wouldn't hurt to post the code you've tried.
UPDATE: concrete example
Here is the method I keep handy for swizzling.
+ (void)swizzleSelector:(SEL)sel1 onClass:(Class)class1 withSelector:(SEL)sel2 fromClass:(Class)class2
{
Method method1 = class_getInstanceMethod(class1, sel1);
Method method2 = class_getInstanceMethod(class2, sel2);
// Make sure that both methods are on the target class (the one to swizzle likely already is).
class_addMethod(class1,
sel1,
method_getImplementation(method1),
method_getTypeEncoding(method1));
class_addMethod(class1, // The swizzling is 'on' the first class, so it's the target here, not class2.
sel2,
method_getImplementation(method2),
method_getTypeEncoding(method2));
// Once they are both added to the class, exchange the implementations of the methods.
method_exchangeImplementations(class_getInstanceMethod(class1,sel1),
class_getInstanceMethod(class1,sel2));
}
Like I said, you'll need to lookup the class of your target somehow, but assuming you are calling this from the class that has your replacement methods, and assuming m1: is the target selector you are trying to swizzle, you might invoke like this:
Class class = NSClassFromString(#"C1");
// Check that `class` is not `nil`
[self swizzleSelector:#selector(m1)
onClass:class
withSelector:#selector(my_m1)
fromClass:self];
I hope this helps to clarify what you can do with swizzling. 999 out of 1000, you probably don't need to swizzle. Your use case sounds like a possibility where you might have to.
I am aware that +(void)load method gets called in ios even before the main function.
In my case it is only getting invoked with UIView and not with UITextView.
Are there any files that need to import ?
Does load method work only for certain set of class.
Please provide necessary input regarding conditions required in order to invoke +(void)load method in ios.
Getting invoked here :
#implementation UIView (SomeMethodSwizzling)
+(void)load{
}
#end
Not Getting inovked here .
#implementation UITextView (SomeMethodSwizzling)
+(void)load{
}
#end
Usual reason is that you're not actually linking the file. Most common cause is that you don't directly use anything declared in it (you sometimes need to pass -objc to the linker to fix that). Another cause is you just forgot to add the file to the target (I see that happen a lot).
My app compiles fine even though not all methods are used, here is my protocol:
#protocol azzams_customer_service_delegate <NSObject>
-(void)just_make_dice_appear_in_beginning;
-(void)generate_magic_numbers;
-(void)roll_the_dice;
-(void)check_for_snake_eyes;
-(void)Check_winning_number;
-(void)flash_random_winning_number;
-(void)update_winning_purse;
#required
-(void)u_need_to_im;
#end
I now attach this protocol to this class:
#interface ViewController : UIViewController<azzams_customer_service_delegate>
-(void)just_make_dice_appear_in_beginning;
-(void)generate_magic_numbers;
-(void)roll_the_dice;
-(void)check_for_snake_eyes;
-(void)Check_winning_number;
-(void)flash_random_winning_number;
-(void)update_winning_purse;
#end
notice how the following code is NOT implemented:
#required
-(void)u_need_to_im;
it should give an error, yet my app compiles just fine.
The app will compile just fine, but you should see a warning saying that method is not implemented.
As others have noted, you will see a warning. You can, however, turn on the option in the compiler "Treat Warnings as Errors". Of course, this means you will have to make sure your project is completely free of warnings.
The thing is like - Say class A is the delegate of a protocol and it is supposed to implement a method XYZ. The program will work fine until another class/method tries to access XYZ. If it tries to access the required method, the program will crash.
Example : You implement UiTableViewSourceDelegate and you didn't implement the required methods. But you didn't use a tableview in your viewcontroller. You are on safer side.
Background.
Please consider the following steps:
1) In Xcode create a new "Single View Application".
2) Create a category NSObject+Extension.h and .m files:
// .h
#interface NSObject (Extension)
- (void)someMethod;
#end
// .m
#implementation NSObject (Extension)
- (void)someMethod {
NSLog(#"someMethod was called");
}
#end
3) Ensure that NSObject+Extension.m file is included into a main target.
4) Add the following line to AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSString new] performSelector:#selector(someMethod)];
return YES;
}
5) Ensure that #import "NSObject+Extension.h line does not exists anywhere in the app!
6) Run Application.
The output is
2013-08-27 04:12:53.642 Experimental[32263:c07] someMethod was called
Questions
I wonder if there is no any #import of this category anywhere in the app, how is it even possible that NSString does still have NSObject+Extension available? This behavior makes me feeling very bad about every Objective-C category I declare because I want the categories I declare to be available only in the scopes they are declared within. For example, I want NSObject to be extended by Extension only in some class but not in the whole app because its globalspace becomes "polluted" otherwise.
Is there a way to avoid this behavior? I do want my categories to work only when I explicitly import them, not when I just have them linked to a target I use to run.
I wonder if there is no any #import of this category anywhere in the app, how is it even possible that NSString does still have NSObject+Extension available? This behavior makes me feeling very bad about every Objective-C category I declare because I want the categories I declare to be available only in the scopes they are declared within. For example, I want NSObject to be extended by Extension only in some class but not in the whole app because its globalspace becomes "polluted" otherwise.
There are no namespaces on Objective-C objects. If you declare that a class has a method (whether via a category or on the primary #interface) then every instance of that class will have that method.
The way that Objective-C deals with "private" methods is by choosing not to tell other people about the methods in question (which is accomplished by not #import-ing the file that declares those methods). This, coupled with -Wundeclared-selector (warn if you use a selector that the compiler doesn't know about) is about as good of a guard as you're going to get.
But regardless, if you compile the .m file into your final binary, the method will exist, even if no one else "knows" about it.
Are there way to avoid this behavior? I do want my categories to work only when I explicitly import them, not just when I have them linked to a target I use to run.
Yeah, use -Wundeclared-selector, and Xcode will warn you.
Including the header just makes it so the compiler knows about it. It compiles it regardless because xCode compiles every file included in a target. At runtime, the method will be there, so even if you didn't include it for compile time checking, the object will still respond to that category method.
Okay I know there's a lot of posts on this, but I'm still having trouble. Here's the pseudo code for what I'm trying to do:
if(device is running iOS 5 or up)
#interface RootViewController : UIViewController <UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
#property (strong, nonatomic) UIPageViewController *pageViewController;
else
#interface RootViewController : UIViewController <LeavesViewDelegate, UIGestureRecognizerDelegate>
#property (strong, nonatomic) LeavesViewController *leavesViewController;
endif
Am I right in thinking I need to use pre-processor macro checks since it's in the header file? It's a book app that should use UIPageViewController if it's iOS 5 or up (and therefore has UIPageViewController), otherwise it falls back on Leaves (https://github.com/brow/leaves). I have all the code set up. Just need to know how to tell the compiler which to use. I don't think using any runtime checks would work since I only need the protocol methods for either UIPageViewController or Leaves compiled, not both. And I'd rather not use completely separate source files. I've tried using these checks:
#ifdef kCFCoreFoundationVersionNumber_xxx
#ifdef __IPHONE_xxx
#if __IPHONE_OS_VERSION_MAX_ALLOWED <__IPHONE_xxx
(with various xxx's)
What am I missing here?
EDIT:
I also noticed this in the default .pch:
#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif
which makes me wonder why that same test didn't work in my .h file?
As I mentioned in the comments, you can't do this at compile time.
But here's an idea for you: It seems that the method names of UIPageViewControllerDelegate and LeavesViewDelegate do not intersect, so you could add the following to your header file:
-(void) leavesView:(LeavesView*)leavesView willTurnToPageAtIndex:(NSUInteger)pageIndex;
-(void) leavesView:(LeavesView*)leavesView didTurnToPageAtIndex:(NSUInteger)pageIndex;
-(void) pageViewController:(UIPageViewController*)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray*)previousViewControllers transitionCompleted:(BOOL)completed;
-(UIPageViewControllerSpineLocation) pageViewController:(UIPageViewController*)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation;
and not explicitly adopt the delegate protocols in the header file (leave out the delegates inside the < >).
Whatever classes you're using for these two could be instantiated in your *.m file in a condition along the lines:
// check for existence of class to determine which controller to instantiate
if(NSClassFromString(#"UIPageViewController"))
{
// do something and set UIPageViewController delegate to "self"
}
else
{
// do something else and set LeavesViewController delegate to "self"
}
Lastly, to get this to compile, you will probably need to forward declare all LeavesViewController- and UIPageViewController-related classes where you use them, and possibly utilize weak linking for some frameworks.
I haven't yet used Apple's UIPageViewController classes and protocols, so I can't provide much more insight than this. Be sure to let us know if you get something hammered out :)
you can't do this, as preprocessor macros are processed at compile-time. How should the compiler know, which iOS you're targeting, since you're compiling on your Mac and not everybody on his iPhone?
You cannot easily switch code at runtime. There are possibilities, but I don't think it is meant like you want it to be.
You can check at runtime if methods are available from specific SDKs. This is much simpler and straightforward. However you cannot achieve your goal with that.
I suggest:
Create a superclass, where you do not have the specific delegate protocols included. There you write all your code, you want to share.
Then create 2 subclasses from upper superclass. In each of the classes put in your specific code.
AND THATS IT.
This is the way it should be.