I am developing a framework which requires error callbacks. I have used delegate methods to give error callbacks to the users(developer) of the framework. Can I use NSError to indicate error details in the delegate method or simply use NSDictionary instead ? Are there any memory issues concerned here?
Instead of delegate methods, you should use blocks with proper error handling, something like this:
Interface:
typedef void(^ResultBlock)(id result, NSError *error);
extern NSString * const DummyErrorDomain;
enum{
DummyErrorReason1,
DummyErrorReason2
};
#interface Dummy : NSObject
-(void)doSomething:(ResultBlock)resultBlock;
#end
Implementation with async NSURLConnection:
NSString * const DummyErrorDomain = #"DummyErrorDomain";
#interface Dummy ()
#property (strong) ResultBlock resultBlock;
#property NSURLConnection *connection;
#property NSMutableData *responseData;
#end
#implementation Dummy
-(void)doSomething:(ResultBlock)resultBlock
{
self.resultBlock = resultBlock;
self.responseData = [[NSMutableData alloc] init];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"dummyURL"]];
self.connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
self.resultBlock(nil,error);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
self.resultBlock(self.responseData, nil);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.responseData appendData:data];
}
#end
Usage:
-(void)usage
{
Dummy *dummy = [[Dummy alloc] init];
[dummy doSomething:^(id result, NSError *error) {
if(result){
// everything went fine
}else{
// error handling
}
}];
}
Related
So, I'm following this answer in trying to pass data between two classes in an iOS project without storyboards. I'm trying to abstract all my server calls in a different class, so the ViewControllers don't get too cramped. I have an OfferRequest class, which handles the server requests and declares the protocol:
#protocol OfferRequestDelegate <NSObject>
-(void)addOfferRequest: (OfferRequest *)request dataFromRequest: (NSMutableArray *)data;
#end
#interface OfferRequest : NSObject <LocationManagerDelegate, NSURLConnectionDataDelegate>
#property (nonatomic, weak) id <OfferRequestDelegate> delegate;
#end
The idea is to call addOfferRequest: dataFromRequest in connectionDidFinishLoading, set the data as the result from the request and pass it on to the ViewController.
In OfferRequest.m:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSMutableArray *result = [NSJSONSerialization JSONObjectWithData:receivedData options:kNilOptions error:nil];
[self.delegate addOfferRequest:self dataFromRequest:result];
}
I declare my ViewController as a delegate for the OfferRequest:
#interface CategorySuggestionViewController : UIViewController <OfferRequestDelegate>
Initialize the OfferRequest object and set it's delegate:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
offer = [OfferRequest new];
offer.delegate = self;
}
And then implement the delegate method:
-(void)addOfferRequest:(OfferRequest *)request dataFromRequest:(NSMutableArray *)data {
NSLog(#"received data = %#", data);
}
But it never gets called. What am I missing ?
try this...
your OfferRequest class like below i have given name it as connectionClass
connectionClass.h
#protocol connectionDelegate <NSObject>
-(void)connectionSucceed:(NSData *)data;
-(void)connectionFailed:(NSError *)error;
#end
#import <Foundation/Foundation.h>
#interface connectionClass : NSObject
{
id<connectionDelegate> delegate;
NSMutableData *receivedData;
NSString *encodedString;
NSMutableURLRequest *theRequest;
NSURL *url;
NSString *length;
NSURLConnection *Connect;
}
#property(nonatomic, retain) id<connectionDelegate> delegate;
#property(nonatomic, retain) NSMutableData *receivedData;
- (void)connectionWithURL:(NSString *)URL;
#end
connectionClass.m
#import "connectionClass.h"
#implementation connectionClass
#synthesize delegate, receivedData;
- (void)connectionWithURL:(NSString *)URL
{
url=[[NSURL alloc] initWithString:[self urlencode:URL]];
theRequest= [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60];
[theRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
length = [NSString stringWithFormat:#"%lu", (unsigned long)[URL length]];
[theRequest setValue:length forHTTPHeaderField:#"Content-Length"];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:[URL dataUsingEncoding:NSUTF8StringEncoding]];
Connect = [NSURLConnection connectionWithRequest:theRequest delegate:self];
if (Connect)
{
self.receivedData = [NSMutableData data];
}
else
{
NSError *error;
[self.delegate connectionFailed:error];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.delegate connectionSucceed:self.receivedData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self.delegate connectionFailed:error];
}
-(NSString *) urlencode: (NSString *) string
{
encodedString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)string, NULL, (CFStringRef)#"", kCFStringEncodingUTF16));
return encodedString;
}
#end
don't forget to import your connectionclass and delegate
#interface yourclass : UIViewController<connectionDelegate>
Initialize the OfferRequest object and set it's delegate:
-(void)viewWillAppear:(BOOL)animated
{
connection = [[connectionClass alloc]init];
connection.delegate = self;
[connection connectionWithURL:[NSString stringWithFormat:#"pass your url here"]];
}
The delegate method will have your values
-(void)connectionSucceed:(NSData *)data
{
NSError *error;
self.dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
//nslog your data
}
//Connection failure
-(void)connectionFailed:(NSError *)error
{
//handle error message
}
Ok, I don't know where the problem exactly was, but what I did to fix it, was to create a new init method, where I pass the delegate:
-(instancetype)initWithDelegate: (id<OfferRequestDelegate>) delegate {
self = [super init];
if (self) {
self.delegate = delegate;
}
return self;
}
So I initialize the OfferRequest object in the VC and pass self and now it works.
I guess delegate should be a pointer object and not the object.
It may be help you
#property (nonatomic, weak) NSObject <OfferRequestDelegate> *delegate;
and make should your delegate is been set, i.e. make sure your code below is getting called.
[self.delegate addOfferRequest:self dataFromRequest:result];
I have an iOS project with ARC enabled and i'm using Leaks instrument to find leaks.
i have one leak in this line of code and i don't understand why it's a leak :
[self.activeDownload appendData:data];
this is the code of the class
#interface IconDownloader ()
#property (nonatomic, strong) NSMutableData *activeDownload;
#property (nonatomic, strong) NSURLConnection *imageConnection;
#end
#implementation IconDownloader
#synthesize url = _url;
#synthesize activeDownload = _activeDownload;
#synthesize imageConnection = _imageConnection;
- (void)startDownload
{
self.activeDownload = [NSMutableData data];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.url]];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
self.imageConnection = conn;
}
- (void)cancelDownload
{
[self.imageConnection cancel];
self.imageConnection = nil;
self.activeDownload = nil;
self.completionHandler = nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.activeDownload appendData:data]; // leak
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.activeDownload = nil;
self.imageConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.completionHandler) {
self.completionHandler(self.activeDownload);
}
self.imageConnection = nil;
}
So I have some code like so:
#interface RequestHandler()
#property (nonatomic) NSInteger statusCode;
#end
#implementation RequestHandler
- (bool)sendRequest:(NSString *)surveyorId withData:(NSData *)requestData
{
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:true];
if (self.statusCode == 200)
{
return YES;
}
return NO;
}
Clearly the routine will carry on into the if-else statement before the request has finished. Therefore, self.statusCode is not set properly in the delegate didReceiveResponse before it is checked. What would be the best way of doing this?
I am just thinking of adding another bool property that will be set in connectionDidFinishLoading and then loop until this property is set. Once it has done that, then it will check self.statusCode. However I am thinking this will block the thread will it not? It will be no different from a sendSynchronousRequest right? Is there any way to do this without putting it into a background thread?
Instead of your sendRequest:withData: method returning a BOOL indicating success/failure, it would be better for your RequestHandler to have a delegate. It could then let its delegate know about the success/failure/whatever else when the asynchronous request has finished, instead of trying to return this information from the sendRequest:withData: method (which, as you've found out, doesn't work so well).
So, you could define you delegate protocol something like this (just as an example - you might want to include some more information in these):
#protocol RequestHandlerDelegate <NSObject>
- (void)requestHandlerSuccessfullyCompletedRequest:(RequestHandler *)sender;
- (void)requestHandlerFailedToCompletedRequest:(RequestHandler *)sender;
#end
Then, give your RequestHandler a delegate property of something that conforms to this protocol:
#property (nonatomic, weak) id<RequestHandlerDelegate> delegate;
(Make sure you set something as the delegate!)
Then, when your asynchronous request completes, you can send your delegate the appropriate message, e.g.:
[self.delegate requestHandlerSuccessfullyCompletedRequest:self];
You'll need to implement the NSURLConnection delegate methods in RequestHandler (from your code, I assume you've already done that), or, if your are targeting iOS 7+, you could take a look at NSURLSession instead.
You have to implement 2 delegate methods:
Status code: - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
Received data: - (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
Example usage:
Declaration
#interface RequestHandler : NSObject <NSURLConnectionDelegate>
{
NSMutableData *receivedData;
}
Request
- (void)sendRequest:(NSString *)surveyorId withData:(NSData *)requestData
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
// Apply params in http body
if (requestData) {
[request setHTTPBody:requestData];
}
[request setURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
Delegates
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *responseCode = (NSHTTPURLResponse *)response;
if ([self.delegate respondsToSelector:#selector(didReceiveResponseCode:)]) {
[self.delegate didReceiveResponseCode:responseCode];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
receivedData = [[NSMutableData alloc] initWithData:data];
if ([self.delegate respondsToSelector:#selector(connectionSucceedWithData:)]) {
[self.delegate connectionSucceedWithData:receivedData];
}
}
Instead of using NSURLConnection with delegate methods you can use NSURLConnection sendAsynchronousRequest block in your code. In the example you can check connection error and compare status codes.
NSURL *URL = [NSURL URLWithString:#"http://yourURLHere.com"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *rspreportStatus, NSData *datareportStatus, NSError *e)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)rspreportStatus;
int code = [httpResponse statusCode];
if (e == nil && code == 200)
{
// SUCCESS
} else {
// NOT SUCCESS
}
}];
You can also check by logging this returnString.
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSArray *arrpicResult = [returnString JSONValue];
I'm writing simple code which is sending me response from http server after sending a POST request to it but if I try moving everything to another file XCode shows me this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'data parameter is nil'
Here are the two files I'm using to make this work:
#import "HTTPRequestHandler.h"
#interface HTTPRequestHandler ()
#property (strong, nonatomic) NSURL *requestURL;
#property (strong, nonatomic) NSData *httpBody;
#property (strong, nonatomic) NSMutableData *responseData;
#end
#implementation HTTPRequestHandler
- (id)initWithURL:(NSString *)url body:(NSString *)body
{
if (self = [super init]) {
self.requestURL = [[NSURL alloc] initWithString:url];
self.httpBody = [body dataUsingEncoding:NSUTF8StringEncoding];
}
return self;
}
- (void)makeConnectionWithRequest
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.requestURL];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:self.httpBody];
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Dane doszły 1");
[self.responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Dane doszły 2");
[self.responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Dane doszły 3");
NSDictionary *recievedData = [NSJSONSerialization JSONObjectWithData:self.responseData options:kNilOptions error:nil];
NSLog(#"%#", [recievedData description]);
}
#end
and the second one:
#import "ViewController.h"
#import "HTTPRequestHandler.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
HTTPRequestHandler *request = [[HTTPRequestHandler alloc] initWithURL:#"http://own-dev1.railwaymen.org:4006/api/get_grant_key"
body:#"email=vergun#gmail.com&password=password"];
[request makeConnectionWithRequest];
}
#end
Okey, got it! Just forgot self.responseData = [NSMutableData data];
I'm trying to use googletts converted and seem to be running into trouble. I think I made the class correctly in terms of structure. But I dont think the NSURLConnection is returning any useful data. Can someone please help me check if its working and if not why?
In the connectiondidfinishloading section I am checking download length
NSLog(#"hmmmm %lu", (unsigned long)[downloadedData length])
which is returning 0. Thats why I think its not working.
The goal is to have the app speak what is downloaded
Thanks!!!
I just have a button in my view controller that launches everything
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "RJGoogleTTS.h"
#interface ViewController : UIViewController <CLLocationManagerDelegate>
#property (weak, nonatomic) IBOutlet UILabel *userLabel;
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) CLGeocoder *geoCoder;
#property (strong, nonatomic) RJGoogleTTS *googleTTS;
- (IBAction)geoCodeLocation:(id)sender;
- (IBAction)rjGoogleButton:(id)sender;
#end
here is the implementation file
- (IBAction)rjGoogleButton:(id)sender {
googleTTS = [[RJGoogleTTS alloc]init];
[googleTTS convertTextToSpeech:#"How are you today user?"];
}
RJGoogleTTS.h
#import <Foundation/Foundation.h>
#protocol RJGoogleTTSDelegate <NSObject>
#required
- (void)receivedAudio:(NSMutableData *)data;
- (void)sentAudioRequest;
#end
#interface RJGoogleTTS : NSObject {
id <RJGoogleTTSDelegate> delegate;
NSMutableData *downloadedData;
}
#property (nonatomic, retain) id <RJGoogleTTSDelegate> delegate;
#property (nonatomic, retain) NSMutableData *downloadedData;
- (void)convertTextToSpeech:(NSString *)searchString;
#end
.m
#import "RJGoogleTTS.h"
#import <AVFoundation/AVFoundation.h>
#implementation RJGoogleTTS
#synthesize delegate, downloadedData;
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
- (void)convertTextToSpeech:(NSString *)searchString {
NSString *search = [NSString stringWithFormat:#"http://translate.google.com/translate_tts?q=%#", searchString];
search = [search stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
NSLog(#"Search: %#", search);
self.downloadedData = [[NSMutableData alloc] initWithLength:0];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:search]];
[request setValue:#"Mozilla/5.0" forHTTPHeaderField:#"User-Agent"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
[delegate sentAudioRequest];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"did you receive response user");
[self.downloadedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"did you receive data user");
[self.downloadedData appendData:data];
// AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]initWithData:downloadedData error:nil];
// [audioPlayer play];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"Failure");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"it worked user!!!");
[delegate receivedAudio:self.downloadedData];
NSLog(#"hmmmm %d", [self.downloadedData length]);
// NSString *txt = [[NSString alloc] initWithData:downloadedData encoding: NSASCIIStringEncoding];
// NSLog(#"%#hello",txt);
}
#end
Remember that this is happening asynchronously. You set the size of downloadedData to 0 after your call the NSURLConnection.
Move:
self.downloadedData = [[NSMutableData alloc] initWithLength:0];
before your call to NSURLConnection.
There's nothing wrong with your NSURLConnection methods. Your search string is wrong. It should be:
NSString *search = [NSString stringWithFormat:#"http://translate.google.com/translate_tts?tl=en&q=%#",searchString];
You didn't start the URLConnection. Change [[NSURLConnection alloc] initWithRequest:request delegate:self]; like this [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];