blocks in nsdictionary? - ios

So I'm storing block actions into a nsmutabledictionary and then recalling them when a response comes back on a websocket. This turns async request into a block syntax. Here's the stripped down code:
- (void)sendMessage:(NSString*)message responseAction:(void (^)(id))responseAction
{
NSString *correlationID = (NSString*)[[message JSONValue] objectForKey:#"correlationId"];
[self.messageBlocks setObject:responseAction forKey:correlationID];
NSLog(#"Sending message: %#", correlationID);
[webSocket send:message];
}
- (void)webSocket:(SRWebSocket *)wsocket didReceiveMessage:(id)message;
{
NSString *correlationID = (NSString*)[[message JSONValue] objectForKey:#"correlationId"];
NSLog(#"Incoming message. CorrelationID: %#", correlationID);
void (^action)(id) = nil;
if (correlationID) {
action = [messageBlocks objectForKey:correlationID];
if (action) action([message JSONValue]);
[messageBlocks removeObjectForKey:correlationID];
}
}
Note: The server responds with a correlationID that is sent with the request. So each response is linked to each request through that id.
This works perfectly, better than I expected. The question I have is that is it safe to run blocks this way? Is calling [messageBlocks removeObjectForKey:correlationID]; enough to remove it from the memory. I remember pre-ARC, block_release was an option.

You need to copy stack-based blocks in order to safely store them in a container.
[self.messageBlocks setObject:[responseAction copy] forKey:correlationID];
For non-ARC code, you need to -autorelease it also.
[self.messageBlocks setObject:[[responseAction copy] autorelease] forKey:correlationID];
Hope that helps.

Related

Waiting for network call to finish

What I'm trying to achieve is to make a network request and wait for it to finish, so that I can make a decission what should be apps next step.
Normally I would avoid such solution, but this is a rare case in which codebase has a lot of legacy and we don't have enough time to apply necessary changes in order to make things right.
I'm trying to write a simple input-output method with following definition:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId;
The thing is that in order to perform some validation inside this method I need to make a network request just to receive neccessary information, so I'd like to wait for this request to finish.
First thing that popped in my mind was using dispatch_semaphore_t, so I ended up with something like this:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
dispatch_semaphore_signal(sema);
} failure:nil];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return [self.paymentCards firstCardForStatus:status];
}
Everything compiles and runs, but my UI freezes and I actually never receive sempahore's signal.
So, I started playing with dispatch_group_t with exactly the same results.
Look like I might have some problems with where code gets executed, but I don't know how to approach this and get the expected results. When I try wrapping everything in dispatch_async I actually stop blocking main queue, but dispatch_async return immediatelly, so I return from this method before the network request finishes.
What am I missing? Can this actually be acheived without some while hacks? Or am I trying to fight windmills?
I was able to achieve what I want with the following solution, but it really feels like a hacky way and not something I'd love to ship in my codebase.
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
flag = YES;
} failure:nil];
});
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
return [self.paymentCards firstCardForStatus:status];
}
I guess fetchLocationProviderStatusFor:completion:failure: calls those callbacks in main queue. That's why you get deadlock. It's impossible. We can't time travel yet.
The deprecated NSURLConnection.sendSynchronousRequest API is useful for those instances when you really can't (or just can't be bothered to) do things properly, like this example:
private func pageExists(at url: URL) -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 10
var response: URLResponse?
try! NSURLConnection.sendSynchronousRequest(request,
returning: &response)
let httpResponse = response as! HTTPURLResponse
if httpResponse.statusCode != 200 { return false }
if httpResponse.url != url { return false }
return true
}
Currently, your method causes work to be done on the main thread, which freezes the UI. Your solution works, but it would be best to change the method to include a completion block. Then, you could call the completion block at the end of the async block. Here's the example code for that:
- (void)validCardForLocationWithId:(ObjectId)locationId completion:(nullable id<UserPaymentCard> (^)(void))completion {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
completion([self.paymentCards firstCardForStatus:status]);
} failure:nil];
}

Refactor code duplication of XCTestAssertions in a seperate method while writing unit test cases in Objective C

I have a unit test class with multiple test cases. Some test cases have common XCTest Assertions. These test cases implements an API call. This API call can have different input request parameters but the response is same. I have put test assertions on response. So, is it good by design to extract out this common assertions code on response in a separate method (not a test method) and call that method in the test methods?
Below is the code for reference:
- (void)testRequest {
AppType app = #"A";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
XCTAssertNil(error);
NSString *name = responseMessage[#"name"];
XCTAssert([name isEqualToString:#"Search"]);
NSString *response = responseMessage[#"response"];
XCTAssert([response isEqualToString:#"1"]);
hasReceivedResponse = YES;
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
}
Here appType can be A, B, C. Response as present in completion block remains the same. Can I extract completion block code to a separate method?
Refactored Code:
- (BOOL)receivedResponseForRequestMessage:(NSDictionary *)responseMessage error:(NSError *)error {
XCTAssertNil(error);
NSString *name = responseMessage[#"name"];
XCTAssert([name isEqualToString:#"Search"]);
NSString *response = responseMessage[#"response"];
XCTAssert([response isEqualToString:#"1"]);
}
-(void)testRequestForA {
AppType app = #"A";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
hasReceivedResponse = [self receivedResponseForRequestMessage:responseMessage error:error
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
}
-(void)testRequestForB {
AppType app = #"B";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
hasReceivedResponse = [self receivedResponseForRequestMessage:responseMessage error:error
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
}
Is this kind of refactoring correct by design?
Having a common XCTAssert does have at least one drawback. XCTAssert reports its line number, which enables you to find the failing test and quickly fix it. When your method testRequestForA fails, the failing line will be in receivedResponseForRequestMessage:error:.
One thing you can do in Objective-C is pass in a message after the expression being asserted. According to the docs, this is "An optional description of the failure. A literal NSString, optionally with string format specifiers." Your test results will contain this information, so if you identified the assertion well it will be easy to find the source line.
XCTAssert([response isEqualToString:#"1"], #"%# response failed", name); // Replace name with another string if appropriate
As a side note, Swift also allows you to also pass in the line number and/or file where the test failed, which makes it a lot easier to use assertions in helper methods.
In general, it's helpful to refactor test code to make it more expressive. This includes extracting assertion helpers.
In practice, this isn't easy to do in the Objective-C because assertions like XCTAssert are actually macros. They need to be macros at the call site in order to pick up __FILE__ and __LINE__. Unfortunately Apple chose to implement them entirely as macros, instead of as thin macros which call methods taking file name and line number arguments.
But in How to Structure Tests that Do Real Networking, I recommend not putting assertions within a block. Instead, have the block capture its arguments, and trigger the exit. This avoids issues with timeouts, and moves assertions to the end of the test where they are easier to read.
This would change the first part of your example to this:
- (void)testRequest {
AppType app = #"A";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
__block NSDictionary *capturedResponseMessage = nil;
__block NSError *capturedError = nil;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
capturedResponseMessage = responseMessage;
capturedError = error;
hasReceivedResponse = YES;
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
The hasReceivedResponse trigger can be replaced by an XCTestExpectation. This would let you use a timeout.
Now we can perform assertions against the captured arguments. But I'm going to change the assertions:
XCTAssertNil(error, #"error");
XCTAssertEqualObjects(responseMessage[#"name"], #"Search", #"name");
XCTAssertEqualObjects(responseMessage[#"response"], #"1", #"response");
}
First, I replaced the XCTAssert … isEqualToString: with XCTAssertEqualObjects. This is important, because when the assertion fails, it will report the 2 values that weren't equal instead of just "Failed". You can immediately see what the actual value was without using the debugger.
I also added a message to each assertion. This can be important when you have multiple assertions in a single test. It may not matter when sitting in front of your IDE, because you can click on a failure message to see which assertion triggered it. But when tests are running on a CI server, we want to log more information so that any failures can be diagnosed without hunting down line numbers.
If you still want to extract common assertions, I'd recommend either:
Write them entirely as macros so you get __FILE__ and __LINE__. Look at the definition of XCTAssert to get started.
Use OCHamcrest and write a custom matcher. Because OCHamcrest takes care of capturing file name and line number, the matcher is easier to write.
However, pulling the assertions out of the block made them so short they're hardly worth extracting. I'd focus instead on extracting a helper for the first part, capturing the block arguments.

How to stub method with response block using OCMock

I have a method that calls a request with a response block inside. I want to stub my response and return fake data. How can this be done?
-(void)method:(NSString*)arg1{
NSURLRequest *myRequest = ...........
[self request:myRequest withCompletion:^(NSDictionary* responseDictionary){
//Do something with responseDictionary <--- I want to fake my responseDictionary
}];
}
- (void)request:(NSURLRequest*)request withCompletion:(void(^)(NSDictionary* responseDictionary))completion{
//make a request and passing a dictionary to completion block
completion(dictionary);
}
If I understand you correctly, you want to mock request:withCompletion: and call the passed completion block.
Here is how I have done this in the past. I've adapted this code to your call, but I cannot check it for compilation errors, but it should show you how to do it.
id mockMyObj = OCClassMock(...);
OCMStub([mockMyObj request:[OCMArg any] completion:[OCMArg any]])).andDo(^(NSInvocation *invocation) {
/// Generate the results
NSDictionary *results = ....
// Get the block from the call.
void (^__unsafe_unretained completionBlock)(NSDictionary* responseDictionary);
[invocation getArgument:&callback atIndex:3];
// Call the block.
completionBlock(results);
});
You will need the __unsafe_unretained or things will go wrong. Can't remember what right now. You could also combine this with argument captures as well if you wanted to verify the passed arguments such as the setup of the request.

How one tests http requests in iOS 8?

In ruby I used to test http requests with vcr gem which recorded the request so the tests didn't send request to real host. Is there anything like this in iOS8 world?
The requests I want to test really need to be recorded since those requests may be outdated in some time and will return some other response
P.S. It would be great if it was some default Apple/iOS approach/library like XCTest for testing in general
What you want is something like OHHTTPStubs or Nocilla or AMY server. All of them essentially use NSURLProtocol to intercept your request and allow you to designate a response. We used OHHTTPStubs but pick the one with the feature set closest to your use case.
Here's an example of an OHHTTPStubs implementation in a unit test for a service that talks to a single REST endpoint:
NSString *loadRoomJSON = #{ #"key" : #"value" }; /* some JSON */
NSNumber identifier = #1;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSString *url = [NSString stringWithFormat:#"v1/user/%#/room", identifier];
XCTAssert([request.URL.relativePath containsString:url], #"Expected certain URL");
return YES;
} andRespond:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithJSONObject:loadRoomJSON statusCode:200 headers:nil];
}];
XCTestExpectation *loadPromise = [self expectation:#"Room loaded"];
[service loadRoomOnSucceed:^(Room *room) {
// Do your asserts here. For us, the JSON is mapped to an object
// so for example you could assert that the object is mapped correctly
[loadPromise fulfill];
} onFail:^(NSError *error) {
expect(error).to.beNil();
}];
[self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) {
expect(error).to.beNil();
}];
In reality our tests are shorter since we write wrapper/helpers to make it read better so this is an exploded-out version. Should give you the general idea. OHHTTPStubs (if you use it) has helper functions to load responses directly from files as well.
Im not sure if I understood you correct. But if I understand you right, you should be able to use XCTest to test your request and response.
class Tests:XCTestCase{
func testing(){
var expectation = self.expectationWithDescription("Your request")
var url = NSURL(string: "http://yourUrl.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if let httpRes = response as? NSHTTPURLResponse {
println("status code=",httpRes.statusCode)
//200 means OK
if httpRes.statusCode == 200 {
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
}else{
println("error \(error)")
}
}
}
}

Why am I not getting an error when sending data through the Game Center without internet?

-(BOOL)sendMessage:(NSMutableDictionary *)_message {
//*** Process _message and convert it to NSData here ***//
NSError *error;
BOOL success = [currentMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (error != nil) {
NSLog(#"Sending data ERROR");
}
return success;
}
I started a match (stored in currentMatch) with another player and continuously sent data using the above method. Then I turned off the wifi.
However, I am not getting the "Sending data ERROR" log message at all.
Why? I turned off the internet connection, so why is there no error here? What could possibly lead to this scenario?
I've also confirmed that the dictionary I am sending is properly encoded into an NSData object, and success is returning YES.
As per the documentation
Return Value
YES if the data was successfully queued for transmission; NO if the match was unable to queue the data.
The method only enqueues the data for transmission, which happens asynchronously.
If you want to monitor the state of the transmission, implement the proper GKMatchDelegate delegate methods, such as match:didFailWithError:.
However, as stated in the documentation:
The match queues the data and transmits it when the network becomes available.
so if you try to perform the method with no network, the transfer just won't happen until the network is back, meaning that you won't see it failing.
By the way you should check the return value, instead of the error, since the error might be nil despite the operation being unsuccessful.
NSError *error;
BOOL success = [currentMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (!success) {
NSLog(#"Sending data ERROR\n%#", error);
}
return success;
You need to check the return value of the method to know whether or not an error occurred. You cannot test the error parameter directly.
if (!success) {
NSLog(#"Sending data ERROR -- %#", error);
}
return success;
As to why you don't get an error, that send method is asynchronous. It simply enqueues the data for transmission and immediately returns. You have to catch the error through some other means (I'm not steeped in GameKit to know what that other means might be).

Resources