ios - returning a value from asynchronous call using semaphores -
i need use nsurlsession
make network calls. on basis of things, after receive response, need return nserror
object.
i using semaphores make asynchronous call behave synchronously. problem is, err set inside call, semaphore ends (after
dispatch_semaphore_wait(semaphore, dispatch_time_forever);
), err
becomes nil.
please help
code:
-(nserror*)loginwithemail:(nsstring*)email password:(nsstring*)password { nserror __block *err = null; // preparing url of login nsurl *url = [nsurl urlwithstring:urlstring]; nsdata *postdata = [post datausingencoding:nsasciistringencoding allowlossyconversion:yes]; // preparing request object nsmutableurlrequest *request = [[nsmutableurlrequest alloc] init]; [request seturl:url]; [request sethttpmethod:@"post"]; [request setvalue:postlength forhttpheaderfield:@"content-length"]; [request sethttpbody:postdata]; nsmutabledictionary __block *parseddata = null; // holds data after parsed dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); nsurlsessionconfiguration *config = [nsurlsessionconfiguration defaultsessionconfiguration]; config.tlsminimumsupportedprotocol = ktlsprotocol11; nsurlsession *session = [nsurlsession sessionwithconfiguration:config delegate:nil delegatequeue:nil]; nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata *data, nsurlresponse *response1, nserror *err){ if(!data) { err = [nserror errorwithdomain:@"connection timeout" code:200 userinfo:nil]; } else { nsstring *formatteddata = [[nsstring alloc] initwithdata:data encoding:nsutf8stringencoding]; nslog(@"%@", formatteddata); if([formatteddata rangeofstring:@"<!doctype"].location != nsnotfound || [formatteddata rangeofstring:@"<html"].location != nsnotfound) { loginsuccessful = no; //*errorr = [nserror errorwithdomain:@"server issue" code:201 userinfo:nil]; err = [nserror errorwithdomain:@"server issue" code:201 userinfo:nil]; } else { parseddata = [nsjsonserialization jsonobjectwithdata:data options:nsjsonreadingallowfragments error:&err]; nsmutabledictionary *dict = [parseddata objectforkey:@"user"]; loginsuccessful = yes; } dispatch_semaphore_signal(semaphore); }]; [task resume]; // have thread wait until task done dispatch_semaphore_wait(semaphore, dispatch_time_forever); return err; }
i suggest cutting gordian knot: should not use semaphores make asynchronous method behave synchronously. adopt asynchronous patterns, e.g. use completion handler:
- (void)loginwithemail:(nsstring *)email password:(nsstring*)password completionhandler:(void (^ __nonnull)(nsdictionary *userdictionary, nserror *error))completionhandler { nsstring *post = ...; // build `post` here, making sure percent-escape userid , password if x-www-form-urlencoded request nsurl *url = [nsurl urlwithstring:urlstring]; nsdata *postdata = [post datausingencoding:nsasciistringencoding allowlossyconversion:yes]; nsmutableurlrequest *request = [nsmutableurlrequest requestwithurl:url]; [request sethttpmethod:@"post"]; // [request setvalue:postlength forhttpheaderfield:@"content-length"]; // not needed set length ... done [request setvalue:@"application/x-www-form-urlencoded" forhttpheaderfield:@"content-type"]; // best practice set `content-type`; use whatever `content-type` appropriate request [request setvalue:@"text/json" forhttpheaderfield:@"accept"]; // , it's best practice inform server of sort of response you'll accept [request sethttpbody:postdata]; nsurlsessionconfiguration *config = [nsurlsessionconfiguration defaultsessionconfiguration]; config.tlsminimumsupportedprotocol = ktlsprotocol11; nsurlsession *session = [nsurlsession sessionwithconfiguration:config delegate:nil delegatequeue:nil]; nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata *data, nsurlresponse *response, nserror *err) { if (!data) { dispatch_async(dispatch_get_main_queue(), ^{ completionhandler(nil, [nserror errorwithdomain:@"connection timeout" code:200 userinfo:nil]); }); } else { nserror *parseerror; nsdictionary *parseddata = [nsjsonserialization jsonobjectwithdata:data options:nsjsonreadingallowfragments error:&parseerror]; dispatch_async(dispatch_get_main_queue(), ^{ if (parseddata) { nsdictionary *dict = parseddata[@"user"]; completionhandler(dict, nil); } else { completionhandler(nil, [nserror errorwithdomain:@"server issue" code:201 userinfo:nil]); } }); } }]; [task resume]; }
and call so:
[self loginwithemail:userid password:password completionhandler:^(nsdictionary *userdictionary, nserror *error) { if (error) { // whatever want on error here } else { // successful, use `userdictionary` here } }]; // don't reliant on successful login here; put inside block above
note:
i know you're going object restoring asynchronous method, it's bad idea make synchronous. first it's horrible ux (the app freeze , user won't know if it's doing or whether it's dead) , if you're on slow network can have sorts of problems (e.g. watchdog process can kill app if @ wrong time).
so, keep asynchronous. ideally, show
uiactivityindicatorview
before starting asynchronous login, , turn off incompletionhandler
.completionhandler
initiate next step in process (e.g.performseguewithidentifier
).i don't bother testing html content; easier attempt parse json , see if succeeds or not. you'll capture broader array of errors way.
personally, wouldn't return own error objects. i'd go ahead , return error objects os gave me. way, if caller had differentiate between different error codes (e.g. no connection vs server error), could.
and if use own error codes, i'd suggest not varying
domain
.domain
should cover whole category of errors (e.g. perhaps 1 custom domain of app's own internal errors), not vary 1 error another. it's not practice usedomain
field error messages. if want more descriptive innserror
object, put text of error message insideuserinfo
dictionary.i might suggest method/variable names conform cocoa naming conventions (e.g. classes start uppercase letter, variables , method names , parameters start lowercase letter).
there's no need set
content-length
(that's done you), practice setcontent-type
,accept
(though not necessary).
Comments
Post a Comment