I have a custom control in OSX with a drawing routine. Every instance of this control uses the same images.
I wonder if it's a good idea to instantiate these images as static objects available for all the instances created by the class.
In case that was a good idea, how can I implement the image loader in a cleaver way?
I thought to add to my class something like
static NSImage *imageone;
static NSImage *imagetwo;
static NSImage *imagethree;
But I'm not sure about the better way to initialize the images... maybe a singleton function like this?
+ (void)setupSharedImages {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
imageone = [NSimage imageNamed:#"...."];
imagetwo = [NSimage imageNamed:#"...."];
image three = [NSimage imageNamed:#"...."];
});
}
You can setup you images in + (void)initialize method. This method is called once when class is loaded and it also handles synchronization.
As an option, you may think of incapsulating images into "storage" singleton class.
#interface ImageStorage : NSObject
+ (ImageStorage *)sharedStorage;
- (NSImage *)imageForKey:(NSString *)key;
#end
#implementation ImageStorage {
NSDictionary *_images;
}
+ (ImageStorage *)sharedStorage
{
static dispatch_once_t onceToken;
static ImageStorage *sharedStorage;
dispatch_once(&onceToken, ^{
sharedStorage = [[ImageStorage alloc] init];
_images = [[NSDictionary dictionaryWithObjectsAndKeys:image1, key1, image2, key2, nil] retain];
});
return sharedStorage;
}
- (NSImage *)imageForKey:(NSString *)key
{
return [_images objectForKey:key];
}
#end
You could always lazy load them.
- (NSImage *)imageOne {
static NSImage *imageone;
if(imageone == nil)
imageone = [NSImage imageNamed:#"..."];
return imageone;
}
Edit:
Just wanted to clarify, do this for each image you need then in your draw method call
[self imageOne] etc...
Related
I have something along the line of this :
#implementation ImageLoader
NSMutableDictionary *_tasks;
- (void) loadImageWithURL:(NSURL *)url callback:(SMLoaderCallback)callback {
NSMutableArray *taskList = [_tasks objectForKey:urlString];
if (taskList == nil) {
taskList = [[NSMutableArray alloc] initWithCapacity:5];
[taskList addObject:callback];
[_tasks setObject:taskList forKey:urlString];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [[UIImage alloc] initWithData:data];
for (SMLoaderCallback cb in taskList) {
cb(image, nil);
}
[_tasks removeObjectForKey:url.absoluteString];
});
});
} else {
[taskList addObject:callback];
}
}
#end
In here, I'm trying to queue up image downloads to gain performance (as an exercise only). So I'm keeping a NSDictionary that maps an URL with an array of callbacks to be called whenever the data is downloaded.
Once the image is downloaded, I no longer need this array, so I remove it from the dictionary.
I would like to know if, in this code (with ARC enabled), my array along with the callbacks are correctly released when I call [_tasks removeObjectForKey:url.absoluteString].
If your project uses ARC and that dictionary is the only thing that is referencing to those values, yes. it will be remove permanently.
ARC keeps track of number of objects that is pointing to some other object and it will remove it as soon as the count reaches 0.
so adding to dictionary -> reference count += 1
removing from dictionary -> reference count -= 1
Somewhe
I have two objects, DNLocations, that have arrays of other locations inside of them. The idea i'm trying to get across is that you can go from one location to another, and back to the previous one. Unfortunately, when i create the locations like this, they keep creating each other resulting in an infinite loop. I'm not sure what to do here. I've seen circular references but this seems like something else.
+ (DNLocation *) aCell {
static dispatch_once_t onceToken;
static DNLocation *instance = nil;
dispatch_once(&onceToken, ^{
instance = [DNLocation locationWithName:#"A Cell" andActions:[NSArray arrayWithObjects: [DNAction doNothingAction], nil] andLocations:[NSArray arrayWithObjects:[DNLocation aWhiteRoom], nil]];
});
return instance;
}
+ (DNLocation *) aWhiteRoom {
static dispatch_once_t onceToken;
static DNLocation *instance = nil;
dispatch_once(&onceToken, ^{
instance = [DNLocation locationWithName:#"A White Room" andActions:nil andLocations:[NSArray arrayWithObjects:[DNLocation aCell], nil]];
});
[instance setColor:[UIColor DNWhiteColor]];
[instance setFontColor:[UIColor DNBlackColor]];
return instance;
}
+ (DNLocation *) locationWithName:(NSString *) name {
return [[DNLocation alloc] initWithName:name];
}
These methods are called upon load of the app, setting up all of the possible locations to go to.
I'd rather not have to instantiate them and then add the locations to themselves, but if that is the only way then i'll do it.
I need some help.
I'm using this singleton pattern within an iOS application I'm developing:
.h file
#import <Foundation/Foundation.h>
#class Item;
#interface ItemManager : NSObject
- (id)init;
+ (ItemManager *)sharedInstance;
- (int)ratingFromObjectName:(NSString *)objectName;
#property(nonatomic,strong) NSArray *itemArray;
#end
.m file
static ItemManager *sharedInstance = nil;
+ (ItemManager *)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ItemManager alloc] init];
});
return sharedInstance;
}
- (int)ratingFromObjectName:(NSString *)objectName {
for (int i = 0; i < itemArray.count; i++) {
if ([[[itemArray objectAtIndex:i] itemName] isEqualToString:objectName]) { //This is the line that throws bad access code 1
NSLog(#"Found: %# Returned: %d", [[itemArray objectAtIndex:i] ratingAverage],
[[[itemArray objectAtIndex:i] ratingAverage] intValue]);
return [[[itemArray objectAtIndex:i] ratingAverage] intValue];
}
}
return 0;
}
I get bad access when I use this in another class:
int rating = [[ItemManager sharedInstance] ratingFromObjectName:bla];
The bla object being sent is a NSString that is definitely working, it is 100% not the issue, as I have tested this. Removing the sharedInstance method and creating an array every time seems to work, however my attempt for this singleton is to avoid that, any suggestions would be greatly appreciated.
Please note that I have commented on the line returning the error.
Regards, WA
You need to work out which line of code is throwing the bad access. Is it the sharedInstance method or ratingFromObjectName:. I would first change the calling code to
ItemManager *manager = [ItemManager sharedInstance];
int rating = [manager ratingFromObjectName:bla];
As that will help with isolating the problem.
Secondly, I would also consider not using the Singleton pattern unless really necessary is iOS apps. It's been my experience that it is often overused (and in the Java world), and whilst convenient, can make writing unit tests more complicated.
I just solved a really weird problem I had where I needed this class to behave sort of like a singleton but not really. Here's a code snippet
#implementation HMFPicturePreviewModalPanel
__weak static UIViewController *presentingInventoryViewController = nil;
static HMFPicturePreviewModalPanel *sharedPicturePreviewModalPanel = nil;
+(void)showPopupWithImage:(UIImage *)image withStartPoint:(CGPoint)startPoint withStartView:(UIView *)startView {
//this checks if there is already a panel visible.
if (![presentingInventoryViewController.view viewWithTag:kHMFPicturePreviewModalPanelTag]) {
sharedPicturePreviewModalPanel = [[HMFPicturePreviewModalPanel alloc] initWithFrame:presentingInventoryViewController.view.bounds withimage:image];
[presentingInventoryViewController.view addSubview:sharedPicturePreviewModalPanel];
[sharedPicturePreviewModalPanel showFromPoint:[startView convertPoint:startPoint toView:presentingInventoryViewController.view]];
}
}
+(void)changePresentingInventoryViewController:(UIViewController *)newInventoryViewController {
[sharedPicturePreviewModalPanel removeFromSuperView];
presentingInventoryViewController = newInventoryViewController;
}
+(void)removePresentingInventoryViewController {
[sharedPicturePreviewModalPanel removeFromSuperView];
presentingInventoryViewController = nil;
}
Is this called a semi-singleton? There's only ever going to be one of these on the screen at a time. I had to recreate this each time for it to work, hence why I couldn't do it as just a singleton.
What are the cons to this solution?
Also is it okay to have a __weak static variable?
I have a Core Data app with a fairly simple data model. I want to be able to store instances of NSImage in the persistent store as PNG Bitmap NSData objects, to save space.
To this end, I wrote a simple NSValueTransformer to convert an NSImage to NSData in PNG bitmap format. I am registering the value transformer with this code in my App delegate:
+ (void)initialize
{
[NSValueTransformer setValueTransformer:[[PNGDataValueTransformer alloc] init] forName:#"PNGDataValueTransformer"];
}
In my data model, I have set the image attribute to be Transformable, and specified PNGDataValueTransformer as the value transformer name.
However, my custom value transformer is not being used. I know this as I have placed log messages in my value transformer's -transformedValue: and -reverseTransformedValue methods which are not being logged, and the data that is being saved to disk is just an archived NSImage, not the PNG NSData object that it should be.
Why is this not working?
Here is the code of my value transformer:
#implementation PNGDataValueTransformer
+ (Class)transformedValueClass
{
return [NSImage class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
if (value == nil) return nil;
if(NSIsControllerMarker(value))
return value;
//check if the value is NSData
if(![value isKindOfClass:[NSData class]])
{
[NSException raise:NSInternalInconsistencyException format:#"Value (%#) is not an NSData instance", [value class]];
}
return [[[NSImage alloc] initWithData:value] autorelease];
}
- (id)reverseTransformedValue:(id)value;
{
if (value == nil) return nil;
if(NSIsControllerMarker(value))
return value;
//check if the value is an NSImage
if(![value isKindOfClass:[NSImage class]])
{
[NSException raise:NSInternalInconsistencyException format:#"Value (%#) is not an NSImage instance", [value class]];
}
// convert the NSImage into a raster representation.
NSBitmapImageRep* bitmap = [NSBitmapImageRep imageRepWithData: [(NSImage*) value TIFFRepresentation]];
// convert the bitmap raster representation into a PNG data stream
NSDictionary* pngProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:NSImageInterlaced];
// return the png encoded data
NSData* pngData = [bitmap representationUsingType:NSPNGFileType properties:pngProperties];
return pngData;
}
#end
If I'm not mistaken, your value transformer has a reversed direction. I do the same thing in my application, using code like the following:
+ (Class)transformedValueClass
{
return [NSData class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
if (value == nil)
return nil;
// I pass in raw data when generating the image, save that directly to the database
if ([value isKindOfClass:[NSData class]])
return value;
return UIImagePNGRepresentation((UIImage *)value);
}
- (id)reverseTransformedValue:(id)value
{
return [UIImage imageWithData:(NSData *)value];
}
While this is for the iPhone, you should be able to swap in your NSImage code at the appropriate locations. I simply haven't tested my Mac implementation yet.
All I did to enable this was to set my image property to be transformable within the data model, and specify the name of the transformer. I didn't need to manually register the value transformer, as you do in your +initialize method.
It seems registering the transformer has no effect on wether Core Data will use it or not.
I've been playing with the PhotoLocations sample code and removing the transformer registration has no effect. As long as your ValueTransformerName is the name of your class, and that your transformer's implementation is included with the target, it should work.
If there is no code execution in the transformer, then the code in the transformer is irrelevant. The problem must be somewhere else in the core data stack, or in the declaration of the transformer.
It turns out that this is actually a bug in the frameworks. See this post by an Apple employee on the Cocoa-Dev mailing list:
http://lists.apple.com/archives/Cocoa-dev/2009/Dec/msg00979.html
You need to explicitly register your transformer during runtime.
Good place to do this is to override Class initialize method in your NSManagedObject entity subclass. As mentioned before it is known Core Data bug. Following is the crucial code from Apple's location code sample, it is tested and works:
http://developer.apple.com/library/ios/#samplecode/Locations/Introduction/Intro.html
+ (void)initialize {
if (self == [Event class]) {
UIImageToDataTransformer *transformer = [[UIImageToDataTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"UIImageToDataTransformer"];
}
}
If nothing in your code elsewhere explicity uses the PNGDataValueTransformer class, then the +initialize method for that class will never be called. Specifying the name in your Core Data model will not trigger it either - it will simply try looking up a value transformer for that name, which will return nil, since no transformer instance has yet been registered under that name.
If this is indeed what's happening in your case, simply add a call to [PNGDataValueTransformer initialize] somewhere in your code before your data model gets accessed, e.g. in the +initialize method of whatever class it is that's using this data model. That should trigger the value transformer instance being created and registered so that Core Data can access it when it needs to.
Check out the example code here.
Does this do what you want?
#implementation UIImageToDataTransformer
+ (BOOL)allowsReverseTransformation {
return YES;
}
+ (Class)transformedValueClass {
return [NSData class];
}
- (id)transformedValue:(id)value {
NSData *data = UIImagePNGRepresentation(value);
return data;
}
- (id)reverseTransformedValue:(id)value {
UIImage *uiImage = [[UIImage alloc] initWithData:value];
return [uiImage autorelease];
}