Returning an NSString from an NSInvocation using setReturnValue
Asked Answered
D

1

5

When I set the return value of an NSInvocation to be an NSString, the invoker is receiving an NSCFString.

In my case I'm mocking to pull a bundle path from file included by unit tests:

[[[_bundlePartial stub] andDo:^(NSInvocation *invocation) {
    NSString* resourceName = [invocation getArgumentAtIndexAsObject:2];
    NSString* type = [invocation getArgumentAtIndexAsObject:3];
    NSString* path = [[NSBundle bundleForClass:self.class] pathForResource:resourceName ofType:type];
    if (!path)
    {
        path = [_bundleOriginal pathForResource:resourceName ofType:type];
    }
    [invocation setReturnValue:(void*)path];
}] pathForResource:OCMOCK_ANY ofType:OCMOCK_ANY];

I call it like this:

NSString* jsonPathInBundle = [[NSBundle mainBundle] pathForResource:self.fileName ofType:self.fileExtension];

Unfortunately I'm getting back a NSCFString. This makes some sense, since my NSString is backed by a NSCFString, but when I lose the bridge I can no longer call NSString instance methods on the object. Is there a way I can return the value as an NSString?

Dulcinea answered 5/3, 2014 at 20:52 Comment(2)
NSString is a class cluster. All strings are instances of some subclass. What method can you not call on the instance?Amaryllidaceous
@MartinR It would fail on the next line: NSString* json = [NSString stringWithContentsOfFile:jsonPathInBundle encoding:NSUTF8StringEncoding error:error]; because stringWithContentsOfFile:encoding:error: is not a method on NSCFString.Dulcinea
D
9

After examine the OCMock code, I found my problem. Effectively a typo but its subtle enough that I don't think it's worth deleting the question.

Change:

[invocation setReturnValue:(void*)path];

To:

[invocation setReturnValue:&path];

My original way of writing this destroyed a layer of abstraction because the NSString was being treated as an address, rather than using its actual address.

Dulcinea answered 5/3, 2014 at 21:0 Comment(1)
Yes -- NSInvocation always needs pointers to the raw value, but you passed the value directly. It would have dereferenced the pointer, getting the "isa" value (the Class object), and treated that as an NSString instance, most likely. Secondly, if using ARC, use __unsafe_unretained on the "type" and "resourceName" variables (used to pull objects out of an NSInvocation), otherwise you will likely get crashes later on since ARC will over-release those objects.Locke

© 2022 - 2024 — McMap. All rights reserved.