Assigning unarchived object to a different object works, why/how? - objective-c

Assigning an archived dictionary object but unarchived and assigned to an array does not seem to produce an exception and the logged out data from the array is that of the dictionary and not in array format at all if it was possible to assign to a different return type, kinda link casting but instead your unarchiving and assigning directly. Why does this work? if anyone can explain it much more deeply it would bring to light a better understanding of the unarchiving process and the way it works.
NSDictionary *glossary = #{
#"Abstract class": #"some class",
#"Adopt": #"borrow",
#"archiving": #"Storing an object for later use"
};
if([NSKeyedArchiver archiveRootObject:glossary toFile:#"propertyList2.plist"] == YES)
NSLog(#"Archive success");
else
NSLog(#"Archive unsuccessfull");
NSArray *unArchiving = [NSKeyedUnarchiver unarchiveObjectWithFile:#"propertyList2.plist"];
NSLog(#"%#", unArchiving);
//for (NSString *obj in unArchiving) {
// NSLog(#"%#: %#", obj,unArchiving[obj]);
// }

Welcome to Objective-C! Due to the dynamism of the language, the type of an object reference (AKA variable) really only matters at compile time. At runtime, what the object is matters; what you thought it aught to be when you wrote the code doesn't.
The reason your code for unarchiving works is because the for (X x in Y) syntax is identical for arrays and dictionaries. Similarly, the indexing syntax (a[x]) is identical. All of that is handled at runtime, based on the type of the collection being iterated. As long as the semantics are the same, your code will run without error. Obviously, the moment you try to treat that dictionary as an array (e.g. refer to .lastObject) it will blow up.

Related

NSFastEnumeration object casting in ARC

I'm trying to implement the countByEnumeratingWithState:objects:count: method from the NSFastEnumeration protocol on a custom class.
So far I have it iterating through my objects correctly, but the objects that are returned aren't Objective-C objects but rather the core foundation equivalents.
Here's the part of the code that sets the state->itemsPtr:
MyCustomCollection.m
- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *)state
objects: (id __unsafe_unretained *)buffer
count: (NSUInteger)bufferSize {
// ... skip details ...
NSLog(#"Object inside method: %#", someObject);
state->itemsPtr = (__unsafe_unretained id *)(__bridge void *)someObject;
// ... skip details ...
}
Then I call the 'for..in' loop somewhere else on like this
SomeOtherClass.m
MyCustomCollection *myCustomCollection = [MyCustomCollection new];
[myCustomCollection addObject:#"foo"];
for (id object in myCustomCollection) {
NSLog(#"Object in loop: %#", object);
}
The console output is:
Object inside method: foo
Object in loop: __NSCFConstantString
As you can see, inside the NSFastEnumeration protocol method the object prints fine, but as soon as it gets cast to id __unsafe_unretained * I lose the original Objective-C corresponding class.
To be honest I'm not quite sure how the (__unsafe_unretained id *)(__bridge void *) casting works in this case. The (__unsafe_unretained id *) seems to cast to match the right type itemsPtr needs. The (__bridge void *) seems to cast to a pointer of type void with __bridge used to bridge the obj-c world to the CF world. As per the llvm docs, for __bridge:
There is no transfer of ownership, and ARC inserts no retain operations
Is that correct?
From my understanding __NSCFConstantString is just the core foundation equivalent of NSString. I also understand that with ARC you need to bridge from Objective-C objects to CoreFoundation equivalents because ARC doesn't know how to manage the memory of the latter.
How can I get this working so that the objects in my 'for..in' loop are of the original type?
Also note that in this case I'm adding NSStrings to my collection but in theory it should support any object.
UPDATE
Rob's answer is on the right track, but to test that theory I changed the for loop to this:
for (id object in myCustomCollection) {
NSString *stringObject = (NSString *)object;
NSLog(#"String %# length: %d", stringObject, [stringObject length]);
}
In theory that should work since the objects are equivalent but it crashes with this error:
+[__NSCFConstantString length]: unrecognized selector sent to class
It almost looks like the objects returned in the for loop are classes and not instances. Something else might be wrong here... Any thoughts on this?
UPDATE 2 : SOLUTION
It's as simple as this: (thanks to CodaFi
state->itemsPtr = &someObject;
You're incorrectly casting someObject. What you meant is:
state->itemsPtr = (__unsafe_unretained id *)(__bridge void *)&someObject;
(Let's get rid of those awful casts as well)
state->itemsPtr = &someObject;
Without the address-of, your variable is shoved into the first pointer, which is dereferenced in the loop. When it's dereferenced (basically, *id), you get the underlying objc_object's isa class pointer rather than an object. That's why the debugger prints the string's value inside the enumerator call, and the class of the object inside the loop, and why sending a message to the resulting pointer throws an exception.
Your code is fine the way it is. Your debug output is revealing an implementation detail.
NSString is toll-free-bridged with CFString. This means that you can treat any NSString as a CFString, or vice versa, simply by casting the pointer to the other type.
In fact, under the hood, compile-time constant strings are instances of the type __NSCFConstantString, which is what you're seeing.
If you put #"hello" in your source code, the compiler treats it as a NSString * and compiles it into an instance of __NSCFConstantString.
If you put CFSTR("hello") in your source code, the compiler treats it as a CFStringRef and compiles it into an instance of __NSCFConstantString.
At run-time, there is no difference between these objects in memory, even though you used different syntax to create them in your source code.

Understanding Objective-C method value passing

Lets say I have in viewDidLoad:
NSMutableArray *entries = [NSMutableArray array];
[self doSomethingWithArray:entries];
NSLog(#"%#", entries);
Then in method I have:
- (void)doSomethingWithArray:(NSMutableArray *)entries
{
// create some custom data here, lets say - Something *something...
[entries addObject:something];
}
How is it possible that entries (one at the top) now (after method is finished) contain object something, since object "something" is not added to property or instance variable, and nslog will log class "Something" ? And doSomethingWithArray doesn't return anything since its "void".
I have encountered this for first time and dunno if there is any name of this appearance ?
I have seen this for second time in some examples and really dunno how its done.
If anyone could explain this a bit whats happening here I would be very very grateful.
Thank you a lot.
Because Objective-C instances are passed by reference (as you can tell by the * pointer syntax). You basically pass the address of the array to the doSomethingWithArray: method. In that method you add something to the array referenced by that address. And of course once the method returns, your array will contain that new object.
When you are adding the something object to the array, the array always retains it i.e it maintains copy of the Something object.
So NSLog prints the something.
Hope that helps.

Is this if needed?

This method is generated by Xcode 3.2 using "Accessor defs to clipboard"
- (void)setBodyMass:(int)newBodyMass {
if (bodyMass != newBodyMass) {
bodyMass = newBodyMass;
}
}
Could I just as easily write this as you see below? It seems to be doing a conditional test to save it doing a possible redundant assignment.
- (void)setBodyMass:(int)newBodyMass {
bodyMass = newBodyMass;
}
cheers -gary-
Normally you do a check like that in a mutator method because you're working with objects that have to be released. Say you have a mutator method without that check:
- (void)setObject:(MyObject *)anObj
{
[obj release];
obj = [anObj retain];
}
Imagine (for some reason) you have a chunk of code like this that uses that method:
MyObject *o = [MyObject object]; // Auto-released
[anotherObject setObject:o];
[anotherObject setObject:o];
On Line 1, you can assume o has a retain count of 0 (since it's autoreleased). On Line 2, o has been passed to setObject:, which retains it and stores it in the instance variable obj. Since we're working with pointers, o and obj point to the same object in memory, which now has a retain count of 1.
On Line 3, you pass the same object to setObject: again. But right away in that method, you release anObj, which is the same object that both o and obj point to! This means that o, obj, and anObj have a retain count of 0. When you set obj to [obj retain], you're making obj point to an object that has been released already.
This is obviously bad, so when working with mutator methods that deal with objects, you should always use that guard, which effectively checks to see if obj and anObj point to the same object in memory; if they do, nothing happens.
However, this guard isn't necessary in your example, because you're passing an int -- not a pointer -- and ints, of course, never get released (since they're not objects).
I'd do it your way; assigning an int is very cheap. The check makes sense if the assignment is to some large data structure or might have unintended side effects, neither of which is true for int.
Does the assignment cause something to trigger (event)? Doesn't seem so. You can compare but for a simple int I do not think it's an obligation to verify if the value is the same or not. Of course, if you want to display something to the user concerning that he has entering the same value, you might check the value, otherwise, I would not check it.

NSMutableArray from filterUsingPredicate error

I am trying to return a subset of my NSMutableArray (MessageArray) with the following code. MessageArray contains an NSDictionary, one of the keys being FriendStatus. I get a strange error which I know is a DUH syntax issue. "error. void value not ignored as it ought to be".
-(NSMutableArray*)FriendMessageArray {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"FriendStatus == 1"];
NSMutableArray *filtered = [MessageArray filterUsingPredicate:predicate];
return filtered;
}
"void value not ignored as it ought to be" means that a method with a signature that starts with (void) is being used to assign a value or object to a variable. What's the signature for filterUsingPredicate? does it start with (void) ?
I'm assuming "MessageArray" is an instance variable (never name instance variables this way; you should have a property called -messages and access it with self.messages). I'l further assume that it is an NSMutableArray or else you'd be getting warnings from the compiler.
NSMutableArray -filterUsingPredicate: modifies the array itself, returning void. The method you want is -filteredArrayUsingPredicate: which returns an array. The fact that the former is a verb and the latter is a noun indicates this fact even without reading the docs. Cocoa naming is extremely consistent, which is why I mention it in the first paragraph. Pay attention to the names and you will have far fewer bugs.

comparing arrays in objective-c

Ok a pretty simple question.. in c++ it seems to work but in objective-c i seem to struggle with it :S ..
If you want to compare two arrays it should be something like this right
for ( int i = 0; i < [appdelegate.nicearray count]; i++ )
{
if ( appdelegate.nicearray[i] == appdelegate.exercarray[i] )
{
NSLog(#"the same elements in this selection");
}
}
what's the problem exactly ?
These are Cocoa array objects (instances of NSArray), not C arrays or C++ vectors, and remember that Objective-C does not have operator overloading. The only things you can do with an object are pass it around, store it in variables, and send messages to it.
So the array-subscript operator is wrong with Objective-C objects. I don't think it's even linguistically valid to dereference a pointer to an Objective-C object, so this code should be giving you a compiler error. I may be misremembering, though. If it does make it to runtime, that code will crash sooner or later, since you're accessing memory beyond the ends of the array objects.
(EDIT from the year 2013: Objective-C now supports subscripting of objects. This ultimately translates into the appropriate objectAtIndex: or replaceObjectAtIndex:withObject: message. So, the code in the question would actually work now, although it's still not the proper way to simply walk an array, much less to compare two arrays.)
The proper way to retrieve an object from an NSArray object by its index is not to use the array-subscript operator, but to send the array object the objectAtIndex: message:
[myArray objectAtIndex:i]
The proper way to iterate on the elements of an array object, assuming you don't really need the index for something else (such as replacing objects in a mutable array), is to loop on it directly (this is called “fast enumeration”):
for (MyObject *myObject in myArray) {
…
}
NSArray also responds to objectEnumerator and reverseObjectEnumerator, which return a similarly-iterable object. Of the two, reverseObjectEnumerator is the more useful in new code, since you can just iterate on the array directly to iterate forward. Both of them were most useful before fast enumeration existed; that code looked like this:
NSEnumerator *myArrayEnum = [myArray objectEnumerator];
MyObject *myObject;
while ((myObject = [myArrayEnum nextObject])) {
…
}
(Yes, that's an assignment in the condition. Deliberately, hence the extra (). We coded boldly back then, didn't we?)
For what you're doing, though, you more likely want to send one of the arrays an isEqualToArray: message, as Williham Totland suggested:
BOOL theyAreEqual = [myFirstArray isEqualToArray:mySecondArray];
This will make sure both arrays have the same length, then walk them both in lock-step, sending isEqual: to each pair of objects. It'll return YES if every isEqual: message returned YES; NO otherwise. The arrays may contain different objects, but as long as each pair is equal, the arrays themselves are equal.
That assumes you want object equality. Two separate objects are equal if one of them responds with YES when you send it an isEqual: message and pass the other object. If you meant to compare the identities of the objects, then you do need to do the lock-step loop yourself and use ==:
BOOL arraysContainTheSameObjects = YES;
NSEnumerator *otherEnum = [otherArray objectEnumerator];
for (MyObject *myObject in myArray) {
if (myObject != [otherEnum nextObject]) {
//We have found a pair of two different objects.
arraysContainTheSameObjects = NO;
break;
}
}
But that's unlikely. Most of the time, I have wanted to test the objects' equality, not identities, so isEqualToArray: is what I wanted.
You want the isEqualToArray: method. As in:
if ([arrayOne isEqualToArray:arrayTwo]) {
// Do something
}
This will recursively compare the two arrays, while having the advantage of not being needlessly circuitous and not requiring a loop.
Try telling us the result you're getting when you run this code. The approach is correct, but try this one:
for (int i =0; i< appdelegate.nicearray.count; i++)
{
if ([[appdelegate objectAtIndex:i] isEqual: [appdelegate.exercarray objectAtIndex:i]])
{
NSLog(#"the same");
}
}
Here's a little one I put together based on the top ranked example. This merely checks that the arrays contains the same values, irrespective of order and if there are any duplicates. I mainly use this to compare keys of two dictionaries (which often return their allKeys arrays in various sort orders) to see if they contain the same objects. Thanks Peter Hosley for providing the example I adapted this from.
#pragma mark - Arrays
// Check to see if arrays contain the same elements, not necessarily in the same order
// This is different from [array isEqualToArray:responseKeys] which demands the same order in both arrays
// ## Does not compensate for duplicate entries in an array
+ (BOOL)doArraysContainTheSameObjects:(NSArray *)firstArray withArray:(NSArray *)secondArray {
BOOL arraysContainTheSameObjects = YES;
for (id myObject in firstArray) {
if (![secondArray containsObject:myObject]) {
// We have found an object that is not in the other array.
arraysContainTheSameObjects = NO;
break;
}
}
return arraysContainTheSameObjects;
}
I do the following when comparing arrays:
Check to see if any of the arrays are nil when the other is not
Check to see if the lengths are the same
Iterate (using a for loop like you have) over each element checking the matching element in the other array.
To compare elements you need to define what you want to regard as being "equal". Are they equal only if the pointers in the array are equal or can they be equal if the content is equal too.
For the pointer case, you can use ==.
For the deep comparison you might need to use CompareTo or something similar.