id pointer in Objective-C - objective-c

I am new to Objective-C. I am coming from a Java background. I am trying to create a generic function in objective-c as follows:
- (id<BaseEntity>*)retreive:(NSString *)idValue unpackedWith:(id<ITransferObject>*) transferObject{
NSString * url = [[[self baseUurl] stringByAppendingString: [self getPath]]stringByAppendingString: #"/retreive/"];
url = [url stringByAppendingString:idValue];
NSMutableURLRequest *request = [self createGET:url];
NSString *responseString = [self processRequest:request];
BaseEntity* responseEntity = [transferObject unpack:responseString];
return responseEntity;
}
What I am trying to accomplish is create a function that can take a pointer to any object that implements the ITransferObject protocol. Use a function on this object as defined in the ITransferObject protocol. Then return a pointer to an object that implements the BaseEntity protocol. I feel like I took a Java generics approach to this and it is not working.
I get the following error:
Bad receiver type `__autoreleasing id<ITransferObject> *'
from this line:
BaseEntity* responseEntity = [transferObject unpack:responseString];
And I get the error:
Implicit conversion of an Objective-C pointer to `__autoreleasing id<BaseEntity> *' is disallowed with ARC
from this line:
return responseEntity;
Any ideas?

id is a pointer type in itself—you don’t need to append a * to it to represent a pointer as you do with a class.
- (id<BaseEntity>)retrieve:(NSString *)idValue unpackedWith:(id<ITransferObject>)transferObject

id is a special type - it is a by-reference type that does not require an asterisk: it is already a pointer. Here is an example that illustrates this point:
-(void)someMethod:(id)data; // id does not need an asterisk
...
NSString *str = #"Hello"; // NSString needs an asterisk
[self someMethod:str]; // This works
Adding an asterisk after an id makes the type a pointer to an id - i.e. a double pointer. There are legitimate situations when you need a pointer to an id, but it is not what you need in your situation.
You have two ways of fixing this issue:
You can change id for NSObject (similar to passing Object in Java), or
Remove the asterisk after the id.
The second approach is more common:
- (id<BaseEntity>)retreive:(NSString *)idValue unpackedWith:(id<ITransferObject>) transferObject;

First of all, it looks like BaseEntity is a class, not a protocol, so you can't use it as a protocol.
Second, the id type is inherently a pointer, so you normally don't want to declare a pointer-to-id.
Declare your method like this instead:
- (BaseEntity *)retreive:(NSString *)idValue unpackedWith:(id<ITransferObject>) transferObject{
(Note that I have changed the transferObject type by removing the *.)
Alternatively, use NSObject instead of id and leave the star, this:
- (BaseEntity*)retreive:(NSString *)idValue unpackedWith:(NSObject<ITransferObject>*) transferObject{
Also, the correct spelling is retrieve.

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.

Objective C - Override setter to accept objects of a different type

I'm trying to override the setter of an NSManagedObject so that I can pass in an object of a different type, do a transformation and then set the property. Something like this:
- (void)setContentData:(NSData *)contentData
{
NSString *base64String;
// do some stuff to convert data to base64-encoded string
// ...
[self willChangeValueForKey:#"contentData"];
[self setPrimitiveValue:base64String forKey:#"contentData"];
[self didChangeValueForKey:#"contentData"];
}
So, in this case the contentData field of my NSManagedObject is an NSString *, and I want to allow the setter to accept an NSData * which I would then convert to an NSString * and save it to the model. However, if I try to do this I get warnings from the compiler about trying to assign an NSData * to an NSString *:
myObject.contentData = someNSData;
-> Incompatible pointer types assigning to 'NSString *' from 'NSData *__strong'
Is there a better way to go about this, or perhaps I should avoid the setters altogether and create custom "setters" that allow me to pass in the NSData * and set the NSString * field without a compiler warning?
I think this is an instance where your fighting with the tools and frameworks is a significant design smell. Retreat from this notion of trying to override the expected data type of a fundamental property for your class.
You didn't say whether the NSManagedObject you are subclassing is under your control. If it's going to be part of your design to have it be something of a template for management of other types of contentData than NSString, then declare it as type id in the root class and specialize in the subclasses. That should prevent the warning.
Probably, you want to follow a Cocoaism: don't subclass. Can you achieve whatever functionality you're looking for from the superclass by say extracting it into a helper class that is held as a property by each of the varying-behavior managed object classes?
following up on my "setContentData: (id) contentData" comment, try something like this:
- (void)setContentData:(id)thingToWorkWith
{
NSString * base64String = nil;
if(thingToWorkWith isKindOfClass: [NSData class])
{
// convert data to string
}
if(thingToWorkWith isKindOfClass: [NSString class])
{
// set up base64 string properly
}
if(base64String)
{
// do some stuff to convert data to base64-encoded string
// ...
[self willChangeValueForKey:#"contentData"];
[self setPrimitiveValue:base64String forKey:#"contentData"];
[self didChangeValueForKey:#"contentData"];
}
}
Make sure to get rid of the "#synthesize" bit for contentData in your .m file, create a "getter" method as well, and because you're using "id" for the setter parameter, you may have to adjust your "#property" declaration a bit. I haven't tried exactly what you are attempting to do (i.e. no warranties on this technique).

Why does NSString respond to appendString?

I was playing with the respondsToSelector method in Objective-C on MacOS-X 10.6.7 and Xcode 4.0.2, to identify if an object would respond to certain messages. According to the manuals, NSString should not respond to appendString: while NSMutableString should. Here's the piece of code which tests it:
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *myString = [[NSString alloc] init];
if ([myString respondsToSelector:#selector(appendString:)]) {
NSLog(#"myString responds to appendString:");
} else {
NSLog(#"myString doesn't respond to appendString:");
}
// do stuff with myString
[myString release];
[pool drain];
return 0;
}
and here's the output:
Class02[10241:903] myString responds to appendString:
I'd sort of expected the opposite. How does an NSString object respond to appendString: ? What's going on here that I'm missing ?
Short answer: That string is of type NSCFString, a class that inherits from NSMutableString, hence it responds to the selectors for the methods declared in NSMutableString, including superclasses.
Not so short answer: Foundation strings are toll-free bridged with Core Foundation strings. Developers use the opaque types CFStringRef (bridged with NSString) and CFMutableStringRef (bridged with NSMutableString) to refer to these strings so, at first glance, there are two different types of strings: immutable and mutable.
From a Core Foundation internal implementation perspective, there’s a private type called struct __CFString. This private type keeps a bit field that stores, amongst other information, whether the string is mutable or immutable. Having a single type simplifies implementation since many functions are shared by both immutable and mutable strings.
Whenever a Core Foundation function that operates on mutable strings is called, it first reads that bit field and checks whether the string is mutable or immutable. If the argument is supposed to be a mutable string but it in fact isn’t, the function returns an error (e.g. _CFStringErrNotMutable) or fails an assertion (e.g. __CFAssertIsStringAndMutable(cf)).
At any rate, these are implementation details, and they might change in the future. The fact that NSString doesn’t declare -appendString: doesn’t mean that every NSString instance doesn’t respond to the corresponding selector — think substitutability. The same situation applies to other mutable/immutable classes such as NSArray and NSMutableArray. From the developer perspective, the important thing is that the object that’s been returned is of a type that matches the return type — it could be the type itself or any subtype of that type. Class clusters make this a tad more convoluted but the situation is not restricted to class clusters per se.
In summary, you can only expect that a method returns an object whose type belongs to the hierarchy (i.e., either the type itself or a subtype) of the type for the return value. Unfortunately, this means that you cannot check whether a Foundation object is mutable or not. But then again, do you really need this check?
You can use the CFShowStr() function to get information from a string. In the example in your question, add
CFShowStr((CFStringRef)myString);
You should get an output similar to:
Length 0
IsEightBit 1
HasLengthByte 0
HasNullByte 1
InlineContents 0
Allocator SystemDefault
Mutable 0
Contents 0x0
where
Mutable 0
means that the string is in fact immutable.
This probably has to do with the implementation. NSString is a class cluster, which means that NSString is just a public interface and the actual implementing class is different (see what the class message gives you).
And at the same time NSString is also toll-free bridged to CFString, meaning that you can switch before those two types freely just by casting:
NSString *one = #"foo";
CFStringRef two = (CFStringRef)one; // valid cast
When you create a new string you really get a NSCFString back, a thin wrapper around CFString. And the point is that when you create a new mutable string, you also get an instance of NSCFString.
Class one = [[NSString string] class]; // NSCFString
Class two = [[NSMutableString string] class]; // NSCFString
I guess this was convenient from the implementation point of view – both NSString and NSMutableString can be backed by a common class (= less code duplication) and this class makes sure you don’t violate the immutability:
// “Attempt to mutate immutable object with appendString:”
[[NSString string] appendString:#"foo"];
There’s a lot of guess work in this answer and I don’t really understand the stuff, let’s hope somebody knows better.
You should not make assumptions about a method being not there. That method might be used internally or for whatever reason it exists. Technically, it's just private API.
You only have a contract to the public declarations (docs), and they don't show that message. So be prepared to get into trouble rather quickly if you use other features.

Functions in Objective-C

I am trying to write a function which returns a string created from two input strings;
but when I try the function declaration
NSString Do_Something(NSString str1, NSString str2)
{
}
the compiler gets sick. (Worked fine for a different function with int arguments.)
If I change the input arguments to pointers to strings, in also gets sick.
So how do I pass Objective-C objects into a function?
All Objective-C objects being passed to functions must be pointers. Rewriting it like this will fix your compiler error:
NSString *Do_Something(NSString *str1, NSString *str2) { }
Also, please keep in mind that this is a (C-style) function and not an instance method written on an Objective-C object. If you wanted this to actually be a method on an object it would probably look something like this:
NSString *doSomethingWithString1:(NSString *)str1 string2:(NSString *)str2 { }
I say "probably" because you can name it however you want.
Functions are perfectly fine in Objective-C (and in fact earn some of the language's benefits).
See my answer to C function always returns zero to Objective C, where someone was trying what you are and had a problem with the compiler assuming return type. The structure that I set up there is important when you are using functions, just like when you are using objects and methods. Be sure to get your headers right.
To be pedantic, you're using a function definition of:
NSString *DoSomething(NSString *str1, NSString *str2) {
// Drop the _ in the name for style reasons
}
And you should be declaring it in a .h file like so:
NSString *DoSomething(NSString *str1, NSString *str2);
Just like C.
that doesn't work for me. i've just declared in the .h:
NSString *myFunction(NSDecimal *value);
and i type in the .m:
NSString *myFunction(NSDecimal *value){
//code
}
but always i get an error saying expected '(' before '*' token
now is fixed. for some reason... sorry.