So I have these two methods:
-(void)importEvents:(NSArray*)allEvents {
NSMutableDictionary *subjectAssociation = [[NSMutableDictionary alloc] init];
for (id thisEvent in allEvents) {
if (classHour.SubjectShort && classHour.Subject) {
[subjectAssociation setObject: classHour.Subject forKey:classHour.SubjectShort];
}
}
[self storeSubjects:subjectAssociation];
}
-(void)storeSubjects:(NSMutableDictionary*)subjects {
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *subjectsList = [documentsDir stringByAppendingPathComponent:#"Subjects.plist"];
[subjects writeToFile:subjectsList atomically:YES];
}
The first loops through an array of let's say 100 items, and builds a NSMutableDictionary of about 10 unique key/value pairs.
The second method writes this dictionary to a file for reference elsewhere in my app.
The first method is called quite often, and so is the second. However, I know, that once the dictionary is built and saved, its contents won't ever change, no matter how often I call these methods, since the number of possible values is just limited.
Question: given the fact that the second method essentially needs to be executed only once, should I add some lines that check if the file already exists, essentially adding code that needs to be executed, or can I just leave it as is, overwriting an existing file over and over again?
Should I care? I should add that I don't seem to suffer from any performance issues, so this is more of a philosophical/hygienic question.
thanks
It depends.
You say
once the dictionary is built and saved, its contents won't ever change
until they do :-)
If your app is not suffering from any performance issues on this particular loop I wouldn't try to cache for the reason that unless you somehow remember that you have a once-only write on the file you are storing up a bug for later.
This could be mitigated by using an intention revealing name on the method. i.e
-(void)storeSubjectsOnceOnlyPerLaunch:(NSDictionary*)subjects
If I got my time back for tracing down bugs caused by caching, I would have several days back in my life.
Your solution is totally over engineered, and has tons of potential to go wrong. What if the users drive is full? Does this file get backed up? Does it need backing up / are you wasting the users time backing it up? Can this fail? Are you handling it? You are concentrating on the entering and storing of data, you should be focusing on accessing that data.
I'd have a readwrite property allEvents and a property eventAssociations, declared readonly in the interface, but readwrite in the implementation file.
The allEvents setter stores allEvents and sets _eventAssociations to nil.
The eventAssociations getter checks whether _eventAssociations is nil and recalculates it when needed. A simple and bullet-proof pattern.
Related
NSString *cachePath= [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
cachePath= [cachePath stringByAppendingPathComponent:#"nerd.archive"];
// Load the cached channel
RSSChannel *cachedChannel= [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];
NSLog(#"unarchived data- %# %p, x value- %d",cachedChannel,cachedChannel,cachedChannel.x);
// if one hasn't already been cached, create a blank one to fill up
if (!cachedChannel) {
cachedChannel= [[RSSChannel alloc] init];
NSLog(#"cachedChannel initialised- %# %p",cachedChannel,cachedChannel);
cachedChannel.x=5;
}
In the above code-snippet, the pointer variable cachedChannel is assigned with the return value of unarchiveObjectWithFile: message. Now obviously in the first run, this would return nil but the pointer will be initialized later on in the “if-statement”. Lets say the cachedChannel var is something like
cachedChannel= [RSSChannel:0X123ff]
After the code has gone through its first run, the object assigned to cachedChannel would become serialized.
When i run the test project the second time and the unarchiveObjectWithFile: message is passed so that the serialized object is returned and assigned to the cachedChannel pointer var, it shows up as a different object with a different object-id.
Instead of cachedChannel pointing to [RSSChannel:0X123ff] object, it is now holding some other object like [RSSChannel:0X445ee]
How could this be possible?? Shouldn’t the object that was serialized before be the one to be unarchived later on with the same object-id residing in the same heap memory location?
How could this be possible?? Shouldn’t the object that was serialized before be the one to be unarchived later on with the same object-id residing in the same heap memory location?
Not at all. This is, as you say, happening later. And at this later time, the memory situation is completely different. Think of it this way: if you have code that creates an object from scratch, e.g. [[MyObject alloc] init], and you run the app today and then quit it and run the app again tomorrow, those two instances of MyObject, even though they play the very same role in the life of the app, will have two different memory addresses.
Moreover, what we are creating as we unarchive the object is a different instance from the one that was archived - identical, in whatever ways you have specified while archiving / unarchiving, to the original, but a different instance. Think of it this way: archive-unarchive is an elaborate way of making a copy of the object - and two copies of one instance are, obviously, two different objects.
After all, you could archive the object, hang on to the original, and immediately unarchive the archived object. That would be two different objects. But they could not possibly live at the same memory address!
It sounds like you may be trying to use the memory address as some sort of unique identifier. Beware of that. If a thing needs a unique identifier, give it a unique identifier as a property. Don't rely on the memory address at runtime for anything, except during debugging to confirm that two instances are one and the same instance.
I would like to understand more about the way XCode/Objective-C handle constant strings. I found a related question, but I would like more information. Consider the following code:
NSString *a = [[NSString alloc] initWithUTF8String:[[_textFieldA stringValue] UTF8String]];
NSString *b = [[NSString alloc] initWithUTF8String:[[_textFieldB stringValue] UTF8String]];
NSString *c = [a copy];
NSString *d = [a mutableCopy];
Note that the textFields are just a way to set the strings at runtime ensuring that the compiler doesn't get too smart on me and build in a single instance.
If my text fields are empty, or contain a single character such as "x" or "$", then a == b == c == the same constant NSString instance. If I instead provide "xy", then a == c != b. d is always unique, as one might expect since it is mutable.
Now normally this wouldn't be an issue, I'm not trying to modify the contents of these strings, however, I am working on a system where I frequently use objc_setAssociatedObject. So here now I might come accross an empty string, and then set associated object data on it, and then have another empty string and collide with the first.
I have, for the moment, solved my issue by creating mutable strings instead.
So my questions:
Is this an Objective-C specification, or an XCode excentricity?
Does anyone know how the instance is determined? Why "x" get's one instance, but not "xy"? I would think some internal dictionary is involved and there's no good reason to stop at 1 character.
Is there a way to turn this off, so all empty strings are unique instances, or other suggestions?
I am using XCode 5.1.1, OSX 10.9.4, SDK 10.9.
Thank you!
Is this an Objective-C specification, or an XCode excentricity?
It is just implementation detail. Not documented any where. These kind of behaviour may changed in future without notice.
Does anyone know how the instance is determined? Why "x" get's one instance, but not "xy"? I would think some internal dictionary is involved and there's no good reason to stop at 1 character.
No until someone able to access source code want to share the details with us.
Is there a way to turn this off, so all empty strings are unique instances, or other suggestions?
No way to turn it off. Don't use objc_setAssociatedObject with NSString
As #Ken Thomases said in comment
In general, it probably doesn't make sense to use objc_setAssociatedObject() with any value class.
Some other examples are NSNumber, NSData and NSValue. They are often cached and reused.
I need to sort a NSMutableArray containing NSURLs with localizedStandardCompare:
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *f1 = [(NSURL *)obj1 absoluteString];
NSString *f2 = [(NSURL *)obj2 absoluteString];
return [f1 localizedStandardCompare:f2];
}];
This works fine, but I worry a bit about the performance: the block will be evaluated n log n times during the sort, so I'd like it to be fast (the array might have up to 100,000 elements). Since localizedStandardCompare is only available on NSString, I need to convert the URLs to strings. Above, I use absoluteString, but there are other methods that return a NSString, for example relativeString. Reading the NSURL class reference, I get the impression that relativeString might be faster, since the URL does not need to be resolved, but this is my first time with Cocoa and OS-X, and thus just a wild guess.
Additional constraint: in this case, all URLs come from a NSDirectoryEnumerator on local storage, so all are file URLs. It would be a bonus if the method would work for all kinds of URL, though.
My question: which method should I use to convert NSURL to NSString for best performance?
Profiling all possible methods might be possible, but I have only one (rather fast) OS-X machine, and who knows - one day the code might end up on iOS.
I'm using Xcode 4.5.2 on OS-X 10.8.2, but the program should work on older version, too (within reasonable bounds).
You may need to use Carbon's FSCatalogSearch, which is faster than NSDirectoryEnumerator. As for getting the path, I see no choice.
The only thing you may consider for speeding up the sorting is that the paths are partially sorted, because the file system will return all the files of the same folder in alphabetical order.
So you may want to take all the path of the same directory and merge them with the other results.
For example the home contents may be:
ab1.txt
bb.txt
c.txt
The documents directory may contain:
adf.txt
fgh.txt
So you just merge them with a customized algorithm, which just applies the merge part of a mergesort.
I benchmarked the sort. It turned out that absoluteString and relativeString are much faster that path or relativePath.
Sorting about 26000 entries:
relativeString 550ms
absoluteString 580ms
path 920ms
relativePath 960ms
field access 480ms
For field access, I put the value of absoluteString into a field prior to the sort and access that. So, the ...String accessors are almost as fast as field access, and thus a good choice for my use case.
In this simple test, after being sure that the index is valid, does it worth to assign a variable instead of calling two times objectAtIndex: method ?
NSString *s = [myArray objectAtIndex:2];
if (s) {
Test *t = [Test initFromString:s];
}
instead of
if ([myArray objectAtIndex:2]) {
Test *t = [Test initFromString:[myArray objectAtIndex:2]];
}
From the performance point of view it’s not worth it, unless the code lies on a really hot path (and you would know that). Sending a message is practically free and looking up an object on a given index is also too fast to care in most situations.
The change makes the code more readable, though: First, you can name the thing that you pull from the container (like testName). Second, when reading the two repeated calls to objectAtIndex you have to make sure that it’s really the same code. After you introduce the separate variable it’s obvious, there’s less cognitive load.
This works -- it does compile -- but I just wanted to check if it would be considered good practice or something to be avoided?
NSString *fileName = #"image";
fileName = [fileName stringByAppendingString:#".png"];
NSLog(#"TEST : %#", fileName);
OUTPUT: TEST : image.png
Might be better written with a temporary variable:
NSString *fileName = #"image";
NSString *tempName;
tempName = [fileName stringByAppendingString:#".png"];
NSLog(#"TEST : %#", tempName);
just curious.
Internally, compilers will normally break your code up into a representation called "Single Static Assignment" where a given variable is only ever assigned one value and all statements are as simple as possible (compound elements are separated out into different lines). Your second example follows this approach.
Programmers do sometimes write like this. It is considered the clearest way of writing code since you can write all statements as basic tuples: A = B operator C. But it is normally considered too verbose for code that is "obvious", so it is an uncommon style (outside of situations where you're trying to make very cryptic code comprehensible).
Generally speaking, programmers will not be confused by your first example and it is considered acceptable where you don't need the original fileName again. However, many Obj-C programmers, encourage the following style:
NSString *fileName = [#"image" stringByAppendingString:#".png"];
NSLog(#"TEST : %#", fileName);
or even (depending on horizontal space on the line):
NSLog(#"TEST : %#", [#"image" stringByAppendingString:#".png"]);
i.e. if you only use a variable once, don't name it (just use it in place).
On a stylistic note though, if you were following the Single Static Assigment approach, you shouldn't use tempName as your variable name since it doesn't explain the role of the variable -- you'd instead use something like fileNameWithExtension. In a broader sense, I normally avoid using "temp" as a prefix since it is too easy to start naming everything "temp" (all local variables are temporary so it has little meaning).
The first line is declaring an NSString literal. It has storage that lasts the lifetime of the process, so doesn't need to be released.
The call to stringByAppendingString returns an autoreleased NSString. That should not be released either, but will last until it gets to the next autorelease pool drain.
So assigning the result of the the stringByAppendingString call back to the fileName pointer is perfectly fine in this case. In general, however, you should check what your object lifetimes are, and handle them accordingly (e.g. if fileName had been declared as a string that you own the memory to you would need to release it, so using a temp going to be necessary).
The other thing to check is if you're doing anything with fileName after this snippet - e.g. holding on to it in a instance variable - in which case your will need to retain it.
The difference is merely whether you still need the reference to the literal string or not. From the memory management POV and the object creational POV it really shouldn't matter. One thing to keep in mind though is that the second example makes it slightly easier when debugging. My preferred version would look like this:
NSString *fileName = #"image";
NSString *tempName = [fileName stringByAppendingString:#".png"];
NSLog(#"TEST : %#", tempName);
But in the end this is just a matter of preference.
I think you're right this is really down to preferred style.
Personally I like your first example, the codes not complicated and the first version is concise and easier on the eyes. Theres too much of the 'language' hiding what it's doing in the second example.
As noted memory management doesn't seem to be an issue in the examples.