Initialize static NSString at class level - objective-c

I have a NSString that should be constant in my class. I used the following code to accomplish this:
#interface DefinitionViewController ()
#end
static NSString *preamble;
#implementation DefinitionViewController {
}
+(void)initialize {
if (self == [DefinitionViewController class]) {
NSString *path = [[NSBundle mainBundle] pathForResource:#"preamble" ofType:#"html"];
preamble = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding
error:nil];
}
}
It seems to work fine. I worry about using a file read inside an initialize. Is there a more appropriate means to accomplish the same goal (shared static string)? I could bury this inside my code, but it was much easier to maintain the somewhat large string as an external file.
Thanks for any advice.

"I worry about using a file read inside an initialize".
Don't (worry). The fact that it is, for example, a class method is utterly irrelevant. It is code. It runs and does its job. It is sound code, it runs coherently at a coherent time, and your app bundle is a real thing that really contains the resource. There's no problem here.
If you want to postpone creation of the string, and make assurance doubly sure that the string is not initialized twice, you could instead use a singleton pattern so that the string value is not generated until the first time it is explicitly requested:
+ (NSString*) preamble {
static NSString* preamble = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *path = [[NSBundle mainBundle] pathForResource:#"preamble" ofType:#"html"];
preamble = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
});
return preamble;
}
But there is no special need for this. (EDIT: But see, to the contrary, the comment below of #bbum, who Really Knows Stuff.)

Related

Bad Access Code 1 when using a Singleton

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.

How to use a class variable?

I have a class method loadImage:(NSString *)pathto load image from path, if the path is nil, the load the default path image.
+(NSImage *) loadImage:(NSString *)path{
if(path== nil){
path = [[NSBundle mainBundle] pathForResource:#"default" ofType:#"png"];
}
}
because the default path is always using the same path, I want to calculate the path only once if I run the method 1000 times, like
if(defaultPath == nil){
defaultPath = [[NSBundle mainBundle] pathForResource:#"default" ofType:#"png"];
}
path = defaultPath;
, I think I can use static variable, but I don't know how to do it, please help me, I'm glad to know any suggestions about improving performance.
Simply use a static variable to hold the default path:
static NSString *defaultPath = …;
if (path == nil) {
path = defaultPath;
}
But if you're doing it for performance reasons, first make sure it's worth it. Most probably it's a premature optimization that's not worth the trouble.
There is a useful pattern based on GCD which handles this sort of situation nicely:
+(NSImage *) loadImage:(NSString *)path{
static NSString *storedPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
storedPath = [[NSBundle mainBundle] pathForResource:#"default" ofType:#"png"];
});
// Do whatever you need to do with the default resource path.
}
dispatch_once does exactly what you are after - it ensures that the initialization code is run only once. It is also thread safe.
Xcode's code completion even helps you use the pattern - if you start typing dispatch_once then you'll get the onceToken template directly.

NSString WriteToFile not stay permanent?

I am trying to save text stored in an NSString variable in a text file that is stored with the main bundle of my project.
So far I have had no success and tried a lot of different methods.
Why doesn't this stay permanent?
NSString *pathToFile = [[NSString alloc]init];
pathToFile = [[NSBundle mainBundle] pathForResource:#"ListOfSavedImages" ofType:#"txt"];
NSLog(#"%#",pathToFile);
NSString *stringToWriteToFile = [[NSString alloc]init];
stringToWriteToFile=#"Adam";
NSLog(#"%#",stringToWriteToFile);
[stringToWriteToFile writeToFile:pathToFile atomically:YES encoding:NSUTF8StringEncoding error:NULL];
NSLog(#"called!");
NSString *contentsOfFile1 = [NSString stringWithContentsOfFile:pathToFile encoding:NSUTF8StringEncoding error:NULL];
NSLog(#"%#",contentsOfFile1);
The actual file doesn't change although the NSLog at the end of this code segment outputs "Adam" but I am also nslogging the contents of the file when the view loads and it always reverts back to the original text(it never actually changes). What am I doing wrong?
I am using Xcode 4.3, ARC, and storyboards.
As you are instantiating your variables locally, they will leak away when you hit the end of the block }.
Try using IVars declared as properties of the particular view controller, synthesized in the .m file.
Look at the C139p at Stanford Course on ITunes, preferably the earlier series given before ARC as this fully explains the concept of data persistence.

iOS/iPhone SDK: initWithCoder and encodeWithCoder not being called

I'm trying to save an NSMutableArray called queueArray so it can be loaded again after the app has been quit. I used a few tutorials to get me going and this is the code I have come up with. The problem seems to be that "initWithCoder" and "encodeWithCoder" are not being called, shown by no NSLog calls and no stopping at breakpoints. I have added the NSCoding protocol to the .h file and I know that queueArray is not nil and it contains MPMediaItems. Here is some of the code I use to try to save and load the array:
-(IBAction)saveQueuePressed {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [rootPath stringByAppendingPathComponent:#"queueArray.archive"];
//should cause encodeWithCoder to be called
[NSKeyedArchiver archiveRootObject:queueArray toFile:filePath];
}
-(IBAction)loadQueuePressed {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [rootPath stringByAppendingPathComponent:#"queueArray.archive"];
//should cause initWithCoder to be called
queueArray = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
}
-(void)encodeWithCoder:(NSCoder *)coder {
NSLog(#"encodeWithCoder");
[coder encodeObject:queueArray forKey:#"savedQueueArray"];
}
-(id)initWithCoder:(NSCoder *)decoder {
NSLog(#"initWithCoder");
queueArray = [decoder decodeObjectForKey:#"savedQueueArray"];
return self;
}
Any help will be greatly appreciated!
The encodeWithCoder: and initWithCoder methods are called when you archive/unarchive an object that responds to them. From what I understand, you have those methods in your class, but the object you are actually archiving (queueArray) is not an instance of that class, it's an NSMutableArray.
If you do want to save your entire object, you can change your saveQueue method to this
-(IBAction)saveQueuePressed {
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [rootPath stringByAppendingPathComponent:#"queueArray.archive"];
// saving the array shouldn't, but saving the self object
//should cause encodeWithCoder to be called:
[NSKeyedArchiver archiveRootObject:self toFile:filePath];
}
But if you just want to save the array, I guess you can just use saveQueuePressed and loadQueuePressed, I don't think you need the encode/init WithCoder: methods
Update:
Maybe your path is not right.
Try
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [[rootPath stringByAppendingPathComponent:#"queueArray.archive"] stringByExpandingTildeInPath]];
Filipe is right! Your comment said you still didn't use his method.
I had this issue too. Switching from the dictionary's atomic write method to the keyedArchiver fixed it, luckily I only had to change one thing, the line that said writeToFile: is now the archive function.
Now my program's working. For some reason, even when responding to NSCoding, the custom object is not being encoded and breaks my dictionary. Is this a bug with iOS? I've read a fair number of Apple Manuals, but I've also seen a fair number of typos and missing info (For example, try MKMapRect functions without the videos to explain them), or Core Animations referencing the Run Loop before you learn threading, I could go on, half finished sentences in Quartz... so yeah, I've read the manuals and this perplexes me, we have to get a more open iOS SDK at some point, hopefully

How can text files be written using Objective-C?

In .NET, a file can be written to the file system using:
FileStream fs = File.Create(#"Filename");
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("Hello, World!");
// etc...
sw.Close();
fs.Close();
How would I achieve the same operation in Objective-C and Cocoa? I believe it involves the NSMutableData class, but I do not know how to implement it.
Tiny Mac Tutorials has a post on this.
The example code from that post is below:
// filetest.m
// Created by macateeny.blogspot.com Sept 2008.
// Copyleft (c) 2008. some rights reserved.
//
// Compile from the command line with:
// gcc filetest.m -Wall -o filetest -framework Foundation
//
#import <Foundation/Foundation.h>
// main entry point of our file test tool with the argument counter and vector
int main(int argc,char *argv[])
{
// allocate a memory pool for our NSString Objects
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// declare NSString Obj pointer and initialise it
NSString *str = #"Cooking with Objective-C\r\n";
// declare NSString filename and alloc string value
NSString *filenameStr = #"./filetest.txt";
// NSObject which contains all the error information
NSError *error;
// write contents and check went ok
if(![str writeToFile: filenameStr atomically: YES encoding:NSUTF8StringEncoding error:&error]) {
NSLog(#"We have a problem %#\r\n",[error localizedFailureReason]);
}
// unleash the allocated pool smithers
[pool release];
// The app is terminated
return 0;
}
Note that Objective C is a pure superset of standard C . Most of the usual posix library calls (in stdio, stdlib, etc.) are available and usable, as long as you don't try to use them to escape the app's sandbox (write to system directories, etc.)
So fopen() and fprintf() will also work perfectly well for writing ASCII or UTF8 text and data to files. You can use NSSearchPathForDirectoriesInDomains to find the appropriate directory names, and use various NSString convenience methods to convert NSStrings to UTF8.
See Apple's development docs - Cocoa concepts especially for this re Strings The Apple overview documents of the librairead all the concepts first it will give you a idea of what details you need
For the latest version of Cocoa on iOS or MacOS you can do this if you don't want to check for an error,
NSString *str = #"Wollah";
[str writeToFile:#"Wollah.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
NSString *str = #"Wollah";
[str writeToFile:#"/Wollah.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
Although using an NSURL is recommended, and I always do that.
NSString has a writeToFile method.