Using NSString as an Array of NSStrings? - iphone

I just came across a line of code that was written by a co-worker and I've never seen this before in Obj-C. It looks like poor form and I want to change it, but it seemingly works (the app is working as expected) and I don't even know what to change it to.
NSString **stringArray= new NSString *[numberOfStrings];
Is this OK? Would an NSArray of NSStrings make more sense?
EDIT: In order to properly free the array from memory, are any release calls needed? Currently, all I see is delete [] stringArray;.
EDIT 2: This seems to properly remove the array from memory:
for (int i=0; i < numberOfStrings; i++)
{
[stringArray[i] release];
}
delete [] stringArray;

Using an NSArray might be more legible to someone without a C background, this is just a plain old C array filled with NSString*, perfectly valid but quite different from an NSArray instance.
Both make sense, but if the users of that array are expecting a C-style array rather than an NSArray instance, there might be a lot of refactoring involved.

He is using Objective-C++ (you should be able to verify that in the 'file type' on the right panel for the file in xcode4).
the new keyword is creates a new array of (NSString *) with numberOfStrings size.
To answer your question, an NSArray would very likely be more manageable, especially if there are different developers on the code. To use a C++ array there may have been performance issues but that depends on the context of that code.

Related

UISearch bar in two NSMutables arrays

I see item UISearchBar search two arrays and very much items and not found solution, the problem its similar, have two NSMutablesArrays "subdetail" and "sublista" its show in Cell cell.textLabel and cell.detailTextLabel.
I try UISearchbar but i tray with NSPredicate and not run, try with NSRange and have more errors, i am desperate.PLEASE help me, any comments agree.
This its my code in Search:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSMutableDictionary *playas = [NSMutableDictionary dictionaryWithObjectsAndKeys:sublista, #"nombre", subdetail, #"localidad", nil];
[alldata addObject:playas];
for(NSDictionary *playas in alldata){
NSString *nombre = [playas objectForKey:#"nombre"];
NSRange nombreRange = [[nombre lowercaseString] rangeOfString:[searchText lowercaseString]];
if(nombreRange.location != NSNotFound)
[filteredList addObject:playas];
}
}
Add rest of code .m and .h
https://dl.dropboxusercontent.com/u/6217319/BuscarViewController.h
https://dl.dropboxusercontent.com/u/6217319/BuscarViewController.m
Thanks in advance.
BEST REGARDS
Your code has a lot of issues with it. I don't know which ones are actually breaking it, but any of these issues could be causing severe problems.
First, the code sample you provided doesn't provide declarations for a lot of hte objects you're referencing. No way this could possibly compile as given. If these are properties of the view controller instance, you need to use the accessor methods -- self.alldata or [self alldata], whichever you prefer.
Second, it looks like you're just adding to these properties. If you never reset their contents, every time this method is called, you're going to increase the size of your data set -- looking at your code, possibly recursively.
Third, you try to merge the two datasets together, and then try to search through only one of them. Either don't merge, or search through both seperately and then merge the results. As it is, what you're doing won't work.
Fourth, your table view should really only be displaying one type of data, so you shouldn't need to merge.
Edit:
Based on the code samples you've provided, your entire VC is going to need restructuring. Given how much of your code is written in what appears to be spanish, I'm doing a bit of guesswork at what you're actually doing.
First off, assuming you're using a modern version of xcode, get rid of the #synthesize in the .m file (it's no longer needed anymore). That will cause every single place you're using the actual ivar instead of a proper getter to turn into an error, so you can fix them quickly. iVars will by default use a prefixed underscore of their property name, so you can still access them if you have to -- but only by explicitly accessing the ivar instead of the property.
Second, you should restructure how you handle the data. I don't know where you get your data, but it looks like the various objects are fairly consistent. You should go ahead and either create a new class to hold all the data, or just put them all into a single dictionary. At that point, the 'alldata' property should, in fact, be an array of all valid data. What you should do is then have a filtered list of data (filteredData would be a good name), and you place whatever data matches the search criteria in there. Just remember to either reload the table or update it appropriately as items move into and out of the filtered list.

Sending message to objectForKey to NSMutableArray instance, And it works!! Strange?

I have a very bad code written in my program, was just playing around as I am learning Objective C and iOS platform. What I did is,
I have created NSMutableArray like this,
placeInfo = [NSMutableArray array];
and than later in my code I am doing something like this, basically I am manipulating Google places api response(JSON).
NSDictionary *results = [responseString JSONValue];
placeInfo = [results objectForKey:#"result"];
self.phoneNumber = (NSString *)[placeInfo objectForKey:#"formatted_phone_number"]; // In this line compiler warns me that NSMutableArray might not response to this.
I checked documentation but I didn't find objectForKey in NSMutableArray.
So what could be the reason? Why my code isn't crashing? Why it is returning phone number by "formatted_phone_number" key?
EDIT
After first answer I have edited my code and added type casting like this, but it still works.
NSDictionary *results = [responseString JSONValue];
placeInfo = (NSMutableArray *)[results objectForKey:#"result"];
self.phoneNumber = (NSString *)[placeInfo objectForKey:#"formatted_phone_number"];
I’ve never used the Google Places API, but I’d guess [results objectForKey:#"result"] actually returns another dictionary, so the objectForKey: works.
Because objective-c just uses pointers to refer to objects, it’s never actually being converted to an NSMutableArray. Also, objective-c doesn't know at compile time if a method will exist, due to its dynamic nature (you can actually add methods and even whole classes at runtime).
Depending on the compiler settings, it may just show a warning that objectForKey: might not be found at runtime, and let it continue compiling anyway. It ends up working just fine if you actually passed it an NSDictionary.
Even when you put the (NSMutableArray *) cast in front of it, it won’t change anything. That simply casts the pointer type, and doesn’t actually change the object in any way.
It's doing this because [results objectForKey:#"result"] is returning you something that is not an NSMutableArray. That something that's being returned is likely another NSDictionary which, of course, does respond to objectForKey: To find out what you've got, set a breakpoint after result = [placeInfo objectForKey:#"result"] and inspect result. The debugger will tell you what kind of object you're dealing with. I'll bet you anything you like that it's an NSDictionary.
Objective C allows you to send any message (called a selector) to any object at any time; the runtime does not care whether a particular object implements a given selector. If the target object does not respond to a given selector it will ignore it. It will not crash. In this respect it's utterly unlike most other OOP languages, including C++ and Java, which will fail to compile if you try to call a method that a particular class doesn't implement. You can find out if an object responds to a given selector (which is analagous to using introspection to see if a given class implements a certain method) by saying [result respondsToSelector:#selector(objectForKey:)]. This difference between methods and messages is critically important to understanding Objective C. I'd recommend reading The Objective C Programming Language before doing anything else.
Also, Objective C's type system is less stringently enforced than those other languages. It's quite legal (although a very bad idea) to do what you have done here, which is to declare a pointer of type NSMutableArray and then assign it to (I'm guessing) an NSDictionary.

Do I need to initialize an iOS empty nested array that's part of a plist import?

the code below is working, but I want to make sure it's correct. I'm nervous about having an empty Array inside my dictionary that I create from the plist, since typically it seems that if you don't, say, initWithCapacity:1 then you often get memory errors once you start trying to add items.
At least, that's been my experience with NSMutableDictionary. However, this is the first time I'm trying to implement nested data objects, so perhaps the reason this code works is that the nested array is automatically initialized when it's imported as part of its parent dictionary?
Any and all comments appreciated. Thanks.
First, here's what the plist looks like that I'm using to create my dictionary:
Next, here's my code where I'm using the plist to create a dictionary, then adding an item to dataArray
// Create a pointer to a dictionary
NSMutableDictionary *dictionary;
// Read "SomeData.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"SomeData.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
// Now let's see if we can successfully add an item to the end of this empty nested array. How 'bout the number 23
NSNumber *yetAnotherNumber = [NSNumber numberWithInt:23];
[[dictionary objectForKey:#"dataArray"] addObject:yetAnotherNumber];
// Dump the contents of the dictionary to the console
NSLog(#"%#", dictionary);
Okay, fine, simple, good. When I Log the dictionary contents it shows that "23" has been added as an array value to dataArray. So the code works. But again, I want to confirm that I'm not "getting lucky" here, with my code just happening to work even though I'm not properly initializing that nested array. If so, then I could run into unanticipated errors later on.
So to sum up, dataArray is an empty array inside the .plist, so do I need to initialize it somehow (using, for example initWithCapacity: or something else) before I can properly populate it, or is the way I'm coding here just fine?
Thanks again.
EDIT
Hey all. I've been doing continued research on this, in the interests of finding a satisfying answer. I think I may have stumbled upon something, via this link on deep copying. His previous posts on deep copying had presented some code to do essentially what I was looking for above: create a mutable copy of a dictionary or array, from a plist, that also has mutable sub-structures.
However, as mentioned in the link above, it looks like these methods were superfluous, due to the CFPropertyListCreateDeepCopy method, which can be invoked with a call such as
testData = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, [NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);
So, my question is, can I properly use CFPropertyListCreateDeepCopy, in the way shown, to achieve what I've been asking about here? In other words, can I use this method to import my dictionary from a plist with fully mutable, nested data objects?
As I mentioned in one of the comments, I know I can create a nested, mutable dictionary manually, but for complex data that's just not practical, and it seems unlikely that built-in methods to import a mutable plist don't exist. So, based on the above, it looks like I've possibly found the solution, but I'm still too new to this to be able to say for sure. Please advise.
(Side note: I would simply test the code, but as we've established, the current SDK is buggy with regard to allow you to edit immutable nested dictionaries, contrary to the documented behavior. So as before, I'm not just interested in whether this works, but whether it's correct)
Thanks in advance.
init... methods should only be called once, immediately after a call to alloc or allocWithZone:. When framework code creates and returns an object or graph of objects, their init... methods have already been called, so sending another init... message would have undefined results. Don't do that.
Interestingly, in spite of what the documentation appears to say (and admittedly I probably missed a key sentence or paragraph somewhere), when you create an instance of a mutable collection by reading a plist, any nested collections are also mutable. I ran the following little experiment in a test harness just to be sure:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSMutableArray *cats = [pets objectForKey:#"cats"];
[cats addObject:#"Foo"]; // EDIT: Added line I accidentally omitted earlier
NSLog(#"%#", cats);
So again, the nested collections created when you read in the plist are fully initialized, and mutable to boot, so you can simply use them, as you've been doing.
EDIT
However, after doing some further reading of the docs, I think the OP is right to feel uneasy about relying on what is apparently an undocumented feature of the current version of the SDK. For example, the Property List Programming Guide states:
If you load the property list with
this call:
NSMutableArray * ma = [NSMutableArray arrayWithContentsOfFile:xmlFile];
ma is a mutable array with immutable
dictionaries in each element. Each key
and each value in each dictionary are
immutable.
So, to be on the safe side, if you need a nested collection to be mutable, you should create it yourself. For example, I'd recommend rewriting the code in the example above as follows:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSArray *cats = [pets objectForKey:#"cats"];
NSMutableArray *mutableCats = [cats mutableCopy];
[pets setObject:mutableCats forKey:cats];
[mutableCats release];
You can then safely make changes to the nested mutable collection:
[mutableCats addObject:#"Foo"];
Any object in a dictionary which is created by reading from disk will be properly initialized. You will not have to do it on your own. However, as pointed out by jlehr, contents of the dictionary should be immutable. If you want the contents of the dictionary to be mutable, you will need to change them on your own. I have no idea why your program is not throwing an exception.
I do not know why you are getting memory errors while not using initWithCapacity:1 in other situations. The following code is perfectly valid:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"object1"];
[array addObject:#"object2"];
NSLog(#"%#",array);
[array release];
If you don't specify a capacity, the array won't pre-allocate any memory, but it will allocate memory as required later.
Edit:
It is perfectly acceptable to use NSDictionary with CFPropertyListCreateDeepCopy. In Core Foundation, a CFPropertyList can be a CFDictionary, CFArray, CFNumber, CFString, or CFData. Since NSDictionary is toll-free bridged to CFDictionary, you can use it wherever a CFDictionary is asked for by casting, and vice-versa. Your code as is will give a warning, but you can suppress it by casting the dictionary and return values.
NSDictionary *testData = (NSDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)[NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);

How to manage memory load of static, lazily loaded dictionaries and arrays

Generally, I use static arrays and dictionaries for containing lookup tables in my classes. However, with the number of classes creeping quickly into the hundreds, I'm hesitant to continue using this pattern. Even if these static collections are initialized lazily, I've essentially got a bounded memory leak going on as someone uses my app.
Most of these are arrays of strings so I can convert strings into NSInteger constants that can be used with switch statements, etc.
I could just recreate the array/dictionary on every call, but many of these functions are used heavily and/or in tight loops.
So I'm trying to come up with a pattern that is both performant and not persistent.
If I store the information in a plist, does the iphoneOS do anything intelligent about caching those when loaded?
Do you have another method that might be related?
EDIT - ANSWER EXAMPLE
Based on a solution proposed below, here's what I'm going to work with...
First, add a method to NSObject via category.
- (void)autoreleaseOnLowMemory;
Now, whenever I want to create lazy-loading static array or dictionary in a helper function, I can just use the following pattern...
- (id)someHelperFunction:(id)lookupKey {
static NSDictionary *someLookupDictionary = nil;
if (!someLookupDictionary) {
someLookupDictionary = [[NSDictionary dictionaryWithObjects:X, Y, Z, nil] autoreleaseOnLowMemory];
}
return [someLookupDictionary objectForKey:lookupKey];
}
Now, instead of that static dictionary living until the end of the program, if we're running out of memory it will be released, and only re-instantiated when needed again. And yes, in a large project running on an iphone, this can be important!
PS - The implementation of autoreleaseOnLowMemory is trivial. Just create a singleton class with a method that takes an object and retains it in a set. Have that singleton listen for low memory warnings, and if it gets one, release all the objects in that set. May want to add a manual release function as well.
I generally prefer plists for this just because they're easy to maintain and reuse in different sections of code. If the speed of loading them into an NSDictionary from file is a concern (and check the profiler to be sure) you can always put them into an instance variable which you can release when you get a memory warning.
If you are just doing strings, you could use C arrays.
id keys[] = { #"a" , #"b" , #"c" };
id values[] = { #"1" , #"2" , #"3" };
And if you occasionally need a true NSArray or NSDictionary from that:
[NSArray arrayWithObjects:values count:3];
[NSDictionary dictionaryWithObjects:values forKeys:keys count:3];
A plist will involve a disk hit and xml parsing for each collection. As far as I know only NSUserDefaults are cached.

There's got to be an easier way of dealing with arrays!

To optimize a bottleneck, I converted the creation of a large NSArray to a c-style array. (The resulting creation was 1/8 the time of the original NSArray version. Yeah!) But once it's created, speed is no longer an issue, so I'd rather benefit from it being an NSArray again.
However, it seems ridiculously involved to convert a c-style array to an NSArray (unless I'm missing some magic initWithArrayWrapElementsInObjects method.)
As I understand the process now, I first have to create an NSMutableArray, iterate through the c-style array converting each element (floats in my case) to objects, adding each object to the NSMutableArray, then creating the NSArray with the NSMutableArray.
Is that right? There's got to be a better way.
And help would be appreciated.
Thanks!
There's no direct way to take a blob of memory that you own and "convert" it cheaply into an NSArray-- after all, the framework would need to then own that memory, and it doesn't know where you got it from (malloc, stack, etc). If there were a convenience method for initWithArrayWrapElementsInObjects, it would itself need to do internally what you surmise: iterate over your provided memory and add items to itself (it's possibly the framework could do this as quickly as a memcpy, but who knows).
One way you could tackle this (and probably a fun learning exercise) is by actually creating your own subclass of NSArray that manages memory exactly as you want (ie, lets you create and init with whatever semantics you want), but that behaves to the outside world as an NSArray would. You can do this by inheriting from NSArray and implementing the methods count: and objectAtIndex: to operate on whatever memory you're holding on to. Obviously, you'd need to implement the management of your own memory in the init/dealloc, etc methods as well. See this page http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
under "Subclassing Notes".
The design discussion here hinges on what your data looks like. NSArray, of course, expects its items to be Obj-C references (of type id), and not just arbitrary chunks of data. If your C-style array is holding structures or some other primitive values that aren't object references, then this technique won't really work for you-- NSArray's interface will never be happy with non-reference items.
One final note: you mention taking an NSMutableArray and "creating" an NSArray with it. You should be aware that an NSMutableArray is already an NSArray, since it's a subclass. You can use an instance of NSMutableArray anywhere you'd want an NSArray, without creating some new copy of it.
UPDATE: Missed the note about your array containing floats. Yeah, you're a little bit screwed here. NSArrays want objects. If the capacity doubling was the expensive part (as another poster notes), then try initWithCapacity:. If it's the boxing/unboxing of the floats into object types, there's nothing you can do.
I have created (but don't have handy) a pair of very simple classes (called like MYArray and MYMutableArray) that are intended to wrap just this kind of data with NSArray-like methods on them. But they're not interchangeable with NSArrays. You must use them intentionally.
UPDATE #2. I know it's been ages since this question was live, but I just revisited it and realized there actually is a sort of clever way around this in this specific case. (You want a non-mutable NSArray from a C-style float array). You can create a custom subclass of NSArray that wraps the float values and only converts them to objects when they're accessed via the primitives. This may have performance pitfalls in some corners (?), but it does neatly meet your requirements:
#interface FloatProxyArray : NSArray
{
float * values;
NSUInteger count;
}
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues;
#end
.
#implementation FloatProxyArray
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues
{
if ((self = [super init])) {
values = (float *)malloc(numberOfValues * sizeof(float));
if (!values) {
[self release]; return nil;
}
memcpy(values, arrayOfFloats, numberOfValues * sizeof(float));
count = numberOfValues;
}
return self;
}
- (void)dealloc
{
free(values);
[super dealloc]
}
- (NSUInteger)count
{
return count;
}
- (id)objectAtIndex:(NSUInteger)index
{
if (index >= count) {
[NSException raise:NSRangeException format:#""];
return nil;
}
float val = values[index];
return [NSNumber numberWithFloat:val];
}
#end
(N.B. Written in the editor without compiling/testing.)
One optimization that you can do with the NSMutableArray is initWithCapacity which will prevent the doubling of your array which is the expensive operation in the addition.
Outside of that, since NSArrays and NSMutableArrays expect objects, so it's difficult to get around this.
What benefits of it being an NSArray are you looking to get?
It seems like you may be better off with a custom wrapper object around the C array that responds to whatever NSArray messages you are looking to call. Otherwise you are right back at the point of array creation... You could try manually creating a call to initWithObjects, but at the very least every float has to be wrapped in an NSNumber which would bring down your speed again.
If you really need an NSArray because something else you want to use takes NSArray objects, then you are probably better off subclassing NSArray (following the guidelines posted by Ben).
The “best” optimisation (for speed) would almost certainly be to avoid using NSArray altogether, if possible.