I a using STHTTPRequest to fetch JSON data in an IOS App. Please see the code below.
When i am testing on Iphone 6 IOS8.0, Sometimes the code is going into error block. Its not happening always , only sometimes. Could anyone help ? Thank u
if(showActivity)
{
[self ShowActivityIndicatorWithTitle:#"Loading..."];
}
STHTTPRequest *request1 = [STHTTPRequest requestWithURLString:[NSString stringWithFormat:#"%#%#",kBaseUrl,api]];
[request1 setTimeoutSeconds:120.0f];
[request1 setHeaderWithName:#"Content-Type" value:#"application/x-www-form-urlencoded"];
[request1 setHTTPMethod:#"POST"];
request1.rawPOSTData = [postData dataUsingEncoding:NSUTF8StringEncoding];
request1.completionDataBlock = ^(NSDictionary *headers, NSData* data)
{
[self HideActivityIndicator];
if(handler != nil)
{
NSError* connectionError;
handler(JSONObjectFromData(data),connectionError);
}
};
request1.errorBlock=^(NSError *error)
{
NSLog(#"Error: %#", [error localizedDescription]);
if(error != nil)
{
[[[UIAlertView alloc] initWithTitle:#"Connection Error !" message:kAlertInternetConnection delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil] show];
[self HideActivityIndicator];
}
};
[request1 startAsynchronous];
I think i found the solution. Basically IOS takes the "Keep Alive" Parameter from first response, and thinks the connection is persistent. When the next JSON call is made, IOS is trying to use existing connection, which is expired. I have added header('Connection: close'); in each PHP web service. I am telling ios in each web service that the connection is closed. I am not sure if it is good way to do it. I have tested for 20 min on device. Its working. I appreciate any thoughts on this.
In PHP I added:
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Content-Length: ' . filesize($file));
readfile($file);
And it worked!
Related
I had a working share routine and now it is broken. Hadn't checked it, or modified it, for some time and now find that it is inoperable. When I call
[sharingService performWithItems:[NSArray arrayWithObject:itemProvider]];
I get a share sheet displayed. It shows the current members of the share. The form is inoperable and will not accept any input or taps. I cannot add, remove or stop sharing altogether. When I close the form, my app is hung up and will not respond or take focus. I have to kill the app and reopen to get it working again.
This used to work fine, within the last few months. I hadn't changed anything so I am very surprised by new problem.
I am adding my code for creating share here:
NSString *shareOption = [[NSUserDefaults standardUserDefaults] objectForKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
if ([shareOption isEqualToString:TTICloudKitShareOwnerService]) {
CDEZipCloudFileSystem *zipFile = (CDEZipCloudFileSystem *)_cloudFileSystem;
CDECloudKitFileSystem *fileSystem = (CDECloudKitFileSystem *)zipFile.cloudFileSystem;
NSItemProvider *itemProvider = [[NSItemProvider alloc] init];
[itemProvider registerCloudKitShare:fileSystem.share container:fileSystem.container];
NSSharingService *sharingService = [NSSharingService sharingServiceNamed:NSSharingServiceNameCloudSharing];
sharingService.subject = #"Share Workforce Data";
sharingService.delegate = self;
if ([sharingService canPerformWithItems:[NSArray arrayWithObject:itemProvider]]) {
[sharingService performWithItems:[NSArray arrayWithObject:itemProvider]];
// This is the point at which the Apple UI is presented but inoperable.
// No changes can be made to the share.
// The only way to dismiss the dialog is to quit or press escape.
// Upon dismissal the app is either crashed or hung up.
// Quitting the app and restart is only option to use the app again.
// If not run from Xcode, requires force quit.
}
} else {
NSLog(#"Is Shared Ensemble");
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:#"Stop Share"];
[alert addButtonWithTitle:#"Cancel"];
[alert setMessageText:#"Shared Data Options"];
[alert setInformativeText:#"You are participating in a shared file. Stop sharing will remove your participation and reset your data. You will no longer participate or have access to the shared information."];
[alert setAlertStyle:NSAlertStyleWarning];
if ([alert runModal] == NSAlertFirstButtonReturn) {
[alert setInformativeText:#"Are you sure? You will no longer have access to shared data. You will need the owner of the share to resend an invitation to join the share."];
if ([alert runModal] == NSAlertFirstButtonReturn) {
// This actually does not remove user from sharing as intended.
// I am sure that is my own implementation incomplete though.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setNilValueForKey:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT];
[defaults setObject:TTICloudKitShareOwnerService forKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
[defaults synchronize];
[self disconnectFromSyncServiceWithCompletion:^{
// TODO: Need to wipe the existing Core Data info here. Leave them with no access to shared data.
// Also need to remove self from the share?
[self reset];
[self setupEnsemble];
}];
}
}
}
Creating share and sending worked flawlessly and I'd been developing app and testing live. Currently my test is shared with two other users and still works. In fact I can't seem to find a way to stop sharing with those users or in any way alter the current share at all.
This is the NSCloudSharingServiceDelegate code:
-(NSCloudKitSharingServiceOptions)optionsForSharingService:(NSSharingService *)cloudKitSharingService shareProvider:(NSItemProvider *)provider
{
return NSCloudKitSharingServiceAllowPrivate | NSCloudKitSharingServiceAllowReadWrite;
}
-(void)sharingService:(NSSharingService *)sharingService willShareItems:(NSArray *)items
{
DLog(#"Will Share Called with items:%#",items);
}
-(void)sharingService:(NSSharingService *)sharingService didShareItems:(NSArray *)items
{
DLog(#"Did share called");
}
-(void)sharingService:(NSSharingService *)sharingService didFailToShareItems:(NSArray *)items error:(NSError *)error
{
DLog(#"Sharing service failed to share items, %#-", error);
if (error.code == NSUserCancelledError) return;
DLog(#"Failed to share, error- %#", error.userInfo);
[self disconnectFromSyncServiceWithCompletion:^{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT forKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
[defaults setNilValueForKey:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT];
[defaults synchronize];
}];
}
It is apparent that I am one of the very few who find this to be important as I have scoured the webs and found almost nobody discussing it. Apple documentation is just about nil.
This is a screenshot of the Apple UI which is not working:
I am posting this as an answer, is more like a work around that I have managed to get working. Still no answer as to why the Apple UI does not respond.
See code for inviting participants without the Apple UI.
-(void)addParticipantWithEmail:(NSString *)email toShare:(CKShare *)share inContainer:(CKContainer *)container
{
[container discoverUserIdentityWithEmailAddress:(email) completionHandler:^(CKUserIdentity * _Nullable userInfo, NSError * _Nullable error) {
if (!userInfo || error) {
NSLog(#"Participant was not found for email %#", email);
if (error) {
NSLog(#"Error: %#", error.userInfo);
} else {
NSLog(#"No error was provided");
}
// abort
return;
}
CKFetchShareMetadataOperation *fetchMetaDataOperation = [[CKFetchShareMetadataOperation alloc] initWithShareURLs:[NSArray arrayWithObject:share.URL]];
fetchMetaDataOperation.shouldFetchRootRecord = YES;
[fetchMetaDataOperation setPerShareMetadataBlock:^(NSURL * _Nonnull shareURL, CKShareMetadata * _Nullable shareMetadata, NSError * _Nullable error) {
CKRecord *root = shareMetadata.rootRecord;
if (!root) {
NSLog(#"There was an error retrieving the root record- %#", error);
} else {
NSLog(#"Root is %#", root);
NSLog(#"/n");
}
CKUserIdentityLookupInfo *info = userInfo.lookupInfo;
CKFetchShareParticipantsOperation *fetchOperation = [[CKFetchShareParticipantsOperation alloc] initWithUserIdentityLookupInfos:[NSArray arrayWithObject:info]];
[fetchOperation setShareParticipantFetchedBlock:^(CKShareParticipant * _Nonnull participant) {
participant.permission = CKShareParticipantPermissionReadWrite;
[share addParticipant:participant];
CKModifyRecordsOperation *modifyOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:[NSArray arrayWithObjects:root, share, nil] recordIDsToDelete:nil];
modifyOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
[modifyOperation setPerRecordCompletionBlock:^(CKRecord * _Nonnull record, NSError * _Nullable error) {
if (error) {
DLog(#"Error modifying record %#. UserInfo: %#", record, error.userInfo);
} else {
DLog(#"No Error Reported in Modify Operation");
}
}];
[container.privateCloudDatabase addOperation:modifyOperation];
}];
[fetchOperation setFetchShareParticipantsCompletionBlock:^(NSError * _Nullable operationError) {
if (operationError) {
NSLog(#"There was en error in the fetch operation- %#", operationError.userInfo);
// Error may be a network issue, should implement a retry and possibly a limit to how many times to run it
}
}];
[container addOperation:fetchOperation];
}];
[container addOperation:fetchMetaDataOperation];
}];
}
It seems now, if I pass an email address to this function they are successfully invited to share, provided the user is in my contacts and has allowed discoverability.
I send the user the link to the share manually via iMessage at this point. Copied the URL from the console. My intent is to provide my own forms to handle that now.
On receiving link, I use Ensembles method:
CDECloudKitFileSystem acceptInvitationToShareWithMetadata:metadata completion:^(NSError *error)
This code didn't seem to work, accepting invites was failing initially. Without having changed anything, the accepting shares started to work. I am not sure why the initial fails.
So i have this method in my app that returns BOOL if and update is available for my apps content
- (BOOL)isUpdateAvailable{
NSData *dataResponse=[NSData dataWithContentsOfURL:[NSURL URLWithString:#"url that returns json object"] ];
if(dataResponse!=nil){
NSError *error;
dicUpdates = [NSJSONSerialization JSONObjectWithData:dataResponse options:NSJSONReadingMutableContainers error:&error];
}
if(dicUpdates.count > 0) isUpdateAvailable = YES;
else isUpdateAvailable = NO;
return isUpdateAvailable;
}
I need a synchronous request for this, cause the next view controller will be dependent on the server response. However sometimes it takes a long time for the server to respond or the internet is really slow, i need to set a time out to prevent the app from 'being frozen'.
I previously used NSUrlconnection to accomplish this task, but it has been deprecated.
Also, I tried using NSURLSession, (been using it also to download updates in the background thread), but i just can figure out if it can be used for a synchronous request.
Any idea how to deal with this? i just need a synchronous method that returns a BOOL. Best regards.
We have to use NSURLRequest in NSURLSession to set timeout interval.
Check below code:
- (BOOL)isUpdateAvailable{
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"url that returns json object"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:4]//timeout
completionHandler:^(NSData *dataResponse,
NSURLResponse *response,
NSError *error) {
// handle response
if(dataResponse!=nil){
NSError *error;
dicUpdates = [NSJSONSerialization JSONObjectWithData:dataResponse options:NSJSONReadingMutableContainers error:&error];
}
if(dicUpdates.count > 0) isUpdateAvailable = YES;
else isUpdateAvailable = NO;
return isUpdateAvailable;
}] resume];
}
I trying to implement Load image asynchronously.
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage *getImg = [[UIImage alloc] initWithData:data];
// do whatever you want with image
}
}];
But when i put this code, getImg will get warning "Unused Variable". i had check "response", "data" and "error", it all look fine but the getImg is NIL. is that i had write any thing wrong? thanks.
The affected variable is response. Although you use data and error, response is only declared as a parameter but nowhere used in your completion handler!
NSURL *url = [NSURL URLWithString:_posterImg];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
NSString *errorMsg = nil;
UIImage *getImg = nil;
if (!error){
getImg = [[UIImage alloc] initWithData:data];
}
else
{
errorMsg = [NSString stringWithFormat:#"Failed to load image. Error Message: %#", [error localizedString]];
}
[self handleImageRequestWithResponse:response image:getImg andErrorMessage:errorMsg];
}];
// Image hasn't load yet here since the request is asynchronously!
//if(getImg != nil && errorMsg == nil)
// NSLog(#"Image is available!");
//else
// NSLog(#"Loading the image asynchronously failed! %#", errorMsg);
// In addition now provide the following method.
- (void) handleImageRequestWithResponse:(NSURLResponse*)response image:(UIImage*)img andErrorMessage:(NSString*)err
{
if(img!= nil && err == nil)
NSLog(#"Image is available!");
else
NSLog(#"Loading the image asynchronously failed! %#", err);
// Handle image
};
EDIT: My bad! Since the code executes asynchronously getImg was of course nil when you checked as before
EDIT:
Using NSData dataWithContentsOfURL is synchronous,i.e. if executed on the main thread your application is blocked.
See this official documentation: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html#//apple_ref/occ/clm/NSData/dataWithContentsOfURL:
Most important:
Important: Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Going for a completionHandler and a handler method called after the requested raw data has been handled/prepared is better for your performance and does not violate the official recommendation!
I have a special URL that returns an image in a browser. I'd like to put this special image into a UIImage object. However, in my code sample below, it seems that the data returned is not an image.
The browser seems to know about the image, how can I get to the image?
Yes, I need get this image via the proxify proxy server to solve an issue we have.
Here is the sample code.
-(UIImage*)retrieveMap
{
NSString* proxifiedGoogleMapURL = #"http://proxify.com/p/011010A1000100/687474703a2f2f6d6170732e676f6f676c652e636f6d2f6d6170732f6170692f7374617469636d61703f63656e7465723d34312e3031343130312c2d3130342e393738333333267a6f6f6d3d362673697a653d35303678353036267363616c653d32266d6170747970653d726f61647326666f726d61743d706e672673656e736f723d66616c7365";
NSURLRequest * firstRequest = [NSURLRequest requestWithURL:[NSURL URLWithString: proxifiedGoogleMapURL]];
NSURLResponse * firstResponse = nil;
NSError * firstError = nil;
NSData* firstData = [NSURLConnection sendSynchronousRequest:firstRequest returningResponse:&firstResponse error:&firstError];
UIImage* dataAsImage = [UIImage imageWithData:firstData];
if(firstError != nil || dataAsImage == nil)
{
NSLog(#"Failed in retrieving the map");
}
else
{
NSLog(#"Succeeded in retrieving the map");
}
return dataAsImage;
}
I've spotted a few problems:
You should test firstError != nil before you try to convert to an UIImage. Testing it after kind of makes it useless.
I've tried to access that URL you posted and I got an error saying it is available only to users logged in. In the browser it might work for you since you are probably logged in, but the iphone is not and you get that HTML from proxify
To see the response you get, before creating the image convert it to NSString and display it to the console:
NSLog(#"Response from server: %#", [[NSString alloc] initWithData:firstData encoding:NSUTF8StringEncoding]
I'm trying to get my Gmail unread email count using Cocoa (Mac) and the PubSub framework. I've seen one or two links showing using PubSub and Gmail, here's my code so far.
PSClient *client = [PSClient applicationClient];
NSURL *url = [NSURL URLWithString:#"https://mail.google.com/mail/feed/atom/inbox"];
PSFeed *feed = [client addFeedWithURL:url];
[feed setLogin: #"myemailhere"];
[feed setPassword: #"mypasswordhere"];
NSLog(#"Error: %#", feed.lastError);
Anyone know how I can get the unread count?
Thanks :)
You have two problems: one which there's a solution for and one which seems to be a perpetual problem.
The first: Feed refreshes happen asynchronously. So you need to listen to the PSFeedRefreshingNotification and PSFeedEntriesChangedNotification notifications to see when the feed gets refreshed and updated. The notification's object will be the PSFeed in question.
As an example:
-(void)feedRefreshing:(NSNotification*)n
{
PSFeed *f = [n object];
NSLog(#"Is Refreshing: %#", [f isRefreshing] ? #"Yes" : #"No");
NSLog(#"Feed: %#", f);
NSLog(#"XML: %#", [f XMLRepresentation]);
NSLog(#"Last Error: %#", [f lastError]);
if(![f isRefreshing])
{
NSInteger emailCount = 0;
NSEnumerator *e = [f entryEnumeratorSortedBy:nil];
id entry = nil;
while(entry = [e nextObject])
{
emailCount++;
NSLog(#"Entry: %#", entry);
}
NSLog(#"Email Count: %ld", emailCount);
}
}
-(void)feedUpdated:(NSNotification*)n
{
NSLog(#"Updated");
}
-(void)pubSubTest
{
PSClient *client = [PSClient applicationClient];
NSURL *url = [NSURL URLWithString:#"https://mail.google.com/mail/feed/atom/inbox"];
PSFeed *feed = [client addFeedWithURL:url];
[feed setLogin: #"correctUserName#gmail.com"];
[feed setPassword: #"correctPassword"];
NSError *error = nil;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(feedUpdated:) name:PSFeedEntriesChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(feedRefreshing:) name:PSFeedRefreshingNotification object:nil];
[feed refresh:&error];
if(error)
NSLog(#"Error: %#", error);
}
The second (and far worse) issue is that PubSub doesn't handle authenticated feeds correctly. I saw this at http://www.dizzey.com/development/fetching-emails-from-gmail-using-cocoa/ and I've reproduced the same behavior on my own system. I don't know if this bug is 10.7 specific or if it affects previous versions of OS X.
The "workaround" is to use NSURLConnection to perform an authenticated retrieval of the raw feed XML. You can then shove that into a PSFeed using its initWithData:URL: method. The very serious drawbacks with this is that you aren't actually PubSubing anymore. You'll have to run a timer and manually refresh the feed whenever appropriate.
The best I was able to do to help you out was to file a bug: rdar://problem/10475065 (OpenRadar: 1430409 ).
You should probably file a duplicate bug to try to increase the chances that Apple will fix it.
Good luck.