I have a class named RestService which I use all over my app to perform several synchronous requests to a web service. I added a new method to this class to perform an asynchronous request which again I want to reuse all over my app. This is the code for that new method:
- (void)backgroundExecutionOfService:(NSString *)serviceName
withParameters:(NSDictionary *)parameters
inView:(UIView *)view
withDelegate:(UIViewController *)delegate
{
NSString *serviceUrl = #"http://MyWebServer/public/api/clients/5";
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.allowsCellularAccess = YES;
sessionConfig.timeoutIntervalForRequest = 10;
sessionConfig.timeoutIntervalForResource = 10;
sessionConfig.HTTPMaximumConnectionsPerHost = 1;
NSURLSession *session;
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:delegate
delegateQueue:nil];
NSURLSessionDownloadTask *getFileTask;
getFileTask = [session downloadTaskWithURL:[NSURL URLWithString:serviceUrl]];
[getFileTask resume];
}
But XCode is giving me a warning about using that parameter as a delegate (Sending UIViewController * __strong' to parameter of incompatible type 'id< NSURLSessionDelegate > _Nullable'). I made sure that the view controller I'm sending as a parameter has declared < NSURLSessionDelegate > in .h and I created the delegate methods in the ViewControllers's implementation file.
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error{
NSLog(#"Became invalid with error: %#", [error localizedDescription]);
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
NSLog(#"Received challenge");
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
NSLog(#"Did finish events for session: %#", session);
}
The app doesn't crash but the delegate methods are never called. The synchronous web services work as expected.
That happened because a UIViewController class don't conform a NSURLSessionDelegate protocol.
To resolve that discrepancy just change the method's signature to this like:
- (void)backgroundExecutionOfService:(NSString *)serviceName withParameters:(NSDictionary *)parameters inView:(UIView *)view withDelegate:(id<NSURLSessionDelegate>)delegate{
//... your code
}
And "read up on the basics of delegates."
Related
I'm creating a task to download an image in the background inside a function and I have a notification object in the same context. I want to retrieve this notification object when the delegate didFinishDownloadingToURL is called. Is there any way I can do this?
I create a task like this:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"imageDownloadSession"];
[sessionConfig setDiscretionary:YES];
[sessionConfig setSessionSendsLaunchEvents:YES];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *getImageTask = [session downloadTaskWithURL:notification.imageRemoteURL];
And this is my delegate:
-(void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
// I want to access the notification object here
}
You can simply store in the notification in a property in the class that you are using as NSURLSessionDownloadDelegate. So when downloadTaskWithURL is called you can access that property.
Alternatively you can create an other class that holds the notification as an instance property and acts as the delegate. An if you have multiple download tasks you can keep them in an array.
For example:
...
self.downloadDelegate = [[SessionDownloadDelegate alloc] init];
downloadDelegate.notification = notification
//Now use the downloadDelegate instead of self
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:downloadDelegate delegateQueue:nil];
...
#interface SessionDownloadDelegate: NSObject<NSURLSessionDownloadDelegate>
#property (strong, nonatomic) NSObject *notification;
#end
#implementation SessionDownloadDelegate
- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
// access the notification object
// self.notification
}
#end
I'm new to block programming with Objective-C, and I searched to try to find an answer to this, so apologies if this is a silly question.
I'm writing a class to wrap Imgur downloads. It's a subclass of NSObject and uses NSURLSession. I'm using a singleton pattern to allow me to easily fire off a download in one line, and get the progress of the image download and the UIImage itself once completed.
- (void)downloadImageWithURL:(NSURL *)URL
progressBlock:(void (^)(CGFloat percentageDownlaoded))progressBlock
completionHandler:(void (^)(UIImage *downloadedImage, NSURLResponse *response, NSError *error))completionHandler;
But I'm confused how I implement this in the class itself. Here's my full class file:
+ (instancetype)sharedClient {
static ImgurClient *sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClient = [[ImgurClient alloc] init];
});
return sharedClient;
}
- (id)init {
self = [super init];
if (self) {
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
}
return self;
}
#pragma mark - External Download Methods
/**
* Asynchronously downloads the image for the given URL.
*/
- (void)downloadImageWithURL:(NSURL *)URL
progressBlock:(void (^)(CGFloat percentageDownlaoded))progressBlock
completionHandler:(void (^)(UIImage *downloadedImage, NSURLResponse *response, NSError *error))completionHandler {
}
/**
* Asynchronously downloads the thumbnail for the given URL at the specified size.
*/
- (void)downloadThumbnailWithID:(NSURL *)URL
size:(CSImgurThumbnailSize)size
progressBlock:(void (^)(CGFloat percentageDownlaoded))progressBlock
completionHandler:(void (^)(UIImage *downloadedImage, NSURLResponse *response, NSError *error))completionHandler {
}
#pragma mark - NSURLSessionDelegate Methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
}
How do I go about structuring this class? Do I have properties representing the progress then throw that into the appropriate methods?
If anyone could shed any light or give a link to an explanation I'd greatly appreciate it.
Here's parts of it in sketch form, I'll concentrate on one block to give you an idea, you'll have to fill in all the other parts yourself otherwise this answer will be huge.
You'll need to add properties to sharedClient to hold the blocks.
It will be much easier if you typedef the types of the blocks first i.e.
typedef void (^PercentageDownloadedBlock)(CGFloat percentageDownlaoded);
Then you can declare your property as
#property (copy, nonatomic) PercentageDownloadedBlock thePercentageBlock;
Then do something like this:
- (void)downloadThumbnailWithID:(NSURL *)URL
size:(CSImgurThumbnailSize)size
progressBlock:(PercentageDownloadedBlock)progressBlock
completionHandler:(completionHandlerBlock)completionHandler
{
self.thePercentageBlock = progressBlock;
start the connection
...
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
CFFloat progress = do calculation of progress
self.thePercentageBlock(progress);
...
}
etc.
I already done that and wrap it in a subclass of NSURLConnection. Hope this help. Check it here:-
https://github.com/hackiftekhar/IQURLConnection
Following blocks have been implemented:-
1) ResponseBlock //NSURLResponse
2) ProgressBlock //Value range from 0.0 to 1.0
3) CompletionBlock //NSData, NSError
Take a look at AFNetworking's AFURLSessionManager and AFHTTPSessionManager. It wraps the NSURLSession APIs in a way similar to what you describe. If you're using CocoaPods it's possible to use AFNetworking's AFURLSession APIs without necessarily importing the rest of the library. Otherwise taking a look at the code is a good way to see how to do what you want.
I am building an app that uses a web service and to get information from that web service I use NSURLSession and NSURLSessionDataTask.
I am now in the memory testing phase and I have found that NSURLSession is causing memory leaks.
This is not all of the leaks. It is all that I could fit in the picture.
Below is how I setup the NSURLSession and request the information from the web service.
#pragma mark - Getter Methods
- (NSURLSessionConfiguration *)sessionConfiguration
{
if (_sessionConfiguration == nil)
{
_sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[_sessionConfiguration setHTTPAdditionalHeaders:#{#"Accept": #"application/json"}];
_sessionConfiguration.timeoutIntervalForRequest = 60.0;
_sessionConfiguration.timeoutIntervalForResource = 120.0;
_sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
}
return _sessionConfiguration;
}
- (NSURLSession *)session
{
if (_session == nil)
{
_session = [NSURLSession
sessionWithConfiguration:self.sessionConfiguration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
#pragma mark -
#pragma mark - Data Task
- (void)photoDataTaskWithRequest:(NSURLRequest *)theRequest
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Photo Request Data Task Set");
#endif
// Remove existing data, if any
if (_photoData)
{
[self setPhotoData:nil];
}
self.photoDataTask = [self.session dataTaskWithRequest:theRequest];
[self.photoDataTask resume];
}
#pragma mark -
#pragma mark - Session
- (void)beginPhotoRequestWithReference:(NSString *)aReference
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Fetching Photo Data...");
#endif
_photoReference = aReference;
NSString * serviceURLString = [[NSString alloc] initWithFormat:#"%#/json?photoreference=%#", PhotoRequestBaseAPIURL, self.photoReference];
NSString * encodedServiceURLString = [serviceURLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
serviceURLString = nil;
NSURL * serviceURL = [[NSURL alloc] initWithString:encodedServiceURLString];
encodedServiceURLString = nil;
NSURLRequest * request = [[NSURLRequest alloc] initWithURL:serviceURL];
[self photoDataTaskWithRequest:request];
serviceURL = nil;
request = nil;
}
- (void)cleanupSession
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Cleaned Up");
#endif
[self setPhotoData:nil];
[self setPhotoDataTask:nil];
[self setSession:nil];
}
- (void)endSessionAndCancelTasks
{
if (_session)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Ended and Tasks Cancelled");
#endif
[self.session invalidateAndCancel];
[self cleanupSession];
}
}
#pragma mark -
#pragma mark - NSURLSession Delegate Methods
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Completed");
#endif
if (error)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Photo Request Fetch: %#", [error description]);
#endif
[self endSessionAndCancelTasks];
switch (error.code)
{
case NSURLErrorTimedOut:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"RequestTimedOut" object:self];
}
break;
case NSURLErrorCancelled:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"RequestCancelled" object:self];
}
break;
case NSURLErrorNotConnectedToInternet:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotConnectedToInternet" object:self];
}
break;
case NSURLErrorNetworkConnectionLost:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"NetworkConnectionLost" object:self];
}
break;
default:
{
}
break;
}
}
else {
if ([task isEqual:_photoDataTask])
{
[self parseData:self.photoData fromTask:task];
}
}
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Invalidation: %#", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self endSessionAndCancelTasks];
}
}
#pragma mark -
#pragma mark - NSURLSessionDataTask Delegate Methods
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Received Data");
#endif
if ([dataTask isEqual:_photoDataTask])
{
[self.photoData appendData:data];
}
}
#pragma mark -
Question:
Why is NSURLSession causing these memory leaks? I am invalidating the NSURLSession when I am finished with it. I am also releasing any properties that I do not need and setting the session to nil (refer to - (void)cleanupSession & - (void) endSessionAndCancelTasks).
Other Information:
The memory leaks occur after the session has completed and "cleaned up". Sometimes, they also occur after I have popped the UIViewController. But, all of my custom (GPPhotoRequest and GPSearch) objects and UIViewController are being dealloced (I've made sure by adding an NSLog).
I tried not to post to much code, but I felt like most of it needed to be seen.
Please let me know if you need any more information. Help is greatly appreciated.
I had this same "leaky", memory management issue when I switched to NSURLSession. For me, after creating a session and resuming/starting a dataTask, I was never telling the session to clean itself up (i.e. release the memory allocated to it).
// Ending my request method with only the following line causes memory leaks
[dataTask resume];
// Adding this line fixed my memory management issues
[session finishTasksAndInvalidate];
From the docs:
After the last task finishes and the session makes the last delegate call, references to the delegate and callback objects are broken.
Cleaning up my sessions fixed the memory leaks being reported via Instruments.
After rereading the URL Loading System Programming Guide it turns that I was setting the NSURLSession property to nil too early.
Instead, I need to set the NSURLSession property to nil AFTER I receive the delegate message URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error, which makes sense. Luckily, it was a minor mistake.
E.g.
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Invalidation: %#", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self cleanupSession];
}
}
Had the same issue. The #Jonathan's answer didn't make a sense - my app still leaked memory. I found out that setting the session property to nil in URLSession:didBecomeInvalidWithError: delegate method is causing the issue. Tried to look deeper into the URL Loading System Programming Guide. It says
After invalidating the session, when all outstanding tasks have been canceled or have finished, the session sends the delegate a URLSession:didBecomeInvalidWithError: message. When that delegate method returns, the session disposes of its strong reference to the delegate.
I left the delegate method blank. But the invalidated session property still have a pointer, when should I set it nil? I just set this property weak
// .h-file
#interface MyClass : NSObject <NSURLSessionDelegate>
{
__weak NSURLSession *_session;
}
// .m-file
- (NSURLSessionTask *)taskForRequest:(NSURLRequest *)request withCompletionHandler:(void(^)(NSData *,NSURLResponse *,NSError *))handler
{
if(!_session)
[self initSession];
//...
}
The app stopped leaking memory.
Please see my answer here: https://stackoverflow.com/a/53428913/4437636
I believe this leak is the same one I was seeing, and only happens when running network traffic through a proxy. My code was fine, but it turned out that an internal bug in the Apple API was causing the leak.
I have an NSURLConnection in a tableview cell subclass that can download most files. I noticed, however, that some fail to start downloading, and time out. An example would be this URL, which is just a test zip file that downloads fine in any other browser. Heres my code for the download
-(void)downloadFileAtURL:(NSURL *)url{
self.downloadedData = [[NSMutableData alloc] init];
self.url = url;
conn = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:self.url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1200.0] delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)response
{
int statusCode = [response statusCode];
if (statusCode == 200){
self.fileName.text = response.URL.lastPathComponent;
self.respo = response;
expectedLength = [response expectedContentLength];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.downloadedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
CFStringRef mimeType = (__bridge CFStringRef)[_respo MIMEType];
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
NSString *fileName = [NSString stringWithFormat:#"%#.%#", [[_respo suggestedFilename] stringByDeletingPathExtension], (__bridge NSString *)extension];
[[NSFileManager defaultManager] createFileAtPath:[[self docsDir] stringByAppendingPathComponent:[NSString stringWithFormat:#"Downloads/%#", fileName]] contents:_downloadedData attributes:nil];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Download failed with error: %#", error);
}
Anybody see anything that might cause this?
Heres the error:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x1fd2c650
{NSErrorFailingURLStringKey=http://download.thinkbroadband.com/10MB.zip,
NSErrorFailingURLKey=http://download.thinkbroadband.com/10MB.zip,
NSLocalizedDescription=The request timed out., NSUnderlyingError=0x1fdc90b0 "The request timed out."}
"I have an NSURLConnection in a tableview cell subclass " - never do this. As Sung-Pil Lim already pointed out correctly, TableView Cells will be reused which may cause this issue.
Anyway, the response data of your connection is a property of the model. The model might encapsulate how it gets to this data. If that data is not immediately available once it will be accessed, it should provide a "placeholder" value instead and start an asynchronous task which retrieves this data.
Suppose a model's property, an image, will be accessed by the view controller in order to be displayed by a view. The model has not yet loaded its actual image - and thus it returns a "placeholder image" in order to let the view display something. But at the same time the model is starting an asynchronous task to load the image. When this connection is finished loading with the data, the model updates internally its property - thereby replacing the placeholder with the real image. The update of the property should be performed on the main thread - since the UIKit views may access the same property as well.
During initialization, the View Controller has registered as an observer of the model's property (see KVO). When the model's property is updated, the controller gets notified. The View Controller then performs appropriate actions so that the view will be redrawn and displays the new updated value.
Your model should have a "cancel" method, which will be send to the model from the controller when the actual value of the model's property is not required anymore. For example, the user switched to another view (see viewWillDisappear).
I tried your codes.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.downloadedData appendData:data];
NSLog(#"%d", data.length);
}
2013-05-04 01:51:13.811 SomethingTodo[2732:c07] 1124
2013-05-04 01:51:13.856 SomethingTodo[2732:c07] 1448
2013-05-04 01:51:14.075 SomethingTodo[2732:c07] 1448
2013-05-04 01:51:17.180 SomethingTodo[2732:c07] 1448
2013-05-04 01:51:17.295 SomethingTodo[2732:c07] 1448
It's working... on ViewController
'request timeout error' was brought to network connection. or...
Are you resuing UITableViewCell? If you initialize for cell reuse codes deal with connection. maybe bring to trouble. Just i thought.
If you attach more your codes. Could I help you more then this.
I would start with a clean slate and just use basic code to work the download. Load in lots of NSLog(s) to track everything. If that works, keep adding your custom code and see if you stumble across an error. I suggest basic NSURLConnection code:
-(void)startDownloading:(NSString *)URLaddress{
NSLog(#"start downloading from: %#",URLaddress);
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:[URLaddress stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
__unused NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"didReceiveResponse: %#", response);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(#"didReceiveData");
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Connection failed! Error - %# %#",[error localizedDescription], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"connectionDidFinishLoading");
}
try with HCDownloadViewController and you can check which url is not downloaded. and next time sync for that particular url which is not downloaded.
.h file
#import "HCDownloadViewController.h"
#interface HomeViewController_iPhone : UIViewController<HCDownloadViewControllerDelegate>
{
HCDownloadViewController *tblDownloadHairStyle;
}
#property (nonatomic,retain) HCDownloadViewController *tblDownloadHairStyle;
.m file
#define kAppDirectoryPath NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
#synthesize tblDownloadHairStyle
- (void)viewDidLoad
{
[super viewDidLoad];
tblDownloadHairStyle=[[HCDownloadViewController alloc] init];
tblDownloadHairStyle.delegate=self;
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)response
{
[self createDocumentDirectory:#"Downloaded_HairStyle"];
NSString *pathHair=[self getDocumentDirectoryPath:#"Downloaded_HairStyle"];
tblDownloadHairStyle.downloadDirectory = pathHair;
////You can put url in for loop, it create queue for downloading.
[tblDownloadHairStyle downloadURL:[NSURL URLWithString:#"yourUrl"] userInfo:YourResponseDictonary];
}
-(void)createDocumentDirectory:(NSString*)pStrDirectoryName
{
NSString *dataPath = [self getDocumentDirectoryPath:pStrDirectoryName];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:NULL];
}
-(NSString*)getDocumentDirectoryPath:(NSString*)pStrPathName
{
NSString *strPath = #"";
if(pStrPathName)
strPath = [[kAppDirectoryPath objectAtIndex:0] stringByAppendingPathComponent:pStrPathName];
return strPath;
}
#pragma mark-
#pragma mark-HCDownloadViewController Delegate Method
- (void)downloadController:(HCDownloadViewController *)vc startedDownloadingURL:(NSURL *)url userInfo:(NSDictionary *)userInfo {
}
- (void)downloadController:(HCDownloadViewController *)vc finishedDownloadingURL:(NSURL *)url toFile:(NSString *)fileName userInfo:(NSDictionary *)userInfo {
if (vc==tblDownloadHairStyle) {
if ([tblDownloadHairStyle numberOfDownloads]==0) {
NSLog(#"AllDownLoad are complete");
}
}
}
- (void)downloadController:(HCDownloadViewController *)vc failedDownloadingURL:(NSURL *)url withError:(NSError *)error userInfo:(NSDictionary *)userInfo {
NSLog(#"failedDownloadingURL=%#",url);
}
https://github.com/H2CO3/HCDownload
accept any response with http response code range 200-299 and disable caching on the http-connector.
double check your url address conforms to RFC 2396. so it must include HTTP://
Do you have any libraries (TestFlight, UA, etc) in the project? Try removing them and re-test. We had an app that used NSUrlConnection with TestFlight SDK that caused all sorts of sporadic network problems.
NSURLConnection timing out
ASIHTTPRequest request times out
https://github.com/AFNetworking/AFNetworking/issues/307
This question already has answers here:
Problem understanding Delegating pattern & callback functions in Objective C
(3 answers)
Closed 9 years ago.
I work on iOS platform , I want to know what is a delegate function and what is a callback function ? what is the difference between the two types of function or they are the same ??
example of delegate function is numberOfRowsInSection in UITableViewDelegate protocol and example of callback function is didReceiveLocalNotification in appDelegate.m
Can we create our own callback function in Objective-C , if YES ,give an example ...
Thank you..
A couple of thoughts:
You suggest that didReceiveLocationNotification was a "callback function", but it's actually just a delegate method of the UIApplicationDelegate protocol. So, both numberOfRowsInSection and didReceiveLocalNotification are simply delegate methods.
Something more akin to a generic callback function would be the selector when scheduling a NSTimer or defining the handler for a UIGestureRecognizer, where the choice of method name is not predetermined.
Or callbacks are used extensively in CFArray.
But, the root of your question is less on the terminology, but rather a question of how to define an interface where the caller can specify a method that some other object will invoke (asynchronously) at some future date. There are a couple of common patterns:
Block parameter to method: It is increasingly common to define methods that take a block as a parameter. For example, you can have a method that is defined as follows:
- (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename completion:(void (^)(NSData *results, NSString *filename))completion {
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, filename);
});
}];
[task resume];
return task;
}
That third parameter, completion, is a block of code that will be called with the download is done. Thus, you can invoke that method as follows:
[self downloadAsynchronously:url filename:filename completion:^(NSData *results, NSString *filename) {
NSLog(#"Downloaded %d bytes", [results length]);
[results writeToFile:filename atomically:YES];
}];
NSLog(#"%s done", __FUNCTION__);
You'll see that "done" message appear immediately, and that completion block will be called when the download is done. It admittedly takes a while to get used to the ungainly mess of punctuation that constitutes a block variable/parameter definition, but once you're familiar with the block syntax, you'll really appreciate this pattern. It eliminates the disconnect between the invoking of some method and the defining of some separate callback function.
If you want to simplify the syntax of dealing with blocks as parameters, you can actually define a typedef for your completion block:
typedef void (^DownloadCompletionBlock)(NSData *results, NSString *filename);
And then the method declaration, itself, is simplified:
- (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename completion:(DownloadCompletionBlock)completion {
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, filename);
});
}];
[task resume];
return task;
}
Delegate-protocol pattern: The other common technique for communicating between objects is the delegate-protocol pattern. First, you define the protocol (the nature of the "callback" interface):
#protocol DownloadDelegate <NSObject>
- (NSURLSessionTask *)didFinishedDownload:(NSData *)data filename:(NSString *)filename;
#end
Then, you define your class that will be invoking this DownloadDelegate method:
#interface Downloader : NSObject
#property (nonatomic, weak) id<DownloadDelegate> delegate;
- (instancetype)initWithDelegate:(id<DownloadDelegate>)delegate;
- (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename;
#end
#implementation Downloader
- (instancetype)initWithDelegate:(id<DownloadDelegate>)delegate {
self = [super init];
if (self) {
_delegate = delegate;
}
return self;
}
- (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename {
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate didFinishedDownload:data filename:filename];
});
}];
[task resume];
return task;
}
#end
And finally, the original view controller which uses this new Downloader class must conform to the DownloadDelegate protocol:
#interface ViewController () <DownloadDelegate>
#end
And define the protocol method:
- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
NSLog(#"Downloaded %d bytes", [data length]);
[data writeToFile:filename atomically:YES];
}
And perform the download:
Downloader *downloader = [[Downloader alloc] initWithDelegate:self];
[downloader downloadAsynchronously:url filename:filename];
NSLog(#"%s done", __FUNCTION__);
Selector pattern: A pattern that you see in some Cocoa objects (e.g. NSTimer, UIPanGestureRecognizer) is the notion of passing a selector as a parameter. For example, we could have defined our downloader method as follows:
- (NSURLSessionTask *)downloadAsynchronously:(NSURL *)url filename:(NSString *)filename target:(id)target selector:(SEL)selector {
id __weak weakTarget = target; // so that the dispatch_async won't retain the selector
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[weakTarget performSelector:selector
withObject:data
withObject:filename];
#pragma clang diagnostic pop
});
}];
[task resume];
return task;
}
You'd then invoke that as follows:
[self downloadAsynchronously:url
filename:filename
target:self
selector:#selector(didFinishedDownload:filename:)];
But you also have to define that separate method that will be called when the download is done:
- (void)didFinishedDownload:(NSData *)data filename:(NSString *)filename {
NSLog(#"Downloaded %d bytes", [data length]);
[data writeToFile:filename atomically:YES];
}
Personally, I find this pattern to be far too fragile and dependent upon coordinating interfaces without any assistance from the compiler. But I include it for a bit of historical reference given that this pattern is used quite a bit in Cocoa's older classes.
Notifications: The other mechanism to provide the results of some asynchronous method is to send a local notification. This is generally most useful when either (a) the potential recipient of the results of the network request is unknown at the time the request was initiated; or (b) there may be multiple classes that want to be informed of this event. So the network request can post a notification of a particular name when it's done, and any object that is interested in being informed of this event can add themselves as an observer for that local notification through the NSNotificationCenter.
This is not a "callback" per se, but does represent another pattern for an object to be informed of the completion of some asynchronous task.
Those are a few examples of "callback" patterns. Clearly, the example provided was arbitrary and trivial, but hopefully it should give you an idea of your alternatives. The two most common techniques, nowadays, are blocks and delegate patterns. Blocks are increasingly being preferred when a simple and elegant interface is needed. But for rich and complicated interfaces, delegates are very common.