OCMock: how to stub -[NSString stringWithContentsOfURL:encoding:error:] - ios

I've tried many things. This still does not work:
id stringMock = OCMClassMock([NSString class]);
[[[[stringMock stub] ignoringNonObjectArgs] stringWithContentsOfURL:[OCMArg anyPointer] encoding:NSUTF8StringEncoding error:(NSError * __autoreleasing *)[OCMArg anyPointer]] andReturn:stringFromFile];
How can I make -[NSString stringWithContentsOfURL:encoding:error:] return stringFromFile anywhere in my code?

Unfortunately, it's not possible.
From OCMock documentation about the limitations (section 10):
10.5 Class methods on NSString cannot be stubbed or verified
id stringMock
= OCMClassMock([NSString class]); // the following will not work OCMStub([stringMock stringWithContentsOfFile:[OCMArg any]
encoding:NSUTF8StringEncoding error:[OCMArg setTo:nil]]);
It is not
possible to stub or verify class methods on NSString. Trying to do so
has no effect.
You can, however, wrap NSString and mock the wrapper. It means that you'll need to replace all the calls in your code with this wrapper, but the tests would work..
Here's an example:
The wrapper:
#interface NSStringWrapper : NSObject
+ (nullable id)stringWithContentsOfURL:(NSURL *)url;
#end
#implementation NSStringWrapper
+ (nullable id)stringWithContentsOfURL:(NSURL *)url
{
return [NSString stringWithContentsOfURL:url];
}
#end
The test:
static NSString *stringFromFile = #"myStringFromFile";
-(void)testStringWrapper
{
id nsstringWrapperMock = OCMClassMock([NSStringWrapper class]);
OCMStub(ClassMethod([nsstringWrapperMock stringWithContentsOfURL:OCMOCK_ANY])).andDo(^(NSInvocation *invocation)
{
[invocation setReturnValue:&stringFromFile];
});
NSURL *fakeURL = [NSURL URLWithString:#"htttp://google.com"];
NSString *test = [NSStringWrapper stringWithContentsOfURL:fakeURL];
XCTAssertEqual(test, stringFromFile, #"Should always return stringFromFile");
}

Another option is to try OCMockito, which mocks class methods differently than OCMock.
__strong Class mockStringClass = mockClass([NSString class]);
[[given([mockStringClass stringWithContentsOfFile:anything()
encoding:NSUTF8StringEncoding
error:NULL])
withMatcher:anything() forArgument:2]
willReturn:stringFromFile];
Unlike OCMock which swizzles class methods, this requires you to inject the class. That is, instead of production code making a direct call like
str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
you need to do something like
str = [self.stringClass stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
You should be able to add OCMockito without disrupting your existing use of OCMock.

Related

No visible #interface for NSURL declares the selector componentsseparatedbystring

The NSArray declaration brings up an error because "no visible #interface for NSURL declares the selector componentsseparatedbytring".
NSURL *MyURL = [[NSBundle mainBundle]
URLForResource: #"artList" withExtension:#"txt"];
NSArray *lines = [MyURL componentsSeparatedByString:#"\n"]; // each line, adjust character for line endings
for (int i = 0; i < 10; i++) {
NSString *line;
//in lines;
NSLog(#"%#", [NSString stringWithFormat:#"line: %#", line]);
_wordDefBox.text = [NSString stringWithFormat:#"%#%#",_wordDefBox.text, lines];
}
You missed a step. Once you have the URL, you need to load the file into an NSString. Then call componentsSeparatedByString on the NSString.
NSURL *myURL = [[NSBundle mainBundle]
URLForResource: #"artList" withExtension:#"txt"];
NSError *error = nil;
// Use the appropriate encoding for your file
NSString *string = [NSString stringWithContentsOfURL:myURL encoding:NSUTF8StringEncoding error:&error];
if (string) {
NSArray *lines = [string componentsSeparatedByString:#"\n"];
// and the rest
} else {
NSLog(#"Unable to load string from %#: %#", myURL, error);
}
In general when you see such an error it means class X( here NSURL) doesn't have any method named Y ( e.g. componentsseparatedbystring) or at least it doesn't have it in its interface ie it's not it's public method, it may be it's private method and available to its implementation. Always try to make sense of what the compiler is telling you. To find out more you can 'Cmmd + click' on any class and it will take you to it's interface and you can see what public methods it has. Try that on NSString and NSURL
Here specifically : NSURL doesn't have that method. It doesn't belong to NSURL, it belongs NSString.

How does ARC deal with local variable within the method?

For example,
- (void) method
{
NSString *string = #"This is a string.";
}
Do I need to add
string = nil;
at the end of the method in order to let ARC release it?
Situation may be different in non-literal object such as
- (void) method
{
NSData *data = [[NSData alloc] init];
}
Do I need to add at the end
data = nil;
to release it?
You don't need to nil it, but ARC doesn't release it either. Since it's a literal, it's statically allocated in the app's binary. It's never released.
- (void)method {
NSString *string = #"I'm never released because I'm statically allocated";
NSString *arcReleasesMeAfterMyLastSourceRef = [NSString stringWithString:string];
}

is there way to replace the Core Data implementation of add and remove methods for relationships in a generic way?

PLEASE NOTE: This is not the same question as this question, as I already know that this stems from an Apple bug. Mind you, overriding the specific methods (say, -addFriendsObject: if I had a friends relationship) is not an option, since I need to do this in a category so it works for any managed object and regardless of modifying the model and rebuildiong the autogenerated classes from it.
The need for this stems from the fact that apparently the minute one makes relationships ordered and to-many (NSMutableOrderedSets) Core Data dynamic methods go to hell:
methods -add<Relationship>Object, -add<Relationship>, -remove<Relationship>Object and -remove<Relationship> will all crash with an exception alike 'NSInvalidArgumentException', reason: '*** -[NSSet intersectsSet:]: set argument is not an NSSet'
methods along the lines of insertObject:in<Relationship>AtIndex: will instead crash because no implementation for them was actually provided by CoreData.
As far as 2, I wrote a category that overrides -methodSignatureForSelector: and -forwardInvocation to instead do something like mutableOrderedSetValueForKey for the relationship name followed by the actual adding or removing.
Now for 1, the problem is that CoreData is actually providing implementations for those methods (though they are not the right implementations for ordered sets). So I need a way of intercepting those selectors too, so I can implement the behavior over mutableOrderedSetValueForKey.
Any ideas how to pull it off?
I don't know that this is the best approach (it's certainly not the prettiest thing ever), but it gets the job done:
#import "NSManagedObject+OrderedSets.h"
#import <objc/runtime.h>
#implementation NSManagedObject (OrderedSets)
+ (void)swizzleMethod:(SEL)originalSelector with:(SEL)replacementSelector
{
const char *methodTypeEncoding = method_getTypeEncoding(class_getInstanceMethod([self class], originalSelector));
class_replaceMethod(self,
originalSelector,
class_getMethodImplementation(self, replacementSelector),
methodTypeEncoding);
}
+ (void)initialize
{
NSEntityDescription *selfEntity = // get a hold of your NSManagedObjectContext and from its entities grab model.entitiesByName[NSStringFromClass(self)] ...
for (NSString *rKey in [selfEntity relationshipsByName]) {
NSRelationshipDescription *r = selfEntity.relationshipsByName[rKey];
if (r.isOrdered) {
NSString *rKeyFirstCaps = [[rKey substringToIndex:1] capitalizedString];
NSString *capitalizedKey = [rKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:rKeyFirstCaps];
NSString *addSelectorString = [NSString stringWithFormat:#"add%#Object:", capitalizedKey];
NSString *removeSelectorString = [NSString stringWithFormat:#"remove%#Object:", capitalizedKey];
[self swizzleMethod:NSSelectorFromString(addSelectorString) with:#selector(addOrRemoveObjectInOrderedSet:)];
[self swizzleMethod:NSSelectorFromString(removeSelectorString) with:#selector(addOrRemoveObjectInOrderedSet:)];
}
}
}
- (void)addOrRemoveObjectInOrderedSet:(NSManagedObject*)object
{
NSString *selectorString = NSStringFromSelector(_cmd);
NSString *prefix = [selectorString hasPrefix:#"add"] ? #"add" : #"remove";
selectorString = [selectorString stringByReplacingCharactersInRange:[selectorString rangeOfString:prefix] withString:#""];
selectorString = [selectorString stringByReplacingCharactersInRange:[selectorString rangeOfString:#"Object" options:NSBackwardsSearch] withString:#""];
selectorString = [selectorString substringToIndex:selectorString.length - 1];
NSString *selectorFirstLowerCase = [[selectorString substringToIndex:1] lowercaseString];
NSString *camelCasedKey = [selectorString stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:selectorFirstLowerCase];
if ([prefix isEqualToString:#"add"]) {
[[self mutableOrderedSetValueForKey:camelCasedKey] addObject:object];
}
else {
[[self mutableOrderedSetValueForKey:camelCasedKey] removeObject:object];
}
}
#end

gettin EXC_BAD_ACCESS while using nsstring

I am getting crazy during 4 hours and I really need help. Here is the code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//check if strGroup has prefix and suffix #
BOOL result;
result = [strGroup hasPrefix: #"#"];
if (result)
{
result = [strGroup hasSuffix: #"#"];
if (result)
{
NSMutableString* string = [NSMutableString stringWithString: strGroup];
str = [strGroup substringWithRange: NSMakeRange (1, [string length]-2)];
strToHoldAllContact = [NSString stringWithFormat:#"%#",str];
}
}
NSLog(#"strToHoldAllContact=%#",strToHoldAllContact);
}
I am gettin the value of strToHoldAllContact correctly. But when I try to access strToHoldAllContact from another method I am getting the error:
[CFString respondsToSelector:]: message sent to deallocated instance 0x856f2a0
Use
strToHoldAllContact = [NSString stringWithFormat:#"%#",str];
[[strToHoldAllContact retain] autorelease];
and forget about release.
where ever you are initializing or setting the string do this [strToHoldAllContact retain]; and don't forget to release it once you are done using it
Just replace
strToHoldAllContact = [NSString stringWithFormat:#"%#",str];
to
strToHoldAllContact = [[NSString alloc] initWithFormat:#"%#",str];
And release it after you don't need it anymore.
with ARC, in .h declare strToHoldAllContact as:
#property(strong) NSString *strToHoldAllContact;
in .m, use it (after #synthesize) as self.strToHoldAllContact = [NSString stringWithFormat:#"%#",str];
this way you will not have problems.
without ARC,in .h declare strToHoldAllContact as:
#property(retain) NSString *strToHoldAllContact;
and use it same ways as with ARC in.m file.

In Objective-C, How Can I Make a Global Configuration That Is Accessible to All?

I am new to Objective-C and iOS development and I'm not sure if I'm doing it right. Anyway, basically I have a file Configs.plist which, for now has two sets of Keys:Value (Customer:Generic and Short_Code:Default). I want these data to be easily accessible to all classes so I created these:
Configs.h
extern NSString * const CUSTOMER;
extern NSString * const SHORT_CODE;
#interface Configs
+ (void)initialize;
+ (NSDictionary *)getConfigs;
#end
Configs.m
#import "Configs.h"
NSString * const CUSTOMER = #"Customer";
NSString * const SHORT_CODE = #"Short_Code";
static NSDictionary *myConfigs;
#implementation Configs
+ (void)initialize{
if(myConfigs == nil){
NSString *path = [[NSBundle mainBundle] pathForResource:#"Configs" ofType:#"plist"];
settings = [[NSDictionary alloc] initWithContentsOfFile:path];
}
}
+ (NSDictionary *)getConfigs{
return settings;
}
#end
And on a the test file Test.m:
NSLog(#"Customer: %#", [[Configs getConfigs] objectForKey:CUSTOMER]);
NSLog(#"Short Code: %#", [[Configs getConfigs] objectForKey:SHORT_CODE]);
The thing is, this approach works but I want to know if there are better ways to do this.
I think this is good as long as your configuration does not change during execution. If it does, you're better off with the singleton exposing your config as properties, so you would be able to do something like this:
[[Config shared] customer];
[[Config shared] setShortCode:#"CODE"];
You could still init the config from the plist, or implement coding protocol to store it in the NSUserDefaults.

Resources