Objective-C ARC: how to convert (void *)YES passed as an argument? - objective-c

I have an array of views, and I need to hide them all, so I use:
BOOL shouldHideViews = YES;
[allViews makeObjectPerformSelector:#selector(setHidden:) withObject:(void *)shouldHideViews]
When I convert the code to ARC, it tells me I need some bridged cast, then I changed:
(void *)shouldHideViews
to
(__bridge BOOL)shouldHideViews
it says incompatible types casting 'int' to 'BOOL' with a __bridge cast
So how do I do it? I know I can just iterate all the views in the array, but this is not the point, I want to know in general what I should do to make this ARC compatible.
Thanks!

The other answers indicating that you can't pass YES this way are correct. There are easier solutions however:
[allViews setValue:[NSNumber numberWithBool: shouldHideViews] forKey:#"hidden"];
This works because NSArray overrides its setValue:forKey: for exactly this use.
You can also now use blocks:
[allViews enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop){ [obj setHidden:shouldHideViews];}];
Or the tried and true for() loop (see #rob mayoff's answer.)
Of them, I generally just use a for loop.

you can't pass primitive types as a void pointer. You should keep the boolean variable as an instance variable and reference it in the setHidden. That way you can just do this:
shouldHideViews = YES; //declare BOOL shouldHideViews; in your .h file
[allViews makeObjectPerformSelector:#selector(setHidden) withObject:nil];

These will be rather helpful to you
How to use performSelector:withObject:afterDelay: with primitive types in Cocoa?.
Using performSelector:withObject:afterDelay: with non-object parameters
SEL performSelector and arguments
Either you should make a wrapper using NSNumber or use NSInvocation.

The object argument to makeObjectsPerformSelector:withObject: is of type id. That means it needs to point to an Objective-C object. Casting a non-object to an id is a bad idea, because the system (particularly under ARC) is allowed to do things like send the retain and release messages to an id, and that will crash if you cast YES to id.
I suggest just using fast enumeration:
for (UIView *view in allViews) {
view.hidden = shouldHideViews;
}

Related

Objective-C: Is there any trick to avoid casting of NSArray objects?

Very often I find myself casting objects in NSArray to my types, when I want to access their specific properties with dot notation (instead of getter) without creating an extra variable.
Is there any cool feature or trick to tell objective-c which one class of objects I'm going to store to NSArray, so that compiler will assume objects in an array to be my type, not an id?
If you mean you're doing things like:
x = ((MyClass *)[myArray objectAtIndex:2]).property1;
You can just split it into two lines to be easier to read:
MyClass *myObject = [myArray objectAtIndex:2]
x = myObject.property1;
If you're really set on the first case, you could make a category on NSArray that has an accessor for your type:
#implementation NSArray (MyCategory)
- (MyClass *)myClassObjectAtIndex:(NSUInteger)index
{
return [self objectAtIndex:index];
}
#end
And then you can use it like you want:
x = [myArray myClassObjectAtIndex:2].property1;
Don't use properties in this situation. You can't say
arr[ix].myProperty
But you can always say
[arr[ix] myProperty]
Strictly answering to your question, no.
There's no language support for indicating the parametric type of a collection, i.e. something like NSArray<MyClass>.
That said, you can find workarounds for avoiding an explicit cast.
Since the returned object is of type id you can invoke any - existing - method on it and the compiler won't raise an eyebrow, unless you're using dot-syntax notation, which has stricter compiler checks.
So for instance
NSString * name = [people[0] firstName];
works flawlessly without a cast, whereas
NSString * name = people[0].firstName;
doesn't.

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.

local variable, optimisation and ARC

Given NSArray *tagsArray and NSMutableDictionary *cache not empty.
This:
for (Tag *tag in tagsArray) {
NSString *name = tag.name;
[cache setObject:tag forKey:name];
}
should not be slower than this:
for (Tag *tag in tagsArray) {
[cache setObject:tag forKey:tag.name];
}
?
the __strong var 'name' would not use an implicit retain/release by ARC ? The compiler will actually generate the second from the first ?
Yes, it IS the same for ARC, because there's no other code that interacts with "name" variable in first example.
For future, try to understand that ARC modifies your code for better performance and optimization, and not vice versa.
Here's the link with entire documentation for ARC - must know - http://clang.llvm.org/docs/AutomaticReferenceCounting.html
If ChildClass objecA, objecB.... has an instance variable, and ParentClass tries to assign the ChildClass's instance variable (which is an instance variable of ParentClass and strong pointer),
then ChildClass' object acts like the same object. Although it is not the same. Definitely not same.

Correct way of setting a BOOL property

I have a BOOL property that I want to set in my class initializer.
#property (assign, nonatomic) BOOL isEditMode;
- (id)init
{
. . .
[self setValue:NO forKey:isEditMode];
return self;
}
The compiler gives me an "Incompatible integer to pointer conversion" warning. What am i doing wrong here?
The Key-Value Coding method setValue:forKey: only accepts objects as arguments. To set a BOOL, you need to wrap the number in a value object with [NSNumber numberWithBool:NO]. But there's little reason to do that. Key-Value Coding is a roundabout way to accomplish this. Either do self.isEditMode = NO or just isEditMode = NO. The latter is preferable in an init method (because setters can run arbitrary code that might not be desirable before an object is fully set up).
But to elaborate on the first point: The reason Key-Value Coding works this way is because the type system can't represent an argument that's sometimes an object and at other times a primitive value. So KVC always deals with objects and just autoboxes primitive values as necessary. Similarly, if you do [yourObject valueForKey:#"isEditMode"], you'll get back an NSNumber object wrapping the real value.
The correct syntax to set a property is just
self.isEditMode = NO;
If you want to use -setValue:forKey: you'd have to write it as
[self setValue:[NSNumber numberWithBOOL:NO] forKey:#"isEditMode"];
However, there's absolutely no reason to do this in your situation.
That said, since you're in an init method, I would strongly recommend avoiding any property access whatsoever and instead using the ivar directly, as in
isEditMode = NO;
This avoids the possibility of an overridden setter being called (either in this class or a subclass) that makes the assumption that the object has already completed initialization. For this same reason you also want to avoid property access inside of -dealloc.
You can just assign the value directly:
isEditMode = NO;
I think you mean:
self.isEditMode = NO;
If your code does indeed compile (I'm pretty new to Objective-C so I don't know) setValue probably takes a pointer to a string (#"isEditMode", e.g.) and not some other type (isEditMode, e.g.).

Change the values within NSArray by dereferencing?

I've come across a problem related to pointers within arrays in objective-c.
What I'm trying to do is take the pointers within an NSArray, pass them to a method, and then assign the returned value back to the original pointer(the pointer which belongs to the array).
Based on what I know from C and C++, by dereferencing the pointers within the array, I should be able to change the values they point to... Here is the code I'm using, but it is not working (the value phone points to never changes based on the NSLog output).
NSArray *phoneNumbers = [phoneEmailDict objectForKey:#"phone"];
for (NSString* phone in phoneNumbers) {
(*phone) = (*[self removeNonNumbers:phone]);
NSLog(#"phone:%#", phone);
}
And here is the method signature I am passing the NSString* to:
- (NSString*) removeNonNumbers: (NSString*) string;
As you can see, I am iterating through each NSString* within phoneNumbers with the variable phone. I pass the phone to removeNonNumbers:, which returns the modified NSString*. I Then dereference the pointer returned from removeNonNumber and assign the value to phone.
As you can tell, I probably do not understand Objective-C objects that well. I'm pretty sure this would work in C++ or C, but I can't see why it doesn't work here! Thanks in advance for your help!
Yeah, that's not going to work. You'll need an NSMutableArray:
NSMutableArray * phoneNumbers = [[phoneEmailDict objectForKey:#"phone"] mutableCopy];
for (NSUInteger i = 0; i < [phoneNumber count]; ++i) {
NSString * phone = [phoneNumbers objectAtIndex:i];
phone = [self removeNonNumbers:phone];
[phoneNumbers replaceObjectAtIndex:i withObject:phone];
}
[phoneEmailDict setObject:phoneNumbers forKey:#"phone"];
[phoneNumbers release];
You can't dereference Objective-C object variables. They are always pointers, but you should treat them as though they're atomic values. You need to mutate the array itself to contain the new objects you're generating.
NSArray is not a C/C++ style array. It's an Objective-C object. You need to use the instance methods of the NSArray class to perform operations on it.
In Objective-C you never "dereference" an object pointer to set its value.
Also, you're using what is called Fast Enumeration, which does not allow mutation.
You can also use enumerateObjectsUsingBlock:.
NSArray *array = [NSArray array];
__block NSMutableArray *mutableCopyArray = [array mutableCopy];
[mutableCopyArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
[mutableCopyArray replaceObjectAtIndex:idx withObject:[object modifiedObject]];
}];
Checkout How do I iterate over an NSArray?
While this may work to some degree, I haven't tested it, I'd file this under 'bad idea' and not touch. NSArray, and many other cocoa objects, a fairly complex and can have a variety of implementations under the hood as part of the class cluster design pattern.
So when it comes down to it you really won't know what you're dealing internally. NSArray is actually designed to be immutable so in place editing is even doubly a bad idea.
Objects that are designed to let you mess around with the internals expose those through api methods like NSMutableData's mutableBytes.
You're better off constructing a new NS(Mutable)Array with the processed values.