"Can't allocate region" malloc error when running millions of iterations of loop - objective-c

Thanks to a lot of help I've received here on SO, I've gotten an algorithm to check a list of around 15,000 8-letter words for any partial anagrams, against a list of around 50,000 total words (so I suppose a total of 108 million iterations). I call this method once for each comparison (so 750 million times). I'm getting the following error, always somewhere in the midst of the 119th iteration through the 1,350 there should be:
AnagramFINAL(2960,0xac8c7a28) malloc: *** mmap(size=2097152) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
I've narrowed the memory issue down to being a huge number of allocated CFStrings (immutable). Any idea what I can do to remedy the issue? I'm using ARC and an #autoreleasepool, not sure what else I could do, it seems something isn't being released when it should be.
AnagramDetector.h
#import <Foundation/Foundation.h>
#interface AnagramDetector : NSObject {
NSDictionary *allEightLetterWords;
NSDictionary *allWords;
NSFileManager *fileManager;
NSArray *paths;
NSString *documentsDirectory;
NSString *filePath;
}
- (BOOL) does: (NSString *) longWord contain: (NSString *) shortWord;
- (NSDictionary *) setupAllWordList;
- (NSDictionary *) setupEightLetterWordList;
- (void) saveDictionary: (NSMutableDictionary *)currentArray;
#end
AnagramDetector.m
#implementation AnagramDetector
- (id) init {
self = [super init];
if (self) {
fileManager = [NSFileManager defaultManager];
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
}
return self;
}
- (BOOL) does: (NSString *) longWord contain: (NSString *) shortWord {
#autoreleasepool {
NSMutableString *longerWord = [longWord mutableCopy];
for (int i = 0; i < [shortWord length]; i++) {
NSString *letter = [shortWord substringWithRange: NSMakeRange(i, 1)];
NSRange letterRange = [longerWord rangeOfString: letter];
if (letterRange.location != NSNotFound) {
[longerWord deleteCharactersInRange: letterRange];
} else {
return NO;
}
}
return YES;
}
}
- (NSDictionary *) setupAllWordList {
#autoreleasepool {
NSString *fileWithAllWords = [[NSBundle mainBundle] pathForResource:#"AllDefinedWords" ofType:#"plist"];
allWords = [[NSDictionary alloc] initWithContentsOfFile: fileWithAllWords];
NSLog(#"Total number of words: %d.", [allWords count]);
}
return allWords;
}
- (NSDictionary *) setupEightLetterWordList {
#autoreleasepool {
NSString *fileWithEightWords = [[NSBundle mainBundle] pathForResource:#"AllDefinedEights" ofType:#"plist"];
allEightLetterWords = [[NSDictionary alloc] initWithContentsOfFile: fileWithEightWords];
NSLog(#"Total number of words: %d.", [allEightLetterWords count]);
}
return allEightLetterWords;
}
- (void) saveDictionary: (NSMutableDictionary *)currentArray {
#autoreleasepool {
filePath = [documentsDirectory stringByAppendingPathComponent: #"A.plist"];
[fileManager createFileAtPath:filePath contents: nil attributes: nil];
[currentArray writeToFile: filePath atomically:YES];
[currentArray removeAllObjects];
}
}
#end
Code running on launch (inside AppDelegate for now, since no VC):
#autoreleasepool {
AnagramDetector *detector = [[AnagramDetector alloc] init];
NSDictionary *allWords = [[NSDictionary alloc] initWithDictionary:[detector setupAllWordList]];
NSDictionary *eightWords = [[NSDictionary alloc] initWithDictionary:[detector setupEightLetterWordList]];
int remaining = [eightWords count];
for (NSString *currentEightWord in eightWords) {
if (remaining % 10 == 0) NSLog(#"%d ::: REMAINING :::", remaining);
for (NSString *currentAllWord in allWords) {
if ([detector does: [eightWords objectForKey: currentEightWord] contain: [allWords objectForKey: currentAllWord]]) {
// NSLog(#"%# ::: CONTAINS ::: %#", [eightWords objectForKey: currentEightWord], [allWords objectForKey: currentAllWord]);
}
}
remaining--;
}
}

The problem seems to be that a lot of autoreleased objects fill up the memory waiting to be released. So a solution is to add your own autorelease pool scope to collect autoreleased objects and release them sooner.
I suggest that you do something like this:
for (NSString *currentEightLetterWord in [eightLetterWordsDictionary allKeys]) {
#autoreleasepool {
for (NSString *currentWord in [allWordsDictionary allKeys]) {
}
}
}
Now all autoreleased objects inside #autoreleasepool { .. } will be released for each iteration of the outer loop.
As you see ARC might save you from thinking about most reference counting and memory management issues but objects can still end up in autorelease pools with ARC when using methods that directly or indirectly create autoreleased objects.
An alternative solution that I don't really recommend is to try to avoid using method that will use autorelease. Then does:contain: could awkwardly be rewritten to something like this:
- (BOOL) does: (NSString* ) longWord contain: (NSString *) shortWord {
NSMutableString *haystack = [longWord mutableCopy];
NSMutableString *needle = [shortWord mutableCopy];
while([haystack length] > 0 && [needle length] > 0) {
NSMutableCharacterSet *set = [[NSMutableCharacterSet alloc] init];
[set addCharactersInRange:NSMakeRange([needle characterAtIndex:0], 1)];
if ([haystack rangeOfCharacterFromSet:set].location == NSNotFound) return NO;
haystack = [haystack mutableCopy];
[haystack deleteCharactersInRange:NSMakeRange(0, [haystack rangeOfCharacterFromSet: set].location)];
needle = [needle mutableCopy];
[needle deleteCharactersInRange:NSMakeRange(0, 1)];
}
return YES;
}

Related

I am trying to pull the value based on the key in a NSArray in Objective-C that are passed in a URL. How do I get the value form the key? [duplicate]

I have an NSURL:
serverCall?x=a&y=b&z=c
What is the quickest and most efficient way to get the value of y?
Thanks
UPDATE:
Since 2010 when this was written, it seems Apple has released a set of tools for that purpose. Please see the answers below for those.
Old-School Solution:
Well I know you said "the quickest way" but after I started doing a test with NSScanner I just couldn't stop. And while it is not the shortest way, it is sure handy if you are planning to use that feature a lot. I created a URLParser class that gets these vars using an NSScanner. The use is a simple as:
URLParser *parser = [[[URLParser alloc] initWithURLString:#"http://blahblahblah.com/serverCall?x=a&y=b&z=c&flash=yes"] autorelease];
NSString *y = [parser valueForVariable:#"y"];
NSLog(#"%#", y); //b
NSString *a = [parser valueForVariable:#"a"];
NSLog(#"%#", a); //(null)
NSString *flash = [parser valueForVariable:#"flash"];
NSLog(#"%#", flash); //yes
And the class that does this is the following (*source files at the bottom of the post):
URLParser.h
#interface URLParser : NSObject {
NSArray *variables;
}
#property (nonatomic, retain) NSArray *variables;
- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
URLParser.m
#implementation URLParser
#synthesize variables;
- (id) initWithURLString:(NSString *)url{
self = [super init];
if (self != nil) {
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
NSMutableArray *vars = [NSMutableArray new];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
[vars addObject:[tempString copy]];
}
self.variables = vars;
[vars release];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
for (NSString *var in self.variables) {
if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:#"="]]) {
NSString *varValue = [var substringFromIndex:[varName length]+1];
return varValue;
}
}
return nil;
}
- (void) dealloc{
self.variables = nil;
[super dealloc];
}
#end
*if you don't like copying and pasting you can just download the source files - I made a quick blog post about this here.
So many custom url parsers here, remember NSURLComponents is your friend!
Here is an example where I pull out a url encoded parameter for "page"
Swift
let myURL = "www.something.com?page=2"
var pageNumber : Int?
if let queryItems = NSURLComponents(string: myURL)?.queryItems {
for item in queryItems {
if item.name == "page" {
if let itemValue = item.value {
pageNumber = Int(itemValue)
}
}
}
}
print("Found page number: \(pageNumber)")
Objective-C
NSString *myURL = #"www.something.com?page=2";
NSURLComponents *components = [NSURLComponents componentsWithString:myURL];
NSNumber *page = nil;
for(NSURLQueryItem *item in components.queryItems)
{
if([item.name isEqualToString:#"page"])
page = [NSNumber numberWithInteger:item.value.integerValue];
}
"Why reinvent the wheel!" - Someone Smart
I'm pretty sure you have to parse it yourself. However, it's not too bad:
NSString * q = [myURL query];
NSArray * pairs = [q componentsSeparatedByString:#"&"];
NSMutableDictionary * kvPairs = [NSMutableDictionary dictionary];
for (NSString * pair in pairs) {
NSArray * bits = [pair componentsSeparatedByString:#"="];
NSString * key = [[bits objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString * value = [[bits objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[kvPairs setObject:value forKey:key];
}
NSLog(#"y = %#", [kvPairs objectForKey:#"y"]);
In Swift you can use NSURLComponents to parse the query string of an NSURL into an [AnyObject].
You can then create a dictionary from it (or access the items directly) to get at the key/value pairs. As an example this is what I am using to parse a NSURL variable url:
let urlComponents = NSURLComponents(URL: url, resolvingAgainstBaseURL: false)
let items = urlComponents?.queryItems as [NSURLQueryItem]
var dict = NSMutableDictionary()
for item in items{
dict.setValue(item.value, forKey: item.name)
}
println(dict["x"])
I've been using this Category: https://github.com/carlj/NSURL-Parameters.
It's small and easy to use:
#import "NSURL+Parameters.h"
...
NSURL *url = [NSURL URLWithString:#"http://foo.bar.com?paramA=valueA&paramB=valueB"];
NSString *paramA = url[#"paramA"];
NSString *paramB = url[#"paramB"];
You can use Google Toolbox for Mac.
It adds a function to NSString to convert query string to a dictionary.
http://code.google.com/p/google-toolbox-for-mac/
It works like a charm
NSDictionary * d = [NSDictionary gtm_dictionaryWithHttpArgumentsString:[[request URL] query]];
Here's a Swift 2.0 extension that provides simple access to parameters:
extension NSURL {
var params: [String: String] {
get {
let urlComponents = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)
var items = [String: String]()
for item in urlComponents?.queryItems ?? [] {
items[item.name] = item.value ?? ""
}
return items
}
}
}
Sample usage:
let url = NSURL(string: "http://google.com?test=dolphins")
if let testParam = url.params["test"] {
print("testParam: \(testParam)")
}
I wrote a simple category to extend NSString/NSURL that lets you extract URL query parameters individually or as a dictionary of key/value pairs:
https://github.com/nicklockwood/RequestUtils
I did it using a category method based on #Dimitris solution
#import "NSURL+DictionaryValue.h"
#implementation NSURL (DictionaryValue)
-(NSDictionary *)dictionaryValue
{
NSString *string = [[self.absoluteString stringByReplacingOccurrencesOfString:#"+" withString:#" "]
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *temp;
NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&temp])
{
NSArray *parts = [temp componentsSeparatedByString:#"="];
if([parts count] == 2)
{
[dict setObject:[parts objectAtIndex:1] forKey:[parts objectAtIndex:0]];
}
}
return dict;
}
#end
All of the current answers are version specific or needlessly wasteful. Why create a dictionary if you only want one value?
Here's a simple answer that supports all iOS versions:
- (NSString *)getQueryParam:(NSString *)name fromURL:(NSURL *)url
{
if (url)
{
NSArray *urlComponents = [url.query componentsSeparatedByString:#"&"];
for (NSString *keyValuePair in urlComponents)
{
NSArray *pairComponents = [keyValuePair componentsSeparatedByString:#"="];
NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
if ([key isEqualToString:name])
{
return [[pairComponents lastObject] stringByRemovingPercentEncoding];
}
}
}
return nil;
}
You can do that easy :
- (NSMutableDictionary *) getUrlParameters:(NSURL *) url
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *tmpKey = [url query];
for (NSString *param in [[url query] componentsSeparatedByString:#"="])
{
if ([tmpKey rangeOfString:param].location == NSNotFound)
{
[params setValue:param forKey:tmpKey];
tmpKey = nil;
}
tmpKey = param;
}
[tmpKey release];
return params;
}
It return Dictionary like it : Key = value
I edited Dimitris' code slightly for better memory management and efficiency. Also, it works in ARC.
URLParser.h
#interface URLParser : NSObject
- (void)setURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
URLParser.m
#import "URLParser.h"
#implementation URLParser {
NSMutableDictionary *_variablesDict;
}
- (void)setURLString:(NSString *)url {
[_variablesDict removeAllObjects];
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
NSString *dataString = [tempString copy];
NSArray *sepStrings = [dataString componentsSeparatedByString:#"="];
if ([sepStrings count] == 2) {
[_variablesDict setValue:sepStrings[1] forKeyPath:sepStrings[0]];
}
}
}
- (id)init
{
self = [super init];
if (self) {
_variablesDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
NSString *val = [_variablesDict valueForKeyPath:varName];
return val;
return nil;
}
-(NSString *)description {
return [NSString stringWithFormat:#"Current Variables: %#", _variablesDict];
}
#end
Quickest is:
NSString* x = [url valueForQueryParameterKey:#"x"];

run applescript from cocoa app stopped working

This code had been working fine until just recently. I hadn't' changed anything nor upgraded my system and I'm completely flummoxed.
I've been using it for 6 years and now it dies on me.
Is there an easier or better way of running an applescript from within a cocoa application? At this point I'm happy to pay to fix this problem!
utils.h
#import <Foundation/Foundation.h>
#interface Utils : NSObject
// Runs an applescript with a given map of variables (name/value)
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables;
// Runs an applescript from a file pathwith a given map of variables
// (name/value)
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables;
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor;
// String is empty or only has white characters (space, tab...)
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string;
#end
Utils.M
#import "Utils.h"
#implementation Utils
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor {
// Enumerate the apple descriptors (lists) returned by the applescript and
// make them into arrays
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger counter, count = [descriptor numberOfItems];
for (counter = 1; counter <= count; counter++) {
NSAppleEventDescriptor *desc = [descriptor descriptorAtIndex:counter];
if (nil != [desc descriptorAtIndex:1]) {
[returnArray addObject:[Utils arrayFromDescriptor:desc]];
} else {
NSString *stringValue = [[descriptor descriptorAtIndex:counter] stringValue];
if (nil != stringValue) {
[returnArray addObject:stringValue];
} else {
[returnArray addObject:#""];
}
}
}
return returnArray;
}
+ (NSString *)escapeCharacters:(NSString *)string {
return [string stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
}
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables {
NSString *input = #"";
NSArray *variableNames = [variables allKeys];
// Transform the dictionary of names/values to set sentences of applescript
for (NSString *variableName in variableNames) {
NSObject *variableValue = [variables objectForKey:variableName];
if ([variableValue isKindOfClass:[NSString class]]) {
input =
[input stringByAppendingString:[NSString stringWithFormat:#"set %# to (\"%#\" as text)\n", variableName,
[Utils escapeCharacters:variableValue], nil]];
} else if ([variableValue isKindOfClass:[NSNumber class]]) {
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to (%# as integer)\n",
variableName, variableValue, nil]];
} else if ([variableValue isKindOfClass:[NSArray class]]) {
// Initialize a list
NSString *entry;
NSArray *values = (NSArray *)variableValue;
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to {", variableName]];
BOOL first = TRUE;
for (entry in values) {
if (!first) {
input = [input stringByAppendingString:#", "];
}
input = [input
stringByAppendingString:[NSString stringWithFormat:#"\"%#\"", [Utils escapeCharacters:entry], nil]];
first = FALSE;
}
input = [input stringByAppendingString:#"}\n"];
}
}
NSString *finalScript = [input stringByAppendingString:[NSString stringWithFormat:#"\n\n%#", source]];
NSLog(#"Final script: %#", finalScript);
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:finalScript];
NSDictionary *error;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
NSLog(#"applescript error: %#", [error description]);
// Transform the return value of applescript to nested nsarrays
return [Utils arrayFromDescriptor:descriptor];
}
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables {
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:scriptName ofType:#"applescript"];
NSString *scriptSource =
[[NSString alloc] initWithContentsOfFile:scriptPath encoding:NSASCIIStringEncoding error:nil];
return [Utils runApplescript:scriptSource withVariables:variables];
}
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [string isEqualToString:#""];
}
#end
Easier, yes, although whether that’s your actual problem is another question.
http://appscript.sourceforge.net/asoc.html
I assume you’ve already got other details, including sandboxing and hardening settings and plist entries, taken care of. (Recent Xcode upgrades also had a habit of breaking it when auto-upgrading your project files, by turning on hardening for you so Apple events can’t get out.)

Converting property lists into Objective-C literals

I'm not sure if this is possible, but I've seen people do crazy things with regex and other tools.
I want to convert this plist to an Objective-C literals:
<dict>
<key>ar</key>
<array>
<string>+54## #### ####</string>
<string>## #### ####</string>
</array>
<key>at</key>
<array>
<string>+43 1 ########</string>
<string>+43 ############</string>
</dict>
converted to:
NSDictionary *dic = #{
#"ar" : #[#"+54## #### ####", #"## #### ####"],
#"at" : #[#"+43 1 ########",#"+43 ############"]
};
Is it possible to automate such conversion? This guy did something similiar: he parsed a PHP list into an NSDictionary using VIM.
Plist don't have a separate 'format' for use in code (this question doesn't quite make sense as-is). You either want to 1. generate Objective-C code which initializes the dictionary with these values, or 2. initialize the dictionary using the file, for which you can write
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:#"UIPhoneFormats.plist"];
Edit: so you want to generate Objective-C code that in turn will reproduce the same dictionary. For this, you need to re-print the contents of the dictionary in a formatted way. You can write a program like this:
#import <stdio.h>
#import <Foundation/Foundation.h>
NSString *recursiveDump(id object)
{
if ([object isKindOfClass:[NSString class]]) {
return [NSString stringWithFormat:#"#\"%#\"", object];
} else if ([object isKindOfClass:[NSNumber class]]) {
return [NSString stringWithFormat:#"#%#", object];
} else if ([object isKindOfClass:[NSArray class]]) {
NSMutableString *str = [NSMutableString stringWithString:#"#["];
NSInteger size = [object count];
NSInteger i;
for (i = 0; i < size; i++) {
if (i > 0) [str appendString:#", "];
[str appendString:recursiveDump([object objectAtIndex:i])];
}
[str appendString:#"]"];
return str;
} else if ([object isKindOfClass:[NSDictionary class]]) {
NSMutableString *str = [NSMutableString stringWithString:#"#{"];
NSString *key;
NSInteger size = [object count];
NSArray *keys = [object allKeys];
NSInteger i;
for (i = 0; i < size; i++) {
if (i > 0) [str appendString:#", "];
key = [keys objectAtIndex:i];
[str appendFormat:#"%#: %#", recursiveDump(key), recursiveDump([object objectForKey:key])];
}
[str appendString:#"}"];
return str;
} else {
// feel free to implement handling NSData and NSDate here,
// it's not that straighforward as it is for basic data types.
}
}
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:#"UIPhoneFormats.plist"];
NSMutableString *code = [NSMutableString stringWithString:#"NSDictionary *dict = "];
[code appendString:recursiveDump(dict)];
[code appendString:#";"];
printf("%s\n", [code UTF8String]);
[pool release];
return 0;
}
This program will generate (hopefully syntax error-free) Objective-C initialization code out of the provided property list which can be copy-pasted to a project and be used.
Edit: I just run the program on a stripped version of the plist file OP has provided (the original file was way too large, so I cut it a bit) and it generated the following code:
NSDictionary *dict = #{#"at": #[#"+43 1 ########", #"+43 ############", #"01 ########", #"00 $"], #"ar": #[#"+54## #### ####", #"## #### ####", #"00 $", #"18 ### $ "]};
To verify it was really valid, I pasted this into the middle of an int main() to a file called 'test.m', so I got this program:
#import <Foundation/Foundation.h>
int main()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *dict = #{#"at": #[#"+43 1 ########", #"+43 ############", #"0$
NSLog(#"%#", dict);
[pool release];
return 0;
}
To verify, I run clang -o test test.m -lobjc -framework Foundation and surprise, surprise:
It worked.
Edit 2: I made this a command line utility, just to facilitate further work - who knows, this may be useful in the future. Plist2ObjC
Hope this helps.
What you need is Serializing a Property List
NSData* plistData = [source dataUsingEncoding:NSUTF8StringEncoding];
NSString *error;
NSPropertyListFormat format;
NSDictionary* plist = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
NSLog( #"plist is %#", plist );
if(!plist){
NSLog(#"Error: %#",error);
[error release];
}
If you want to get it as a string, use this,
NSString *aString = [NSString stringWithFormat:#"%#", plist];
or else you can call NSString *aString = [plist description] to get the same as a string.
Source

What is the best practices for sharing methods across classes in an app?

I'm still trying to wrap my head around how things should be done in the object-oriented world and I think my problem is that I don't understand how to best utilize encapsulation. Specifically, I have lots of small bits of code that I use in several classes in my project. For example:
+ (NSString *)getFormattedDate;
+ (NSString *)getResultsFilePath;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
These are all 3-5 line methods that I use in more than one class. My standard practice has been to put these snippets into a Utility.inc file and call them when I need them. Is that appropriate in the object-oriented world or should each class be self-contained? And if it's appropriate, would you put the code into a singleton or just a regular class file and [[Utilities alloc] init] in each class where you want to use the methods?
Look into using Categories. For the examples you gave, these are methods related to objects of a particular class that happen to be used in several of your own classes. Categories will allow you to park these often used methods where they can be associated with the common factors.
Create a utitity singelton which will be created only ones and then used by the other classes.
Thanks for the answers. I'm not sure that this the right way to do things, but this is what I've done on the projects I just submitted.
I made two classes, one for Utility methods and one for globals. The methods in the Utilities class are all class methods since they operate on files and constants or globals. Then I made a singleton for global variables. I have all of my global constants in the .pch file. Also in the .pch file I put the following two lines of code so that the utilities and globals are available everywhere.
// File for utilities like file delete, find Documents file
#import "Utilities.h"
#import "Globals.h"
Accessing the methods is straightforward. Here's an example of a call to both methods to generate an HTML header for an email.
NSString *gameNameHeader = [NSString stringWithFormat:#"<p>&nbsp</p><h1>%# Results</h1><h2>%#%#</h2>",GAME_NAME_TITLE,[Utilities formattedClientName], [Utilities formattedDate]];
In case anyone can use it, here is my current version of the code. (Sorry for the formatting-I can't seem to get the wiki to cooperate.)
#interface Utilities : NSObject {
}
+ (NSString *)formattedDate;
+ (NSString *)formattedClientName;
+ (NSString *)cachedResultsFilePath;
+ (NSString *)cachedResultsFileContents;
+ (NSString *)resultsFileName;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
+ (NSString *)applicationCachesDirectory;
+ (NSString *)applicationDocumentsDirectory;
+ (NSString *)applicationLibraryDirectory;
+ (NSError *)copyCachedResultsToFile;
#end
#import "Utilities.h"
#implementation Utilities {
}
+ (NSString *)formattedDate {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSString *todaysDate = [dateFormatter stringFromDate:[NSDate date]];
return todaysDate;
}
+ (NSString *)formattedClientName {
NSString *client = [NSString stringWithFormat:#" "];
if( [Globals sharedInstance].currentClient ) client = [NSString stringWithFormat:#" %# ",[Globals sharedInstance].currentClient];
return client;
}
+ (NSString *)cachedResultsFilePath {
NSString *resultsFilePath = [[self applicationCachesDirectory] stringByAppendingPathComponent:#"Results.txt"];
return resultsFilePath;
}
+ (NSString *)cachedResultsFileContents {
NSStringEncoding encoding; NSError* error = nil;
NSString *resultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
return resultsText;
}
+ (NSString *)resultsFileName {
return [NSString stringWithFormat:#"%# Results%#%#.html",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
}
+ (NSError *)removeFileFromCache:(NSString *)fileName {
NSError *error = nil;
NSFileManager *localFileManager=[[NSFileManager alloc] init];
NSString *fullPath = [NSString stringWithFormat:#"%#/%#", [self applicationCachesDirectory],fileName];
[localFileManager removeItemAtPath: fullPath error:&error ];
return error;
}
+ (NSString *)applicationCachesDirectory {
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationLibraryDirectory {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSError *)copyCachedResultsToFile {
// Grab the header and footer and put it around the cached data
NSStringEncoding encoding; NSError *error = nil;
NSString *htmlHeaderTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_header" ofType:#"html" ];
NSString *htmlHeaderText = [NSString stringWithContentsOfFile:htmlHeaderTextPath usedEncoding:&encoding error:&error];
NSString *cachedResultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
// Write the results to a file if there are any
if (cachedResultsText) {
NSString *htmlFooterTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_footer" ofType:#"html" ];
NSString *htmlFooterText = [NSString stringWithContentsOfFile:htmlFooterTextPath usedEncoding:&encoding error:&error];
NSString *gameNameHeader = [NSString stringWithFormat:#"<h1>%# Results for%#%#</h1>",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
NSString *tempStringP1 = [htmlHeaderText stringByAppendingString:gameNameHeader];
NSString *tempStringP2 = [tempStringP1 stringByAppendingString:cachedResultsText];
NSString *formattedTextForPrinting = [tempStringP2 stringByAppendingString:htmlFooterText];
NSString *resultsFilePath = [ [Utilities applicationDocumentsDirectory] stringByAppendingPathComponent:[self resultsFileName] ];
if ( !([[NSFileManager defaultManager] fileExistsAtPath:resultsFilePath]) ) {
if (! ([[NSFileManager defaultManager] createFileAtPath:resultsFilePath contents:nil attributes:nil]) ) {
NSLog(#"Error was code: %d - message: %s", errno, strerror(errno));
}
}
NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:resultsFilePath];
[fileHandler writeData:[formattedTextForPrinting dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandler closeFile];
}
return error;
}
#end
Globals in a singleton. Probably not thread-safe, but I don't care right now.
#interface Globals : NSObject {
}
#property (nonatomic, strong) NSString *currentClient;
#property (nonatomic, strong) NSString *showmePict;
#property BOOL checkBoxes;
+ (Globals *)sharedInstance;
- (void)resetClient;
#end
#implementation Globals {
}
static Globals *singleton = nil;
#synthesize currentClient = _currentClient;
#synthesize showmePict = _showmePict;
#synthesize checkBoxes = _checkBoxes;
+(Globals *) sharedInstance {
NSLog (#"sharedInstance of Globals called.");
if (nil != singleton) return singleton;
static dispatch_once_t pred; // lock
dispatch_once(&pred, ^{ // this code is at most once
singleton = [[Globals alloc] init];
});
return singleton;
}
- (void)resetClient {
self.currentClient = nil;
}
#end

NSArray with componentsSeparatedByString memory leak

I'm having problems with memory leaks with this function. I thought creating an NSArray with componentsSeparatedByString was autorelease but instruments seems to indicate a leak at the NSArray aPair. Why would it indicate a leak there and not also at the other NSArrays created in the same way?
-(void) checkRequest: (NSString *)request view:(UIViewController *)theView webView:(UIWebView *)wView
{
//NSLog(#"JSResponder - checkRequest()");
NSString *aRequest = [NSString stringWithString:request];
NSArray *urlArray = [aRequest componentsSeparatedByString:#"?"];
if([urlArray count] > 1)
{
NSString *paramsString = [urlArray lastObject];
NSString *cmd = #"";
NSArray *urlParamsArray = [paramsString componentsSeparatedByString:#"&"];
int numCommands = [urlParamsArray count];
NSMutableDictionary *paramsWithNames = [[NSMutableDictionary alloc ] initWithCapacity:numCommands];
for (NSString *elementPair in urlParamsArray)
{
NSArray *aPair = [elementPair componentsSeparatedByString:#"="];
NSString *aKey = [aPair objectAtIndex:0];
NSString *aParam = [aPair objectAtIndex:1];
if([aKey compare:#"_command"] == NSOrderedSame)
{
cmd = aParam;
}
else
{
[paramsWithNames setValue: aParam forKey:aKey];
}
}
[self executeCommand: cmd withParams: paramsWithNames view:theView webView:wView];
[paramsWithNames release];
}
}
This function get called by the following:
- (void)pullJSEvent:(NSTimer*)theTimer
{
NSLog(#"MainView - pullJSEvent()");
NSString *jsCall = [NSString stringWithString:#"if(typeof checkOBJCEvents == 'function'){checkOBJCEvents();}"];
NSString *jsAnswer = [[webView stringByEvaluatingJavaScriptFromString:jsCall] retain];
if([jsAnswer compare:#"none"] != NSOrderedSame)
{
//NSLog(#" answer => %#", jsAnswer);
[jsResponder checkRequest:jsAnswer view:(UIViewController *)self webView:self.webView];
}
[jsAnswer release];
}
Thank-you
You're going to have to dig a bit deeper with the Leaks instrument. You're leaking one of the strings in the array, not the array itself. Leaks indicates that line because that's where the strings in the array are allocated.
Go into Leaks, look at a leaked instance, and click that little arrow button. You'll see all the retains and releases of the leaked object, which should point you to the problem.