diff --git a/mobile/.swiftlint.yml b/mobile/.swiftlint.yml index 0c87726dd06a..4cc0cf904095 100644 --- a/mobile/.swiftlint.yml +++ b/mobile/.swiftlint.yml @@ -39,7 +39,6 @@ whitelist_rules: - force_unwrapping - generic_type_name - identical_operands - - identifier_name - implicit_getter - inert_defer - is_disjoint diff --git a/mobile/examples/objective-c/hello_world/ViewController.mm b/mobile/examples/objective-c/hello_world/ViewController.mm index 9ae7d4b79533..e6c755b18917 100644 --- a/mobile/examples/objective-c/hello_world/ViewController.mm +++ b/mobile/examples/objective-c/hello_world/ViewController.mm @@ -3,25 +3,29 @@ #pragma mark - Constants -NSString* _CELL_ID = @"cell-id"; -NSString* _ENDPOINT = @"http://localhost:9001/api.lyft.com/static/demo/hello_world.txt"; +NSString *_CELL_ID = @"cell-id"; +NSString *_ENDPOINT = @"http://localhost:9001/api.lyft.com/static/demo/hello_world.txt"; -#pragma mark - ResponseValue +#pragma mark - Result -/// Represents a response from the server. -@interface ResponseValue : NSObject -@property (nonatomic, strong) NSString* body; -@property (nonatomic, strong) NSString* serverHeader; +/// Represents a response from the server or an error. +@interface Result : NSObject +@property (nonatomic, assign) int identifier; +@property (nonatomic, strong) NSString *body; +@property (nonatomic, strong) NSString *serverHeader; +@property (nonatomic, strong) NSString *error; @end -@implementation ResponseValue +@implementation Result @end #pragma mark - ViewController @interface ViewController () -@property (nonatomic, strong) NSMutableArray* responses; -@property (nonatomic, weak) NSTimer* requestTimer; +@property (nonatomic, strong) NSURLSession *session; +@property (nonatomic, assign) int requestCount; +@property (nonatomic, strong) NSMutableArray *results; +@property (nonatomic, weak) NSTimer *requestTimer; @end @implementation ViewController @@ -31,8 +35,14 @@ @implementation ViewController - (instancetype)init { self = [super init]; if (self) { - self.responses = [NSMutableArray new]; - self.tableView.allowsSelection = FALSE; + NSURLSessionConfiguration *configuration = + [NSURLSessionConfiguration defaultSessionConfiguration]; + configuration.URLCache = nil; + configuration.timeoutIntervalForRequest = 10; + self.session = [NSURLSession sessionWithConfiguration:configuration]; + + self.results = [NSMutableArray new]; + self.tableView.allowsSelection = NO; } return self; } @@ -59,66 +69,109 @@ - (void)startRequests { } - (void)performRequest { - NSURLSession* session = [NSURLSession sharedSession]; // Note that the request is sent to the envoy thread listening locally on port 9001. - NSURL* url = [NSURL URLWithString:_ENDPOINT]; - NSURLRequest* request = [NSURLRequest requestWithURL:url]; + NSURL *url = [NSURL URLWithString:_ENDPOINT]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSLog(@"Starting request to '%@'", url.path); - __weak ViewController* weakSelf = self; - NSURLSessionDataTask* task = - [session dataTaskWithRequest:request - completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { - if (error == nil && [(NSHTTPURLResponse*)response statusCode] == 200) { - [weakSelf handleResponse:(NSHTTPURLResponse*)response data:data]; - } else { - NSLog(@"Received error: %@", error); - } - }]; + self.requestCount++; + int requestID = self.requestCount; + + __weak ViewController *weakSelf = self; + NSURLSessionDataTask *task = + [self.session dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf handleResponse:(NSHTTPURLResponse *)response + data:data + identifier:requestID + error:error]; + }); + }]; [task resume]; } -- (void)handleResponse:(NSHTTPURLResponse*)response data:(NSData*)data { - NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (body == nil || response == nil) { - NSLog(@"Failed to deserialize response string"); +- (void)handleResponse:(NSHTTPURLResponse *)response + data:(NSData *)data + identifier:(int)identifier + error:(NSError *)error { + if (error != nil) { + NSLog(@"Received error: %@", error); + [self addResponseBody:nil serverHeader:nil identifier:identifier error:error.description]; return; } - // Deserialize the response, which will include a `Server` header set by Envoy. - ResponseValue* value = [ResponseValue new]; - value.body = body; - value.serverHeader = [[response allHeaderFields] valueForKey:@"Server"]; + if (response.statusCode != 200) { + NSString *message = [NSString stringWithFormat:@"failed with status: %ld", response.statusCode]; + [self addResponseBody:nil serverHeader:nil identifier:identifier error:message]; + return; + } + + NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (body == nil) { + NSString *message = @"failed to deserialize body"; + [self addResponseBody:nil serverHeader:nil identifier:identifier error:message]; + return; + } + // Deserialize the response, which will include a `Server` header set by Envoy. + NSString *serverHeader = [[response allHeaderFields] valueForKey:@"Server"]; NSLog(@"Response:\n%ld bytes\n%@\n%@", data.length, body, [response allHeaderFields]); - dispatch_async(dispatch_get_main_queue(), ^{ - [self.responses addObject:value]; - [self.tableView reloadData]; - }); + [self addResponseBody:body serverHeader:serverHeader identifier:identifier error:nil]; +} + +- (void)addResponseBody:(NSString *)body + serverHeader:(NSString *)serverHeader + identifier:(int)identifier + error:(NSString *)error { + Result *result = [Result new]; + result.identifier = identifier; + result.body = body; + result.serverHeader = serverHeader; + result.error = error; + + [self.results insertObject:result atIndex:0]; + [self.tableView reloadData]; } #pragma mark - UITableView -- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView { +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } -- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { - return self.responses.count; +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.results.count; } -- (UITableViewCell*)tableView:(UITableView*)tableView - cellForRowAtIndexPath:(NSIndexPath*)indexPath { - UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:_CELL_ID]; +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_CELL_ID]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_CELL_ID]; } - ResponseValue* response = self.responses[indexPath.row]; - cell.textLabel.text = [NSString stringWithFormat:@"Response: %@", response.body]; - cell.detailTextLabel.text = - [NSString stringWithFormat:@"'Server' header: %@", response.serverHeader]; + Result *result = self.results[indexPath.row]; + if (result.error == nil) { + cell.textLabel.text = + [NSString stringWithFormat:@"[%d] %@", result.identifier, result.body]; + cell.detailTextLabel.text = + [NSString stringWithFormat:@"'Server' header: %@", result.serverHeader]; + + cell.textLabel.textColor = [UIColor blackColor]; + cell.detailTextLabel.textColor = [UIColor blackColor]; + cell.contentView.backgroundColor = [UIColor whiteColor]; + } else { + cell.textLabel.text = + [NSString stringWithFormat:@"[%d]", result.identifier]; + cell.detailTextLabel.text = result.error; + + cell.textLabel.textColor = [UIColor whiteColor]; + cell.detailTextLabel.textColor = [UIColor whiteColor]; + cell.contentView.backgroundColor = [UIColor redColor]; + } + return cell; } diff --git a/mobile/examples/swift/hello_world/Response.swift b/mobile/examples/swift/hello_world/Response.swift deleted file mode 100644 index a03924003f17..000000000000 --- a/mobile/examples/swift/hello_world/Response.swift +++ /dev/null @@ -1,5 +0,0 @@ -/// Represents a response from the server. -struct Response { - let body: String - let serverHeader: String -} diff --git a/mobile/examples/swift/hello_world/ResponseModels.swift b/mobile/examples/swift/hello_world/ResponseModels.swift new file mode 100644 index 000000000000..c36c63da9fc8 --- /dev/null +++ b/mobile/examples/swift/hello_world/ResponseModels.swift @@ -0,0 +1,12 @@ +/// Represents a response from the server. +struct Response { + let id: Int + let body: String + let serverHeader: String +} + +/// Error that was encountered when executing a request. +struct RequestError: Error { + let id: Int + let message: String +} diff --git a/mobile/examples/swift/hello_world/ViewController.swift b/mobile/examples/swift/hello_world/ViewController.swift index 5063fa1f69f2..f935f0c2f8cf 100644 --- a/mobile/examples/swift/hello_world/ViewController.swift +++ b/mobile/examples/swift/hello_world/ViewController.swift @@ -5,7 +5,14 @@ private let kCellID = "cell-id" private let kURL = URL(string: "http://localhost:9001/api.lyft.com/static/demo/hello_world.txt")! final class ViewController: UITableViewController { - private var responses = [Response]() + private let session: URLSession = { + let configuration = URLSessionConfiguration.default + configuration.urlCache = nil + configuration.timeoutIntervalForRequest = 10 + return URLSession(configuration: configuration) + }() + private var requestCount = 0 + private var results = [Result]() private var timer: Timer? override func viewDidLoad() { @@ -27,25 +34,35 @@ final class ViewController: UITableViewController { } private func performRequest() { - // Note that the request is sent to the envoy thread listening locally on port 9001. + self.requestCount += 1 + let requestID = self.requestCount let request = URLRequest(url: kURL) + + // Note that the request is sent to the envoy thread listening locally on port 9001. NSLog("Starting request to '\(kURL.path)'") - let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in - if let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data { - self?.handle(response: response, with: data) - } else if let error = error { - NSLog("Received error: \(error)") - } else { - NSLog("Failed to receive data from the server") + self.session.dataTask(with: request) { [weak self] data, response, error in + DispatchQueue.main.async { + self?.handle(response: response, with: data, error: error, id: requestID) } + }.resume() + } + + private func handle(response: URLResponse?, with data: Data?, error: Error?, id: Int) { + if let error = error { + return self.add(result: .failure(RequestError(id: id, message: "\(error)"))) } - task.resume() - } + guard let response = response as? HTTPURLResponse, let data = data else { + return self.add(result: .failure(RequestError(id: id, message: "missing response data"))) + } + + guard response.statusCode == 200 else { + let error = RequestError(id: id, message: "failed with status \(response.statusCode)") + return self.add(result: .failure(error)) + } - private func handle(response: HTTPURLResponse, with data: Data) { guard let body = String(data: data, encoding: .utf8) else { - return NSLog("Failed to deserialize response string") + return self.add(result: .failure(RequestError(id: id, message: "failed to deserialize body"))) } let untypedHeaders = response.allHeaderFields @@ -54,14 +71,14 @@ final class ViewController: UITableViewController { return (header.key as? String ?? String(describing: header.key), "\(header.value)") }) + // Deserialize the response, which will include a `Server` header set by Envoy. NSLog("Response:\n\(data.count) bytes\n\(body)\n\(headers)") + self.add(result: .success(Response(id: id, body: body, serverHeader: headers["Server"] ?? ""))) + } - // Deserialize the response, which will include a `Server` header set by Envoy. - let value = Response(body: body, serverHeader: headers["Server"] ?? "") - DispatchQueue.main.async { - self.responses.append(value) - self.tableView.reloadData() - } + private func add(result: Result) { + self.results.insert(result, at: 0) + self.tableView.reloadData() } // MARK: - UITableView @@ -71,7 +88,7 @@ final class ViewController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.responses.count + return self.results.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) @@ -80,9 +97,25 @@ final class ViewController: UITableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? UITableViewCell(style: .subtitle, reuseIdentifier: kCellID) - let response = self.responses[indexPath.row] - cell.textLabel?.text = "Response: \(response.body)" - cell.detailTextLabel?.text = "'Server' header: \(response.serverHeader)" + let result = self.results[indexPath.row] + switch result { + case .success(let response): + cell.textLabel?.text = "[\(response.id)] \(response.body)" + cell.detailTextLabel?.text = "'Server' header: \(response.serverHeader)" + + cell.textLabel?.textColor = .black + cell.detailTextLabel?.textColor = .black + cell.contentView.backgroundColor = .white + + case .failure(let error): + cell.textLabel?.text = "[\(error.id)]" + cell.detailTextLabel?.text = error.message + + cell.textLabel?.textColor = .white + cell.detailTextLabel?.textColor = .white + cell.contentView.backgroundColor = .red + } + return cell } }