How to override a method in framework's class (runtime) - objective-c

problem:
framework will cache the image data when above:
[UIImage imageNamed:]
I don't want the caching happen,so I can replace it by
[UIImage imageOfContentOfFile:]
Seems solved,but after my tests,in xib's instantiate progress, framework uses imageNamed: rather than imageOfContentOfFile.
That is, images loaded by xib still cached.
So I try to override the UIImage's imageNamed: method, make all this method DO NOT CACHE.
--try1. category:
#implementation UIImage (ImageNamedNoCache)
+ (UIImage *)imageNamed:(NSString *)name;
{
NSString* path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
return [UIImage imageWithContentsOfFile:path];
}
#end
--result:warning."Category is implementing a method which will also be implemented by its primary class"
--try2. runtime replace the method's imp
//get super method
Method method_imageNamed = class_getClassMethod([UIImage class], #selector(imageNamed:));
//get my method
Method method_myImageNamed = class_getClassMethod([self class], #selector(myImageNamed:));
//get my imp
IMP imp_myImageNamed = method_getImplementation(method_myImageNamed);
//set super method's imp to my imp
method_setImplementation(method_imageNamed, imp_myImageNamed);
--result
these too try only work when I invoke [UIImage imageNamed:]
but xib's instantiation doesn't work.
help me, thanks

Sounds like a job for method swizzling. I find it dangerous and unnecessary, but if you are so inclined, it is an option. Use at your own risk.

The category method should be fine, just give your method a different name. It's my understanding that it's not good practice to override methods in a category, but adding a new one is fine.

Correct me if I do not interpret correctly, but it looks like your core problem is that you are trying to get framework (i.e., Apple code) to call imageWithContentsOfFile instead of imageNamed when loading xibs.
A third option might be to leave the UIImageViews in the xib 'blank' as a placeholder, and then programmatically loading UIImages into them later (for example in the FileOwner's init code), perhaps with the help of the 'tag' property + a dictionary to determine the right image, if it's convenient. Or you could even create both the images and their views dynamically instead of keeping them in the xib file. Obviously there are lots of tradeoffs here.

Your solution isn't good :) Caching is used for good reasons.
Just don't use [UIImage imageNamed:] In YOUR code, and all will be fine
If you Actually need not to cache your images, you should use method #2.
NOTE: Images(actually, all objects) from XIBS are being loaded with -[object initWithCoder:], and not with [UIImage imageNamed:] or [UIImage imageWithContentsOfFile:]

Related

Objective-c convert a parent class to its derived class

I have a custom class named MyImage inherit from UIImage. Now I have a UIImage object, is there any ways to convert it into a MyImage object?
Update
Sorry for being unclear.
I cannot use a category because I need to add new properties to my class.
What I want to accomplish is to just let my MyImage object point to the original UIImage object.
Update 2
I tried something like this:
- (MyImage*)initWithUIImage:(UIImage *)image {
if (self = [super init]) {
self = image;
}
return self;
}
Obvious it does not work.
Also, since UIImage does not have a method named 'initWithImage:(UIImage*)', I cannot write something like
myImage = [[MyImage alloc] initWithImage:uiImage];
I also tried
self = [image copy]
But the return value is still a UIImage object, not a MyImage object.
Update 3
In MyImage, I need to add 3 properties: url, width, and height. Since I am writing a instant messaging app, and when receiving a new image message, I only have its url, width, and height. Then I assign those to a MyImage object, and download the image in the background.
Now given an original UIImage A, I want to create a new MyImage object B, which points to the same image as A, but with those new properties unassigned. Then I manually assign url, width, and height to B.
to #rmaddy, could you tell me how to write the method
[[MyImage alloc] initWithImage:(UIImage*)]
?
One way I can come up is first convert the UIImage object to NSData, then use
[MyImage imageWithData:]
Is there a better way?
I do not necessarily recommend this as a solution, but there is indeed a way to do exactly what you are looking for. It is called ISA Swizzling. Think of it like method swizzling but for Classes instead of methods.
Take a look at the Objective-C runtime's object_setClass [link]
I won't go into it (there are better resources out there), but this is essentially how KVO works. Regardless, it would be helpful if you better described the functionality of your UIImage subclass, so that we could help describe why a custom initializer as rmaddy describes is probably the best solution.
Your MyImage class is a subclass of UIImage, so if you're creating a new instance you need to properly initialize the superclass as well. UIImage doesn't provide a method to do that from another UIImage instance, but you can use a CGImage, and you can get one of those from the existing image. So do this:
- (MyImage*)initWithUIImage:(UIImage *)image {
if (self = [super initWithCGImage:image.CGImage]) {
// put any necessary MyImage-specific stuff here
}
return self;
}

how to call a method from a class in another class

I'm working to a new app for mac osx where i'm using a drag and drop system to let the user to input some files [this part works well] and i have a tabelView where i would like to display the paths of files inputed.
I have the next method in tabelViewController.m:
-(void)add{
NSLog(#"da");
[list addObject:[[Source alloc] init]];
[tableView reloadData];
}
In the DropView.m i included the tabelViewController.h and i'm trying to call the add method but it does nothing:
#import "TableViewController.h"
.....
- (void)concludeDragOperation:(id<NSDraggingInfo>)sender{
[self setNeedsDisplay:YES];
TableViewController *tvc;
[tvc add];
}
Can someone to figure out why it doesn't do anything ?
Edit1:
Ok after I fallow the answers, my concludeDragOperation method looks like this:
- (void)concludeDragOperation:(id<NSDraggingInfo>)sender{
[self setNeedsDisplay:YES];
TableViewController *tvc = [[TableViewController alloc] init];
[tvc add];
[tvc rD];
}
rD is a method from tableViewController which contain the reloadData method.
But it doesn't want to work it don't reload the table view.
Any ideea ???
tvc needs to point to an actual object. [[tvc alloc] init]
Otherwise you are simply calling add on nil. This doesn't cause your program to crash as you might expect in other languages. Try it out and see what happens.
it seems as if you missed a great chunk regarding how OOP and Objective-C work (seriously, no offense there).
What link is there between DropView.m and tableViewController.h do you have?
By typing TableViewController *tvc; all you are doing is creating a pointer. You are neither creating an object nor pointing to an object, you have just simply created a pointer that can eventually point to an object in memory of type tableViewController.
Solution:
What you will need to do, is to somehow create a link between the two classes. For instance, you could create a custom delegate method for DropView that could communicate with any class who uses that custom DropViewDelegate methods. So, you could create a delegate method that tells objects that follow that delegate protocol that you just concluded a drag operation. A tutorial how to do so can be found at my blog [it's a permalink].
I am happy to post code, or you can read it on my blog. Good Luck.

Is it necessary to use a temporary variable when setting a properties' value?

I have a (retained) UIImage property that is being used to hold a user selected image.
This is the code I have at present when the user makes a selection:
- (IBAction) selectImage1 {
UIImage *image = [UIImage imageNamed: #"image1-big.png"];
self.bigImage = image;
}
but I'm wondering if it is possible to omit the use of the temporary variable convenience method and just do this:
- (IBAction) selectImage1 {
self.bigImage = [UIImage imageNamed: #"image1-big.png"];
}
If there are problems with this second method (I'm guessing something to do with memory management), could someone please explain?
Thank you!
The second way is perfectly fine. The line UIImage *image = [UIImage imageNamed: #"image1-big.png"]; gives you a variable image that is auto-released. Assigning it to your ivar via the self.bigImage = image calls bigImage's setter method which retains the value. Thus the line self.bigImage = [UIImage imageNamed: #"image1-big.png"]; is equivalent to the more verbose way.
There is no difference in terms of memory management between the two snippets you posted; unless you get really specific about retain counts in between the two lines in the first snippet.
In an ARC environment, the local variable will be a 'strong' pointer, however it is released when the method leaves scope. In the second snippet, there is no intermediate retain/release'd pointer, and so may actually be slightly more efficient.
The places I have seen the first snippet's technique be necessary are when you have a weak pointer (i.e. a weak #property) where setting self.foo = [UIView ... would immediately allow it to be released. In these cases it is better to use a local variable to keep it around while you work with it:
UIView *someFoo = [UIView...
[self addSubview:someFoo];
self.someWeakProperty = someFoo;
compare with:
self.someWeakProperty = [UIView...
[self addSubview:self.someWeakProperty]; // it's already nil!!

How can I correct this 'Incompatible Obj-c types' warning?

Specifically the warning is: "Incompatible Objective-C types 'struct NSString *', expected 'struct UIImage *' when passing argument 4 of 'objectWithType:name:code:image' from distinct Objective-C type". It follows a line that looks like so:
[Object objectWithType:#"Type" name:#"Name" code:#"0001" image:#"image.png"],
So, I understand that I created the class Object to take type UIImage, but I am providing it with type NSString. Here's the problem: I don't know how to indicate the image differently than its file name.
(Apologies if this is a basic problem. I'm new to this and trying to look for solutions before posting here. Any help you can offer is appreciated.)
You actually need an instance of UIImage, which you understand. So, the class method imageNamed: is typically used for this:
[Object objectWithType:#"Type" name:#"Name" code:#"0001" image:[UIImage imageNamed:#"image.png"]];
Another option (since you are using all strings here) might be to rewrite your method so that it takes a name instead of an image and then create the image inside the method implementation. So you might define the method:
- (void)objectWithType:(NSString*)type name:(NSString*)name code:(NSString*)code imageName:(NSString*)imageName
{
UIImage* theImage = [UIImage imageNamed:imageName];
// do whatever
}
Use [UIImage imageNamed:]. Will only work for images that are part of your project.
[Object objectWithType:#"Type" name:#"Name" code:#"0001" image:[UIImage imageNamed:#"image.png"]]
If 'image.png' is included in your project bundle, then you can do this to pass a UIImage instead of an NSString:
[Object objectWithType:#"Type" name:#"Name" code:#"0001" image:[UIImage imageNamed:#"image.png"]];

Finding image name loaded into UIImage instance

How can I find out what the image name (usually file name?) loaded into a UIImage instance is? In this case, they were all initWithContentsOfFile:.
Once it's loaded using that, you can't. The file name is only used to load the data into memory. After that, it becomes irrelevant.
If you need to maintain the file name, then perhaps you need to make a class with the filename (as an NSString or NSURL, perhaps) and the image data (as an NSImage or NSData object) as ivars. I'm not sure what your application is, so I can't give you any architectural advice at this point.
If you already know what the image names are you can compare your image to another image using image named:
if (bg.image == [UIImage imageNamed:#"field.jpeg"]) {
bg.image = [UIImage imageNamed:#"cave.jpeg"];
} else {
bg.image = [UIImage imageNamed:#"field.jpeg"];
}
Here I use one button to switch between two images.
It's also something you can't just add a category on UIImage to, unfortunately, because it requires state.