This question already has answers here:
Is it okay to pass in NULL to a block parameter?
(2 answers)
Closed 9 years ago.
I have a method that does an asynchronous network call, and then passes the success or failure results back via blocks:
- (void) loginWithSuccess:(void (^)(id responseObject))success failure:(void (^)(NSError* error))failure {
...
if(error) {
failure(error);
}
else {
success(responseObject);
}
}
I noticed that if I call this method and pass in nil as my blocks, my application will crash with EXEC_BAD_ACCESS:
[manager loginWithWithSuccess:nil failure:nil];
But, if I pass in empty blocks, it runs fine:
[manager loginWithWithSuccess:^(id responseObject){} failure:^(NSError *error){}];
I assume this is because at runtime you can't pass parameters to nil? So, when defining my methods that take blocks, should I always check that the blocks are not nil before calling them?
Just looking at Apple's Frameworks, some methods with block parameters accept NULL/nil as the block argument (e.g. animateWithDuration:animations:completion:), others don't (e.g. enumerateObjectsUsingBlock:).
If you're designing an API, you have to make that decision. If it makes sense to not have a block, you should accept nil and check before executing the block, otherwise you should throw an assertion like [#[] enumerateObjectsUsingBlock:nil] does:
'NSInvalidArgumentException', reason: '*** -[NSArray enumerateObjectsUsingBlock:]: block cannot be nil'
So why are you getting EXEC_BAD_ACCESS?
When invoking a block you are dereferencing the address, which you obviously can't do if it's not pointing to an actual block. There is a great explanation in this answer.
Please try the following example to understand call block logic:
void (^printString)(NSString*) = ^(NSString* arg) {
NSLog(#"%#", arg);
};
//(1) printString = ^(NSString* arg){};
//(2) printString = NULL;
printString(#"1");
In the console you will see "1". Then uncomment (1) and console reveals "Called" but no errors!
And at last, uncomment (2) and get EXEC_BAD_ACCESS. It is your situation exactly.
Called block must be not NULL or nil. You need to check existence of passing blocks in loginWithSuccess before call.
Related
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.
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.
I don't understand how Objective-C loop system works. I have function (hope names are right, rather check in code) which executes query from Health Kit. I got my mind blown when I realised that function pass return value before query finishes.
__block bool allBeckuped = true;
HKSampleQuery *mySampleQuery = [[HKSampleQuery alloc] initWithSampleType:mySampleType
predicate:myPredicate
limit:HKObjectQueryNoLimit
sortDescriptors:#[mySortDescriptor]
resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if(!error && results)
{
for(HKQuantitySample *samples in results)///main hk loop
{
allBeckuped = false;
NSLog(#"1");
}
}
}];//end of query
[healthStore executeQuery:mySampleQuery];
NSLog(#"2");
return allBeckuped;
I'm trying to check if there are any new data, but I don't know where to put condition for that, because nslog2 is called before nslog 1.
Any words I should Google up?
Any words I should google up?
You can start with: asynchronous design, blocks, GCD/Grand Central Dispatch should help as well - you're not using it but asynchronous designs often do.
Look at the initWithSampleType: method you are calling, it is an example of a method following the asynchronous model. Rather than return a result immediately, which is the synchronous model you are probably used to, its last argument, resultsHandler:, is a block which the method calls at some future time passing the result of its operation to it.
This is the pattern you will need to learn and follow.
Your method which contains the call to initWithSampleType: cannot return a result (e.g. your allBeckuped) synchronously. So it needs to take a "results handler" block argument, and the block you pass to initWithSampleType: should call the block passed to your method - and so the asynchronous flow of control is weaved.
HTH
Im trying to implement CometChat in my swift application. I managed to import the Objective-c framework successfully via a bridging header. But now I'm stuck trying to call Objective-C methods from swift.
This is the method from the interface i want to call:
- (void)loginWithURL:(NSString *)siteURL
username:(NSString *)username
password:(NSString *)password
success:(void(^)(NSDictionary *response))success
failure:(void(^)(NSError *error))failure;
And this is how the method is called from Objective-C:
[cometChat loginWithURL:#"localhost/cometchat/" username:usernameTextField.text password:passwordTextField.text success:^(NSDictionary *response) {
NSLog(#"SDK log : Username/Password Login Success %#",response);
[self handleLogin];
} failure:^(NSError *error) {
NSLog(#"SDK log : Username/Password Login Error%#",error);
[self handleLoginError:#[#0,error]];
}];
So far i have this:
cometChat.loginWithURL("localhost/cometchat/", username: EmailField.text, password: PasswordField.text){
(success: [NSDictionary], failure:NSError) in {
println("did i make it here?")
}
}
The problem is, it says that there is missing argument "success", but its a mystery to me how it can be an argument, when it clearly returns the response. I want to know how to put together this method call. I also used the objectivec2swift converter, but it wasn't any help. Also, i have no clue what the # means before the #[#0,error]
I know its a beginners question, but i already wasted a whole day on this, since i couldn't find any tutorials on how to call such "complex" Obj-C methods from swift.
Try this :-
cometChat.loginWithURL("localhost/cometchat/", username: "abc", password: "123", success: { (response) -> Void in
print("SDK log : Username/Password Login Success \(response)")
}) { ( error) -> Void in
print("SDK log : Username/Password Login Error \(error)")
}
When you look at the Objective-C signature, you see that the method takes two closures: success is a void function that takes a dictionary, and failure is a void function that takes an error.
In your Swift code you have only one closure: a void function that takes a dictionary and an error.
You either need to change the Objective-C method to take just one closure, or change the Swift code to provide two closures.
When you call a function, and the last parameter is a block / closure, then you can write the last parameter after the function call in { }. That applies to the last block only.
Anyway, you are trying to pass a closure with two parameters success and failure. You need to pass two closures, one as the success parameter of your function, with a parameter response, and one either as the failure parameter of your function, or following the function, with a parameter error.
In Xcode i am running tests on creating users based on ID. When a wrong ID is set the test should fail. Though this test fails since the method it tests has assertions in itself:
[[Apiclient sharedClient] findAndCreateUserWithID:nil success:^(Player *player) {
STFail(#"should not be able to create player with no ID");
} failure:^(NSError *error) {
}];
the method called:
- (void)findAndCreateUserWithID:(NSNumber *)ID success:(void (^)(Player *))createdPlayer failure:(void (^)(NSError *error))failure
{
NSParameterAssert(ID);
Will fail the test when parameter ID is nil. I know this is a pretty stupid example since it will always fail, but there are more assertions allready in code that are more useful. Whats the best practice on running Xcode unit tests which test code that allready has assertions?
As of late 2014, if you're using the new testing framework XCTest, you'll want to use XCTAssertThrowsSpecificNamed instead of the older STAssertThrowsSpecificNamed method:
void (^expressionBlock)() = ^{
// do whatever you want here where you'd expect an NSParameterAssertion to be thrown.
};
XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException);
NSParameterAssert throws an NSInternalInconsistencyException (source) when its assertion fails. You can test that this happens with the STAssertThrowsSpecificNamed macro. For example:
void (^expressionBlock)() = ^{
[[Apiclient sharedClient] findAndCreateUserWithID:nil success:^(Player *player) {
} failure:^(NSError *error) {
}];
};
STAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException, nil);
I use an expression block there to make it easier to put that much code in the macro.