What is a practical example of using Kiwi's KWCaptureSpy? - ios

I'm having trouble understanding what a practical application of using Kiwi's KWCaptureSpy is. I could do something like this and have it pass:
__block id successJSON;
KWCaptureSpy *successBlockSpy =
[HNKServer captureArgument:#selector(GET:parameters:completion:)
atIndex:2];
[[HNKServer sharedServer] GET:#""
parameters:nil
completion:^(id JSON, NSError *error) {
successJSON = JSON;
}];
HNKServerRequestCallback successBlock = successBlockSpy.argument;
successBlock(#"JSON", nil);
[[successJSON shouldEventually] equal:#"JSON"];
but that doesn't seem to actually be testing anything. The example in Kiwi's documentation doesn't help: https://github.com/kiwi-bdd/Kiwi/wiki/Mocks-and-Stubs#capturing-arguments
Has anyone had a good reason to use KWCaptureSpy in practice?

Here's a possible scenario:
you're consuming a RESTful webservice that allows you to update your profile by doing a POST /user with the details you want to update.
you have a HNKUser class that declares an updateFirstName:lastName: method that calls the webservice
you want to make sure that the method will send only the firsName and lastName to the server (e.g. it doesn't also send birthday and other details)
Supposing the method in discussion looks like this (I've omitted the completion handlers for simplicity):
- (void)updateFirstName:(NSString*)firstName lastName:(NSString*)lastName {
// preparation code
// ...
[serverApi POST:#"/user" parameters:someParamsYouveBuiltInTheMethod completion:someCompletionHandler];
// ...
}
then you might want to capture the second argument and make sure that it contains only the firstName and lastName fields, and also that those fields have the proper value.
As a note, spies are recommended to be used on mocks, and from your example I think yours is not.

Related

Best practice to declare Objective-C blocks as variable

I have a question regarding the best practice for declaring a block as a variable.
Initially I wrote my block variable like this:
id actionHandler = ^(UIAlertAction * action) {
// Handling code
};
To be later used like so:
UIAlertAction *action = [UIAlertAction actionWithTitle:#"Title"
style:UIAlertActionStyleDefault
handler:actionHandler];
But when I came across Apple's Working With Blocks guide, I saw I could rewrite it like so:
void (^actionHandler)(UIAlertAction * action) = ^(UIAlertAction * action) {
// Handling code
};
Is this the 'correct' way to declare it? That is in my opinion not as readable, but I don't have a lot of experience with Objective-C. So what is the best practice for declaring a block as a variable?
Edit: Alright, thanks all for the clarification! Defining a typedef as shown by amin-negm-awad and others seems like a good alternative approach as well.
There is no one-fits-all answer here: when you declare your block variable as id you no longer have compile-time information associated with your block, so calling it manually becomes problematic:
id myHandler = ^(NSString *str) {
NSLog(#"%#", str);
};
// Error: Called object type id is not a function or function pointer
myHandler(#"Hello");
if you want to make a direct call to the block from your code, you need to cast it back to a block.
On the other hand, if you declare a block variable only so that you could pass it to a function that takes a block as a parameter, using id provides a more readable approach.
Additional to the problem mentioned by dasblinkenlicht I want to ask a rhetoric question:
Likely you know that you can substitute this code …:
NSString *string = #"All about types";
… with this code:
id string = #"All about types";
Would you do? I'm sure, you don't.
So why should one change the "typed" version of the var into an id version? The only reason is, that the syntax of block types is unhandy and not easy to read (and not easy to write). I always define a concrete type to get rid of the unhandy syntax:
typedef void (^ActionHandlerType)(UIAlertAction * action);
And then:
ActionHandlerType actionHandler = ^(UIAlertAction * action) {
// Handling code
};
To make that clear: id is great to use the dynamic nature of Objective-C's message passing. But block execution is neither late bound. Nor the parameters of the block can change its number or type, so there is nothing to dynamically bind. It is a simple call with fixed numbers of arguments, fixed typed. Therefore the usage of id is possible as a side-effect of the block's object nature. But it is not an usage, which is intended.
BTW: If you use a concrete type in a parameter list, Xcode can autocomplete the syntax of the argument. With id this is not possible. Obviously.
If you use id in this context the compiler will not check that the type of the block you declare matches the type of the block the method expects. If you accidentally get the block wrong nasty, hard to debug, things will probably happen when the method tries to use the block...
So if you never make mistakes go with id, but if like me you do provide the correct type so the compiler can help you out when you do.
To make it easier, and consequently less error prone, use a typedef, e.g.:
typedef void (^AlertActionHandler)(UIAlertAction * action);
...
AlertActionHandler actionHandler = ^(UIAlertAction * action) { ...

Parse.com caching with localDataStore enabled

I have caching problems in the iOS-Parse-SDK and I don't know what is wrong. I'm using the local datastore and it runs smoothly. But sometimes when I call a PFCloud function I don't get the current but an old version of an object or at least some fields of it are old (in my case it is a custom object with the field "status"). The strange thing is if I call the same function in the dev console on the Parse site I get the updated object. Is there anything I can do to prevent caching within the app or at least make sure I get the current version of an object?
Example PFCloud call:
[PFCloud callFunctionInBackground:#"importFriends"
withParameters:#{#"phoneNumbers": numbers}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
DDLogDebug(#"Request Send Numbers SUCCESS: %#", result); //result objects are out of date
if (successBlock) successBlock(result);
}else{
DDLogDebug(#"Request Send Numbers FAIL: %#", error);
if (failedBlock) failedBlock(error);
}
}];
It seem like when you use the localdatastore and custom classes for any object you can't overwrite it's setters otherwise parse will never change it again. Even though the setter did exactly what a normal setter would do just a little bit more math afterwards.

Get NSString out of ENNoteContent with Evernote Cloud SDK 2.0

I am new to Evernote SDK development and am using the evernote cloud SDK 2.0 as recommended by Evernote.
However, I am having trouble to get the NSString content out of the ENNoteContent object. I have tried the followings from searching online but none seems to work with the cloud sdk as I guess they are all for the old version of Evernote SDK...
1 Using "convertENMLToHTML" method.
According to this and this, I could call convertENMLToHTML directly on an ENNoteContent object much like this convertENMLToHTML:note.content. However, in the cloud SDK, this resulted in an exception inside ENMLUtility that terminates the app because convertENMLToHTML is expecting an NSString as opposed to ENNoteContent and the first thing this function does is trying to call [enmlContent dataUsingEncoding:NSUTF8StringEncoding]] which caused the exception if enmlContent is a pointer to ENNoteContent but not a pointer to NSString.
2 Attempting to get _emml object out of the ENNoteContent object
This post has a quote of calling [note.content enml] but this again doesn't work with cloud sdk as object enml isn't defined in the interface.
Does anyone know how one can get an NSString out of ENNoteContent? I would expect this to be a very straightforward process but am surprised that I wasn't able to find anything that works for the Cloud SDK.
3 Using generateWebArchiveData method
Per Sash's answer below, I have also attempted to use the generateWebArchiveData method in the example from the cloud sdk. The code I have looks like this:
[[ENSession sharedSession] downloadNote:result.noteRef progress:^(CGFloat progress) {
} completion:^(ENNote *note, NSError *downloadNoteError) {
if (note) {
NSLog(#"%#", note.title);
[note generateWebArchiveData:^(NSData *data) {
NSString* strContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"test content %#", strContent);
}];
} else {
NSLog(#"Error downloading note contents %#", downloadNoteError);
}
}];
However, strContent outputs "null" for a note that I have verified with legitimate content.
As a temporary hack, we added #property (nonatomic, copy) NSString * emml;
in ENNoteContent.h and removed the same line in ENNoteContent.m to get around this for now.
You are close. Technique #1 above is what you want, but as you discovered the enml property is private in the "default" SDK. Import the "advanced" header and you'll have access to note.content.enml. That is a string, and you can send it to convertENMLtoHTML if you prefer an HTML representation.
Do note that there is no "plaintext" string content for an existing note. You'll always see it as markup, and if you want to get rid of the markup, doing so is beyond the scope of the SDK-- how to do that depends very much on what the content you're dealing with looks like.
You should check out their samples included with SDK, seems like
-[ENNote generateWebArchiveData:] will get you HTML NSData in the completion block
https://github.com/evernote/evernote-cloud-sdk-ios/blob/master/Getting_Started.md#downloading-and-displaying-an-existing-note might also help

Is it a good practice to extend NSError

Sorry for asking this question. I know in java we are extending Exception class for custom exceptions. But I don't see any scenarios for that in objective c.
So my question, Is it a good practice to extend NSError and introducing custom errors? If so when we should extend NSError class. I checked documentation for this too. But I can't see overriding notes for NSError.
While I agree that you shouldn't subclass NSError, it is very useful to put categories on it, and I do this regularly. For example, say your system often posts errors that come from some JSON block. I'd find it very convenient to create a category like:
#interface NSError (MyErrors)
// Construct an NSError from data in JSON.
// Include full JSON response in the userInfo
+ (NSError *)myErrorWithJSON:(JSON *)json;
// Parse the text out of the user info
- (NSString *)myErrorMessage;
// The full JSON error block as a string
- (NSString *)myErrorJSON;
// BOOLs can be helpful sometimes, or you could return an enum for use in a switch.
- (BOOL)myIsNetworkError;
- (BOOL)myIsAuthError;
#end
I often write little helpers to construct NSError more simply, construct the userinfo the way I want, and the pull data back out of the userinfo without callers needing to know its internal representation. I find this to be a very good form of data-hiding, and encourages the use of more descriptive messages.
Similarly, even for smaller projects, I often create a +myErrorWithCode:localizedDescription: category method. I know my domain, so I usually don't need to pass that, and this makes it a lot easier to set the NSLocalizedDescription key in the user info. Again, this encourages better errors by making them easier to create, and makes it easier to change the implementation details of your error handling.
I've never seen it done and that's because NSError is already very versatile. It allows the type of error to be defined by setting the domain and code properties and allows arbitrary additional information to be attached within the userInfo dictionary.
So, no, it's not good practice.
In the documentation is written that it is ok to subclass:
Applications may choose to create subclasses of NSError, for example,
to provide better localized error strings by overriding
localizedDescription.
In my case I am working with OSStatus which is Int32. NSError constructor supports only Int. So I need to subclass it to support OSSStatus.
It's not a bad idea to extend NSError.
I also have made a category on NSError for my own use. I would like to share it with you.
(1) Make a strings file to define all the error codes:
/* Following are general error messgaes those we can show to user
regarding to Internet connection and request. You can add more
codes. */
"-1001" = "Connection time out";
"-1003" = "Cannot find Host";
"-1004" = "Cannot connect to Host";
"-1005" = "Server is temporarily down";
"-1009" = "The Internet connection appears to be offline";
"-1012" = "Authentication failed";
"2000" = "This is a custom error message"; // custom created error code
/* Always describe unknow error with whatever you want in
except case (i.e. except error codes). If not mentioned
the following line, still you will get message "Unknown error" */
"Unknown error" = "Network error occured";
(2) Make a category on NSError, let say "NSError+ErrorInfo":
#interface NSError (ErrorInfo)
-(NSString *)userDescription;
#end
(3) Define it:
#define ERROR_KEY(code) [NSString stringWithFormat:#"%d",code]
#define ERROR_LOCALIZED_DESCRIPTION(code) NSLocalizedStringFromTable(ERROR_KEY(code),#"Errors",nil)
#implementation NSError (ErrorInfo)
-(NSString *)userDescription
{
NSString *errorDescrption = NSLocalizedStringFromTable(ERROR_KEY(self.code),#"Errors",nil);
if (!errorDescrption || [errorDescrption isEqual:ERROR_KEY(self.code)]){
return NSLocalizedStringFromTable(#"Unknown error",#"Errors",nil);;
}
else{
return ERROR_LOCALIZED_DESCRIPTION(self.code);
}
return nil;
}
#end
(4) Make use of it:
NSError *yourError; // This can be any NSError object you get
yourError = [NSError errorWithDomain:#"yourDomain" code:2000 userInfo:details]; // Just for test
NSLog(#"%#",[yourError userDescription]);

iOS RestKit artificial identification attributes

linked RestKit issue #1604
If my API gives me no id attribute, but i still want to cache the objects via Core Data, what should i use to identify my object.
For example i have
response = {
translation = {
text = "longlongtext";
dictionary = "general";
lang = "en";
};
otherdata = {
author = "May";
date = "434134";
};
}
So i would be glad to use hashed (md5) translation text as an id string.
Notice that my future requests which happen without network connection should be able to identify this cached entity and give it as a result.
I cant declare mapping to fill responseID property from [translation.text md5hash] to use as responseMapping.identificationAttributes = #[ responseID ]; because mappings doesnt have such feature.
As proposed by #segiddins in the github issue discussion:
... in your managed object subclass, hook into one of the core data callbacks to generate a compound key that is saved as part of the model and just use that key as your identification attribute.
The approach may look like this:
#property (nonatomic, copy) NSString *identifier;
- (void)willSave
{
[super willSave];
NSString *computedIdentifier = [[NSString stringWithFormat:#"%#%#", self.text, self.langCode] md5hash];
[self setPrimitiveValue:computedIdentifier forKey:#"identifier"];
}
I also wanted to do a hash of the JSON fields like you, but as you know it's not possible. I ended up doing the following to achieve (I believe) the same end result, which is for JSON objects returned without a unique ID, a unique identification attribute is generated by RestKit:
entityMapping.identificationAttributes = #[ #"text",#"dictionary",#"lang",#"author",#"date" ];
You should keep this kind of functionality outside of RestKit if you have no identifiers being provided by the server.
I would generate a custom identifier for each request you make (a GUID), I'd save that identifier into each of the result objects in the RestKit success completion block. I'd also save the request details and the identifier into user defaults.
Now, when the user makes a request and they are offline you can analyse user defaults to determine if it's a repeat request and find the identifier with which to query the results from the data store.
Just to clarify about offline requests after discussion.
In the end, such feature (offline requests) does not exist inside RestKit. The way you can achieve it is complicated, but possible. Steps are:
you use CoreData with RestKit (RKEntityMapping, managed objects etc)
Your provide good identification attributes for entities. It can be URL of request from #metadata.
on both success and failure callbacks from getObjectsAtPath you query CoreData with fetch request and return the result just the same way as if it was loaded directly and taken from mappingResult.firstObject and mark it as cached if it is old result loaded on failure.

Resources