I have objects, more precisely models, some properties of which are lazily loaded, i.e fetched on read, from a server. At the moment, I apply the classical technique, e.g.
#synthetize description = _description;
- (NSString *)description {
if (!_description) {
NSError *error = nil;
_description = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
_description = nil;
// error handling
}
}
return _description;
}
However, it involves a lot of code repetition. Of course, I still can have a generic method doing this and calling this method in all the getters (that's what I do). But do you have a better idea ?
EDIT: To make the code safer as suggested in comments. Here is another suggestion:
#synthesis description = _description;
- (NSString *)descriptionWithCompletion:(void (^)(NSString *description, NSError *error))completion {
if (!_description) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
_description = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
if (error) _description = nil;
completion(_description, error);
});
}
completion(_description, nil);
}
So, you're trying to reduce the boilerplate of methods like this:
- (NSString *)prop {return [self _genericGetterForProperty:_prop];}
Using a macro is one option; it would reduce the amount of text, but you still need a line for each method, so it's not clear to me that's it's really worth the extra complexity.
There is a way you could do this via dynamic method resolution. Essentially, you implement + resolveInstanceMethod: on your class. It will be called if someone tries to call a method on your model object that you haven't supplied at compile time. You can implement it to check the selector passed to see if it matches your xxxWithCompletion: structure. If it does, you can build an implementation based on the "xxx" value and add it to your class. You could prevent the compiler from warning you about this by declaring your properties as #dynamic or by explicitly suppressing warnings via pragmas.
I don't recommend it, though. It's a complicated and tricky solution; unless you've got hundreds of these properties, I would just write the boilerplate. (Or write a script to write the boilerplate.)
Related
I am trying to use OCMock library. I am trying to create mock of class object, but it is failing to verify the method. I am unable to understand why the tests are failing.
#interface MyClass:NSObject
+(void) someMethod;
#end
#implementation MyClass
+(void) someMethod
{
NSError* error = nil;
if (![Utility isValidPropWithError:&error])
{
[Logger log:LoggerLevelWarning message:[error localizedDescription] className:className];
}
}
#end
Test :
-(void)testIfLoggerIsset{
id partialMockLogger = OCMClassMock([Logger class]);
id partialMockUtility = OCMClassMock([Utility class]);
id partialMockClass = OCMClassMock([MyClass class]);
NSError *error = nil;
OCMExpect([partialMockUtility isValidPropWithError:&error]);
[MyClass someMethod];
//This works fine.
OCMVerifyAll(partialMockClass);
NSString *className = #"classname";
//This is failing...
OCMVerify([partialMockUtility isValidPropWithError:&error]);
OCMVerifyAll(partialMockUtility);
//This is failing...
OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]);
[partialMockUtility stopMocking];
[partialMockLogger stopMocking];
}
In the above code, although [Utility isValidPropWithError:&error]; is called OCMVerify([partialMockUtility isValidPropWithError:&error]);is failing.
Several things here:
First, OCMVerify([partialMockUtility isValidPropWithError:&error] is failing because you are expecting the address of the NSError object you created in the test to be passed to isValidPropWithError:, but in MyClass +someMethod you are creating a different NSError object. The addresses of two different objects will not be the same.
To fix this, change your expectation and verification to:
OCMExpect([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg anyPointer]]);
OCMVerify([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg
and just ignore the actual value of the parameter and expect that it's going to be an NSError pointer (since you're creating it inside of someMethod, there's no way to know what it's going to be before you call the method).
Second, since you are already explicitly verifying +isValidPropWithError, OCMVerifyAll(partialMockUtility) isn't going to verify anything. You should either explicitly verify all of your expectations, or simply use OCMVerifyAll(partialMockUtility) and let it verify all your expectations and don't bother with expecting the specific call. OCMVerifyAll will verify everything you expect on the mock object you give it. This isn't going to cause a test failure - both calls will pass, since you've already verified the call the first time, the call to OCMVerifyAll() isn't going to have anything to verify, so it will pass.
Last, OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]); is failing because you didn't set an expectation for it.
This is a snippet from AFNetworking's sample code:
+ (void)globalTimelinePostsWithBlock:(void (^)(NSArray *posts, NSError *error))block {
[[AFAppDotNetAPIClient sharedClient] getPath:#"stream/0/posts/stream/global" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSArray *postsFromResponse = [JSON valueForKeyPath:#"data"];
NSMutableArray *mutablePosts = [NSMutableArray arrayWithCapacity:[postsFromResponse count]];
for (NSDictionary *attributes in postsFromResponse) {
Post *post = [[Post alloc] initWithAttributes:attributes];
[mutablePosts addObject:post];
}
if (block) {
block([NSArray arrayWithArray:mutablePosts], nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block([NSArray array], error);
}
}];
}
What I don't understand are:
The (void (^)(NSArray *posts, NSError *error))block part. Assuming that it is a block, does it mean the block is a parameter of the globalTimelinePostsWithBlock method?
Following the first question, can anyone explain the syntax for me? Why is there a block keyword in the end?
if you don't know how blocks work.. then don't bother trying to understand it just by looking at the code (even if you have used lambda/anonymous functions in other languages like javascript or ruby).. b/c the objective-c syntax is a class on it's own..
i'd recommend you take the time to understand block syntax in obj-c on it's own.. then take a look at examples that use them. This tutorial is excellent (two parts)..
I did what you did before.. and pulled out half my hair.. after looking at the said tutorial.. my hair grew right back up :)
just for fun i'll try to address your specific questions:
1.The (void (^)(NSArray *posts, NSError *error))block part. Assuming that it is a block, does it mean the block is a parameter of the globalTimelinePostsWithBlock method?
yes it is.. so this is a way of calling this method:
// first define the block variable
void(^block)(NSArray *posts, NSError *error) = (NSArray *posts,NSError *error) {
// block body
// posts and error would have been passed to this block by the method calling the block.
// so if you look at the code sample below..
// posts would be [NSArray arrayWithArray:mutablePosts]
// and error would just be nil
}
// call the function
[AFNetworking globalTimelinePostsWithBlock:block];
2. Following the first question, can anyone explain the syntax for me? Why is there a block keyword in the end?
basically the block keyword is the name of the argument.. notice how it's used in the body of the method:
if (block) {
block([NSArray arrayWithArray:mutablePosts], nil);
}
again to understand how/why.. i recommend you look at the above article.. learning blocks in obj-c has a bit of learning curve.. but once you master it.. it's an amazing tool. please take a look at my answer here to see some sample uses for blocks.
Here is also a sample question/answer that provides a case study of converting delegation into a block based approach, which can also illustrate how blocks work.
The block is passed into the method as something to be called when the API call succeeds. globalTimelinePostsWithBlock will call the block passed in with the data (and possibly an NSError)
block in this case isn't a keyword, it's just the name of the variable.
If you wanted to use globalTimelinePostsWithBlock, you would call it like
[ClassName globalTimelinePostsWithBlock:^(NSArray *posts, NSError *error) {
// Check error, then do something with posts
}];
(where ClassName is the name of the class globalTimelinePostsWithBlock is defined on)
Block definition are similar to C-functions.
(void (^)(NSArray *posts, NSError *error))block
The initial void defines the return type of the function.
The ^ is the block pointer. Similar to * for objects.
(NSArray *posts, NSError *error) are the parameters with variable names.
block is the variable in which this block gets stored. (Bad naming here)
I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)
I could not figure out how to change the value of results inside the success block. I use __block like some post suggests but results is forever nil. I set breakpoint inside of block and make sure that JSON is not nil, which download data as I expected.
I am using AFNetworking library if that's relevant.
+(NSArray *)eventsByCityID:(NSString *)cityID startIndex:(NSUInteger)start count:(NSUInteger)count
{
__block NSArray *results = nil;
[[DoubanHTTPClient sharedClient] getPath:#"event/list" parameters:#{#"loc":dataSingleton.cityID} success:^(AFHTTPRequestOperation *operation, id JSON) {
results = [JSON valueForKey:#"events"];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"download events error: %# \n\n",error);
}];
return results;
}
More likely than not, that [very poorly named] method getPath:parameters:success:failure: is asynchronous.
Thus, you need to tell something in the success block that the value has changed. I.e.
^{
[something yoManGotEvents:[JSON valueForKey:#"events"]];
}
(Methods shouldn't be prefixed with get outside of very special circumstances. Third party libraries with lots of API using that prefix outside of said circumstances raise question as to what other system specific patterns they may not be following.)
Xcode 4.3
I've read the SO questions on NSError**, so I wrote a simple test program that uses a slightly different syntax recommended by Xcode 4.3 (see __autoreleasing below), so I'm not 100% sure if this is correct, although the code does appear to function properly. Anyway, just a simple file reader, prints an error if the file can't be found.
Questions
Would like to know if the NSError initialization, argument passing using &, and error condition checking are correct.
Also, in the readFileAndSplit.. method, I noticed a big difference between if(!*error) and if(!error), in fact, if(!error) does not work when no error condition is raised.
File Reading Method w/Possible Error Condition
-(NSArray*) readFileAndSplitLinesIntoArray:(NSError *__autoreleasing *) error {
NSString* rawFileContents =
[NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error
NSArray* fileContentsAsArray = nil;
if(!*error)
fileContentsAsArray =
[rawFileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
return fileContentsAsArray;
Caller
SimpleFileReader* reader = ...
NSError* fileError = nil;
NSArray* array = [reader readFileAndSplitLinesIntoArray: &fileError];
if(fileError){
NSLog(#"Error was : %#, with code: %li",
[fileError localizedDescription],(long)[fileError code]);
}
There are a couple of issues.
First, As per Apple's Error Handling Programming Guide, you should be checking a method's return value to determine whether a method failed or not, and not NSError. You only use NSError to get additional error information in the event that the method failed.
E.g.:
NSArray* fileContentsAsArray = nil;
NSString* rawFileContents = [NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error];
if (rawFileContents)
{
// Method succeeded
fileContentsAsArray = [rawFileContents ...];
}
return fileContentsAsArray; // may be nil
Second, NSError out parameters are typically optional and may be NULL. But if you pass a NULL error variable into your method it will crash on this line:
if (!*error) {
because you're dereferencing a NULL pointer. Instead, you must always check for NULL before referencing a pointer, like so:
if (error && *error)
{
// Do something with the error info
}
However, if you rewrite the method as indicated above then you won't be accessing the error variable at all.