I am trying to compare objects in Objective-C and was just wandering how, for example, two objects (which are instances of UIView) are compared that hold two NSStrings like so:
#import <Foundation/Foundation.h>
#interface Notebook : UIView
{
NSString *nameOfBook;
NSString *colourOfBook;
}
#property (nonatomic, retain) NSString *nameOfBook;
#property (nonatomic, retain) NSString *colourOfBook;
Let's assume that I have two NSMutableArrays which hold several objects of the type Notebook. One is called reality and the other theory. Reality holds two notebooks with the nameOfBook #"Lectures" and #"Recipies", but colourOfBook are all empty. Theory holds three notebooks with the nameOfBook #"Lectures", #"Recipies", #"Zoo", and colourOfBook #"red", #"yellow", #"green".
I would like to compare the two arrays and adjust theory according to reality. In this case, it would mean to remove #"Zoo". I can't simply replace theory with reality as I would loose all the colours stored in theory.
This is the code I've come up with:
for (int i=0; i < [theory count]; i++) {
Notebook *testedNotebook = [Notebook alloc];
testedNotebook = [theory objectAtIndex:i];
if ([reality indexOfObject:testedNotebook] == NSNotFound)
{
NSLog(#"Book is not found in reality - remove it from theory...");
[theory removeObject:testedNotebook];
}
[testedNotebook release];
}
Now, my big question is how the objects will be compared. Ideally, I'd like to compare ONLY their NAMES regardless of the COLOUR. But I guess this is not how my code works right now. The entire objects are compared and the object in reality which holds #"Lectures" and #"" (no colour) cannot be the same as the object in theory which holds #"Lectures" and #"red".
How could I achieve to compare objects according to one of their attributes only (in this case the name)?
If you read the documentation for indexOfObject:, you'd find that NSArray calls isEqual: for each object in the array. So, override isEqual for Notebook and implement your own comparison routine.
Apropos of nothing, why are you allocating without initializing an instance of Notebook, overwriting it with an autoreleased instance, and subsequently releasing that? (Never mind you might be releasing it in your loop first!) You're destined for a crash. And why are you removing objects from an array while you're iterating through it?
You want to implement methods like -isEqual:. Please have a look here.
Related
I have a custom object (Artist) which is an NSManagedObject subclass (it's one of the entities in my Core Data database).
The following 2 properties are declared on an Artist object:
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSSet *artworks;
When comparing two artist objects ('selectedArtist' and 'artist'), the following appears to perform a deep equality comparison even though it's only comparing pointers. It returns true when the two Artist objects have the same 'name' and 'artworks' even though they are at different memory addresses:
if (selectedArtist == artist) {
NSLog(#"found equal artists");
NSLog(#"Selected artist named: %# located at: %p", selectedArtist.name, &selectedArtist);
NSLog(#"Other artist named: %# located at: %p", artist.name, &artist);
}
Output for 2 Artist objects with same 'name' and 'artworks':
"found equal artists
Selected artist named: artist1 located at: 0x175a80e8
Other artist named: artist1 located at: 0x1716bc"
Does anyone have any idea what's going on here? I can provide more info if this isn't sufficient.
You are logging something completely meaningless and are confused by it.
You have two variables, selectedArtist and artist, both pointing to the same object. That's what "==" compares. But you are logging the addresses of the variables. Of course you get different results, because there are two different variables.
int x = 5;
int y = 5;
if (x == y) {
NSLog (#"%p %p", &x, &y); // Not tested
}
Do you expect &x and &y to be the same? Of course not. You just got yourself totally confused and are jumping to the wrong conclusion. You have two variables pointing to the same object, so the variables compare equal, and of course name and artwork will compare equal, because they are the name and artwork are the same.
I don't even know what you are asking. The thing is you can't just compare objects using "==", it doesn't work that way. Apple has designated comparators for this stuff. You really should post what your custom NSManagedObject looks like but even without that, and since these are NSSets, the following should work:
if ([[selectedArtist artworks] isEqualToSet:[artist artworks]]) {
//do work
}
you could also convert the NSSet to an Array and compare arrays, you can also compare the NSString "name" assuming this code will correspond to your managed object.
if ([[selectedArtist name] isEqualToString:[artist name]]) {
//do work
}
Here's the info you wanted based on the comments below. First off, "==" compares pointers, which I guess is weird because you show the pointers being the same. It looks like NSManagedObjects can be compared with "==" just fine, it's only certain NSObjects that can't be compared that way, very interesting.
Read here:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/index.html#//apple_ref/doc/uid/TP30001171-223939
Read here:
http://nshipster.com/equality/
I'm learning bindings in cocoa. My ArrayController is bound to a tableview and a searchfield. data display and filtering is fine.
These properties are in the mutableArray.
#property (nonatomic, strong) NSString *displayName;
#property NSInteger entityID;
#property (nonatomic, strong) NSString *MemberStatus;
In the table, I'm displaying the 'displayName'. My main problem is, I need to select a row and run an webservice using 'entityID' of that selected member.
If I do not filter and select a row, I'm able to fetch the needed 'entityID' from the mutable array using selected row index.
[[[self.participantArray objectAtIndex:selectedRow] valueForKey:#"entityID"] integerValue]
Whenever I'm filtering, depending on the selected row index it is returning wrong values naturally. How do I get the correct fields? Please help. If my question is not clear, please mention what you need.
thanks.
The table view is displaying the arrangedObjects of the array controller. So, you should index into that, too.
You will need an outlet to the array controller so that you can do something like:
[[[self.particpantsController.arrangedObjects objectAtIndex:selectedRow] valueForKey:#"entityID"] integerValue]
(I'm actually not sure why you're using -valueForKey: here. The expression [self.particpantsController.arrangedObjects objectAtIndex:selectedRow] gives you the object. If it has an entityID property, you can just call the getter on that object. If it's an NSDictionary, then you should prefer -objectForKey: over -valueForKey:. Well, actually, you should prefer creating a real model object class and using that rather than NSDictionary. Making dictionaries tends to work well in toy apps and prototyping, but quickly shows its limits.)
My apologies if this has already been asked and answered. I am new to Objective-C and I am trying to create a project that contains an object with NSMutableString and I am creating an NSMutableArray of those objects.
Each of the strings in the object are declared as follows:
#property (assign) NSMutableString* propname;
In the initialization routine (initStringObject) for the object I am setting each of the strings as follows:
self.propname = [NSMutableString stringWithCapacity:16];
[self.propname setString:#"Name"];
There are a number of properties with multiple NSMutableStrings, a couple of NSInteger and a float. All NSMutableStrings are allocated with different capacities.
For the NSMutableArray, I am declaring that in the interface section of the view controller .m file as follows:
#property (strong) NSMutableArray *objectarray;
In the loadview, routine I am initializing the array as follows:
self.objectarray = [NSMutableArray array];
[self.objectarray addObject:[[StringObject alloc] initStringObject];
The project builds fine but when I go to populate the view with the information in the object, the strings are corrupted. The integer and float values in the object are correct. I figure I must be losing the pointer to the correct location in memory but I cannot figure out what is going on. Eventually, if I keep running the program, I get an EXC_BAD_ACCESS error message.
This is an OS X application.
Can anyone see what I am doing wrong?
Thank you in advance.
I suspect it's the assign attribute in #property (assign). Use strong (the default) instead to have ARC properly manage the string objects.
On some intuitive (perhaps wrong) idea of performance, I always get a copy of a mutable instance before I store it. So if a property expects an NSArray I take the mutable array I'm working with and store it as self.array = mutableArray.copy (though the property is specified as strong or retain).
This seems silly to me, suddenly, but is it? Do mutable instances -- doing the exact same task -- perform the same?
Note: The mutable instance falls out of scope and (thanks to ARC) gets released right after this, so there's no worry that it'll be mutated once it's assigned to the property.
NSArray and NSMutableArray are both (as far as I'm aware) implemented on top of CFArray, which simply has a flag specifying whether it's mutable. CFArray functions which require a mutable array have an assertion right at the beginning, checking that flag:
void CFArraySetValueAtIndex(CFMutableArrayRef array, CFIndex idx, const void *value) {
// snip...
CFAssert1(__CFArrayGetType(array) != __kCFArrayImmutable, __kCFLogAssertion, "%s(): array is immutable", __PRETTY_FUNCTION__);
Mutable and immutable CFArrays are identical other than passing or failing this assertion, and so should NSArrays and NSMutableArrays be, performance- or other-wise.
Partly answered here: NSArray size and mutability
NSMutableArray is not noticeably slower or larger (memory-wise) than an NSArray. It's basically just an NSArray that reallocates itself when it gets full as as bigger array, and keeps doing that as you add items to it.
The reason for copying mutable arrays as immutable ones when assigning them to values in your class is so you can guarantee that their values don't change. If you store a mutable array in your class, other code can change its values outside of your class without calling any of your methods. That leaves you vulnerable to crashes due to internal inconstancy errors within your classes.
For example, supposing that when the array was set, you cached the length of the array as an int property in your class. That would be fine if the array was immutable, but if it was mutable, someone else could change the array, and your cached value would now be wrong, but you have no way of knowing that.
However, it's not necessary to do the copying manually. If you declare your array properties as:
#property (nonatomic, copy) NSArray *foo;
Then whenever you assign an array to object.foo, it will automatically be copied. You don't need to copy it again yourself. It's best practice to use a property type of copy instead of strong/retain for any type that has a mutable variant, like so:
#property (nonatomic, copy) NSArray *foo;
#property (nonatomic, copy) NSString *foo;
#property (nonatomic, copy) NSDictionary *foo;
#property (nonatomic, copy) NSData *foo;
etc...
However be careful not to use it for mutable properties, or it will make an immutable copy stored in a property that thinks it's mutable and cause a crash if you try to mutate it. The synthesised copy property isn't smart enough to use mutableCopy automatically.
#property (nonatomic, copy) NSMutableArray *foo; //don't do this
For clarity you're asking if, given an NSArray and an NSMutableArray both subjected to a battery of non-mutating test methods, does the NSArray perform noticeably faster? I specify non-mutataing, because it looks like you're copying a mutable array to an immutable array with the belief that the immutable array will perform its non-mutating methods faster than the mutable array. Anyways, the answer is no. (But don't take my word for it; profile).
Even if NSMutableArray overrode some non-mutating methods (which we can't know about, one way or another), you wouldn't need to worry about it. Adding a couple CPU cycles is trivial compared to the overall computational complexity of the operation. As long as NSMutableArray doesn't manage to turn a O(n) lookup-operation into a O(n2) operation, you'll be fine 99% of the time. (Those complexities are just fictitious examples).
While there are perfectly valid reasons why you might want to copy a mutable array into an immutable array (as pointed out by #NickLockwood), performance shouldn't be one of them. Premature optimization is very bad, after all.
With XCode 3 compiler, I could manage an array of objects like:
#interface myView:UIView
{
CALayer *layer[4];
}
#property (nonatomic,retain) CALayer **layer;
#end
#implementation myView
#dynamic layer;
- (CALayer **)layer { return layer; }
// I could then access elements like
- (void) example
{
self.layer[3] = NULL;
}
#end
With XCode 4 compiler the #property declaration generates an error "Property with retain must be an object type".
I guess best way to fix is to convert to NSArray, but I have 100's lines of code using the c-style array subscript (e.g., self.layer[i]). Is there some other way to fix?
Several problems with this code:
It should be MyView, not myView; classes start with capital letters.
CALayer ** is not an object type; it is a pointer to an object type, hence the compiler complaint. Simply making it assign will make it compile, but it'll still be wrong.
There is likely no reason to use a language array (MyClass foo[4]) to hold this data. Use an NSMutableArray (you can use [NSNull null] as a stand-in for "this slot is not populated".
If you really want to stick with the language array, drop the retain. Just remember that you have to explicitly manage the retain/releases of the objects within the array. The #property won't do that for you.
Also, while it may seem a pain to fix your code to be inline with typical standard patterns, it is only going to be more costly to do so as the code evolves and, someday, you'll likely be in a situation where you really need to do so....
Change it to an assign property so you don't try to retain a non-object?
You cannot use Objective-C memory management calls (i.e. retain) on a C array. You need to manage your array using standard C or C++ logic. You need to malloc and free memory on your own. If you do not need to retain the array then you can remove the retain property.