HTTP request over CFStream - ios

I am trying to get simple HTTP communications working using CFStream, however with the below code I still get a connection success even when I give an invalid URL (wwwww.yahoo.com).
Can someone point to me what is wrong with this code? I am expecting the call to CFReadStreamOpen() to fail but it is succeeding even though the URL is bad.
I haven't been able to find a simple CFNetwork example which does HTTP (without use of NSURL) that works on ARC. If anyone knows of such sample code please let me know.
- (void) test
{
CFStringRef m_host = CFStringCreateWithCString(NULL, "wwwww.yahoo.com", kCFStringEncodingASCII);
int m_port = 80;
CFHostRef host;
NSLog(#"Attempting to (re)connect to %#:%d", m_host, m_port);
{
CFReadStreamRef m_in = NULL;
CFWriteStreamRef m_out = NULL;
host = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)m_host);
if (!host)
{
NSLog(#"Error resolving host %#", m_host);
}
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host , m_port, &m_in, &m_out);
CFRelease(host);
CFStreamClientContext context = {0, nil,nil,nil,nil};
if (CFReadStreamSetClient(m_in, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkReadEvent, &context))
{
CFReadStreamScheduleWithRunLoop(m_in, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
if (CFWriteStreamSetClient(m_out, kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkWriteEvent, &context))
{
CFWriteStreamScheduleWithRunLoop(m_out, CFRunLoopGe tCurrent(),kCFRunLoopCommonModes);
}
BOOL success = CFReadStreamOpen(m_in);
CFErrorRef error = CFReadStreamCopyError(m_in);
if (!success || (error && CFErrorGetCode(error) != 0))
{
NSLog(#"Connect error %s : %ld", CFErrorGetDomain(error), CFErrorGetCode(error));
[NSThread sleepForTimeInterval:5.0];
}
success = CFWriteStreamOpen(m_out);
error = CFReadStreamCopyError(m_in);
if (!success || (error && CFErrorGetCode(error) != 0))
{
NSLog(#"Connect error %s : %d", CFErrorGetDomain(error), CFErrorGetCode(error));
[NSThread sleepForTimeInterval:5.0];
}
else
{
NSLog(#"Connected");
}
}
}

For asynchronous code such as yours, you need to set up the delegate and catch the error there. Check out http://iphonedevsdk.com/forum/iphone-sdk-development/17542-cfstream-catch-error-in-connection.html
According the the Apple Developer site, "If the stream can open in the background without blocking, this function always returns true.". Meaning the request to open the stream is sent out on a different thread and hence the function returns true when you call it and starts the connection process on the background thread. All read/write calls on the respective streams then become blocking until the background thread finishes opening up the stream. You will be able to see this if you set up a break point on "CFReadStreamOpen(m_in);" and see what happens after that single line executes. Hope this answers your question.

Related

Unit test case for call back methods ios

I have a following method in my app for which I need to write unit test cases.
Can anyone suggest how can I test whether the success block or error block is called.
- (IBAction)loginButtonTapped:(id)sender
{
void (^SuccessBlock)(id, NSDictionary*) = ^(id response, NSDictionary* headers) {
[self someMethod];
};
void (^ErrorBlock)(id, NSDictionary*, id) = ^(NSError* error, NSDictionary* headers, id response) {
// some code
};
[ServiceClass deleteWebService:#“http://someurl"
data:nil
withSuccessBlock:SuccessBlock
withErrorBlock:ErrorBlock];
}
You have to use expectations, a relatively recently introduced API. They were added to solve exactly the problem you are having right now, verifying callbacks of asynchronous methods are called.
Note that you can also set a timeout that will affect the result of the test (slow network connections for example can fire false positives, unless you are checking for slow connections of course, although there are much better ways to do that).
- (void)testThatCallbackIsCalled {
// Given
XCTestExpectation *expectation = [self expectationWithDescription:#"Expecting Callback"];
// When
void (^SuccessBlock)(id, NSDictionary*) = ^(id response, NSDictionary* headers) {
// Then
[self someMethod];
[expectation fulfill]; // This tells the test that your expectation was fulfilled i.e. the callback was called.
};
void (^ErrorBlock)(id, NSDictionary*, id) = ^(NSError* error, NSDictionary* headers, id response) {
// some code
};
[ServiceClass deleteWebService:#“http://someurl"
data:nil
withSuccessBlock:SuccessBlock
withErrorBlock:ErrorBlock];
};
// Here we set the timeout, play around to find what works best for your case to avoid false positives.
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}

Address book change callback registered with ABAddressBookRegisterExternalChangeCallback is never called (iOS 8)

I've found plenty of examples around this but after reading the entire ABAddressBook documentation I'm still not able to figure out why, in my case, my change callback is not being called. I simply set up an address book and register a callback function for it.
I can access the address book just fine, but the callback function is never called no matter how much I change contacts in the Contacts app and then reopen my app. Is there any reason that the callback would never be called? I've already made sure I don't release the address book or unregister the callback.
The init code:
// Set up address book API.
CFErrorRef *error = NULL;
_addressBook = ABAddressBookCreateWithOptions(NULL, error);
if (error) {
NSLog(#"Could not initialize address book: %#", CFBridgingRelease(CFErrorCopyFailureReason(*error)));
} else {
ABAddressBookRegisterExternalChangeCallback(_addressBook, RogerAddressBookChangeCallback, (__bridge void *)self);
NSLog(#"Registered callback");
}
The callback function:
void RogerAddressBookChangeCallback(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
NSLog(#"Address book change");
ABAddressBookRevert(addressBook);
RogerAddressBook *instance = (__bridge RogerAddressBook *)context;
[instance import];
}
I see the log output Registered callback but never Address book change.
Actually the code for ABAddressBook is written in C. So you may find difficulties in using the Original ABAddressBook Framework.
So I suggest for using an third party library (Which is just an makeover of C to Obj-C) to access contacts and the Contacts Change.
Here is the link for an popular library https://github.com/Alterplay/APAddressBook
Using the above framework you could easily observe the changes in Address book.
Observe address book external changes
// start observing
[addressBook startObserveChangesWithCallback:^
{
NSLog(#"Address book changed!");
}];
// stop observing
[addressBook stopObserveChanges];
This library also has lot of options like sorting, filtering etc.
Access to address book requires user authorization. If authorization status is kABAuthorizationStatusNotDetermined, your code fails silently, returning non-nil result and producing no error.
I have next code to create address book:
- (ABAddressBookRef)newAddressBookRef
{
ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
if (authorizationStatus == kABAuthorizationStatusAuthorized)
{
ABAddressBookRef addressBookRef = nil;
CFErrorRef error;
addressBookRef = ABAddressBookCreateWithOptions(NULL, &error);
return addressBookRef;
}
return nil;
}
And next code to explicitly request address book access (usually performed on application start).
typedef void(^AddressBookHelperAccessRequestCompletionHandler)(BOOL accessGiven);
- (void)requestAccessToAddressBook:(ABAddressBookRef)addressBookRef withCompletionHandler:(AddressBookHelperAccessRequestCompletionHandler)completionHandler
{
ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
switch (authorizationStatus)
{
case kABAuthorizationStatusNotDetermined:
{
// Request access permissions for even for NULL address book reference.
// When permissions have not been granted yet, all address book references will be equal to NULL
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error)
{
if (granted)
{
[self registerForAddressBookChanges];
}
if (completionHandler)
{
completionHandler(granted);
}
});
break;
}
case kABAuthorizationStatusDenied:
case kABAuthorizationStatusRestricted:
[self showNoContactsAccessAlert];
default:
{
if (completionHandler)
{
completionHandler(authorizationStatus == kABAuthorizationStatusAuthorized);
}
break;
}
}
}

mailcore2 imap Error Domain=MCOErrorDomain Code=5

I in turn to the making relevant mailcore2 problems, based on past errors, and all of the share, or didn't find the solution to this problem below, so the problem I have encountered redistribution, also hope to get more help, thank you!
Mailcore2 using the code below:
self.imapSession = [[MCOIMAPSession alloc] init];
self.imapSession.hostname = hostname;
self.imapSession.port = 993;
self.imapSession.username = username;
self.imapSession.password = password;
if (oauth2Token != nil) {
self.imapSession.OAuth2Token = oauth2Token;
self.imapSession.authType = MCOAuthTypeXOAuth2;
}
self.imapSession.connectionType = MCOConnectionTypeTLS;
MasterViewController * __weak weakSelf = self;
self.imapSession.connectionLogger = ^(void * connectionID, MCOConnectionLogType type, NSData * data) {
#synchronized(weakSelf) {
if (type != MCOConnectionLogTypeSentPrivate) {
NSLog(#"event logged:%p %i withData: %#", connectionID, type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
}
};
// Reset the inbox
self.messages = nil;
self.totalNumberOfInboxMessages = -1;
self.isLoading = NO;
self.messagePreviews = [NSMutableDictionary dictionary];
[self.tableView reloadData];
NSLog(#"checking account");
self.imapCheckOp = [self.imapSession checkAccountOperation];
[self.imapCheckOp start:^(NSError *error) {
MasterViewController *strongSelf = weakSelf;
NSLog(#"finished checking account.");
if (error == nil) {
[strongSelf loadLastNMessages:NUMBER_OF_MESSAGES_TO_LOAD];
} else {
NSLog(#"error loading account: %#", error);
}
strongSelf.imapCheckOp = nil;
}];
When I use iOS8.1 equipment, error message as follows, the console output:
checking account
2015-02-15 10:58:36.712 MailCoreTest[11971:2988194] event logged:0x166a4d50 0 withData: * OK [CAPABILITY IMAP4 IMAP4rev1 IDLE XAPPLEPUSHSERVICE ID UIDPLUS AUTH=LOGIN NAMESPACE] QQMail IMAP4Server ready
2015-02-15 10:58:36.821 MailCoreTest[11971:2988194] event logged:0x166a4d50 0 withData: (null)
2015-02-15 10:58:36.824 MailCoreTest[11971:2988128] finished checking account.
2015-02-15 10:58:36.825 MailCoreTest[11971:2988128] error loading account: Error Domain=MCOErrorDomain Code=5 "Unable to authenticate with the current session's credentials." UserInfo=0x16596ec0 {NSLocalizedDescription=Unable to authenticate with the current session's credentials.}
But when I use iOS8.0 devices, console to print the results are as follows:
checking account
2015-02-15 11:22:53.249 MailCoreTest[7853:1742121] finished checking account.
2015-02-15 11:22:53.249 MailCoreTest[7853:1742121] error loading account: Error Domain=MCOErrorDomain Code=1 "A stable connection to the server could not be established." UserInfo=0x17e50ab0 {NSLocalizedDescription=A stable connection to the server could not be established.}
Please help me, I contact with Objective - C time is short, but I must do my best to be aggressive, still hope to give a detailed solution, sincere thank you once again, hard work!
Yes its an authentication issue, I had same issue with my app but it was working fine before.
After I got this error, I checked my gmail account and there was an email from Google with subject "Suspicious Sign in prevented" and This sign in was of CA, USA. Actually My Application was under review so it was rejected for this reason.
Then I found that gmail is disabling unauthorised IPs.
You have to enable the 2 step verification for the gmail account to resolve this and generate an app specific password. Use that password in your app and It'll work fine.
Follow below google link to generate application specific password.
https://support.google.com/mail/answer/1173270?hl=en

Why am I not getting an error when sending data through the Game Center without internet?

-(BOOL)sendMessage:(NSMutableDictionary *)_message {
//*** Process _message and convert it to NSData here ***//
NSError *error;
BOOL success = [currentMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (error != nil) {
NSLog(#"Sending data ERROR");
}
return success;
}
I started a match (stored in currentMatch) with another player and continuously sent data using the above method. Then I turned off the wifi.
However, I am not getting the "Sending data ERROR" log message at all.
Why? I turned off the internet connection, so why is there no error here? What could possibly lead to this scenario?
I've also confirmed that the dictionary I am sending is properly encoded into an NSData object, and success is returning YES.
As per the documentation
Return Value
YES if the data was successfully queued for transmission; NO if the match was unable to queue the data.
The method only enqueues the data for transmission, which happens asynchronously.
If you want to monitor the state of the transmission, implement the proper GKMatchDelegate delegate methods, such as match:didFailWithError:.
However, as stated in the documentation:
The match queues the data and transmits it when the network becomes available.
so if you try to perform the method with no network, the transfer just won't happen until the network is back, meaning that you won't see it failing.
By the way you should check the return value, instead of the error, since the error might be nil despite the operation being unsuccessful.
NSError *error;
BOOL success = [currentMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (!success) {
NSLog(#"Sending data ERROR\n%#", error);
}
return success;
You need to check the return value of the method to know whether or not an error occurred. You cannot test the error parameter directly.
if (!success) {
NSLog(#"Sending data ERROR -- %#", error);
}
return success;
As to why you don't get an error, that send method is asynchronous. It simply enqueues the data for transmission and immediately returns. You have to catch the error through some other means (I'm not steeped in GameKit to know what that other means might be).

Convert error codes to text in iOS

I have a wrapper for encrypting and decrypting using CommonCryptor. Occasionally the decryption process will fail, in which case I fill an error like so:
if (result == kCCSuccess) {
cipherData.length = outLength;
} else {
if (error) {
*error = [NSError errorWithDomain:kBridgeEncryptorErrorDomain
code:result
userInfo:nil];
}
return nil;
}
And then I log the error like this:
if (error != nil) {
DDLogError(#"Decrypt fail %i, %#", [error code], [error localizedDescription]);
}
However, this ends up generating strings like:
2013-01-09 09:15:19.753 [BridgeEncrypter decryptDataFromData:] [Line 83] E: Decrypt fail -4304, The operation couldn’t be completed. (com.***.bridgecrypt error -4304.)
Where the -4304 could be any of the error codes in CommonCryptor.h (-4300 to -4305). Is there a good way to map the error codes to their string values, or do I need to have a switch statement that adjusts the string by hand? If I do have to depend on a switch, would best practice be to put it where the issue is logged or where the error is generated?
I'm not sure what you're looking for here. I'm not familiar with CommonCryptor or how error messages are handled in it.
I can recommend that you lean on NSError and it's userInfo and NSLocalized*Key feature.
For example, if you set a NSLocalizedDescriptionKey in the userInfo dictionary, error:
NSDictionary userInfo = #{
NSLocalizedDescriptionKey : #"This is the error message I want users to see"
};
*error = [NSError errorWithDomain:kBridgeEncryptorErrorDomain
code:result
userInfo:userInfo];
Then This is the error message I want users to see is the string returned by -localizedDescription. Then the calling code can use the string to display a message to the user without needing to reinterpret it.
As to the question of how to link error codes to messages you want users to see, there could be a CommonCryptor function that converts error codes to human readable string. If not, then you could write your own. I would recommend using a switch.
NSString *MyCodeToLocalizedDescription(CCCryptorStatus cryptorStatus)
{
switch(cryptorStatus) {
case kCCDecodeError: return #"This is the error message I want users to see";
…
default: return #"Oh noes, unknown error";
}
}
At that point setting the error is:
NSDictionary userInfo = #{
NSLocalizedDescriptionKey : MyCodeToLocalizedDescription(result)
};
*error = [NSError errorWithDomain:kBridgeEncryptorErrorDomain
code:result
userInfo:userInfo];

Resources