In a lot of Objective-C code (and also some C), I have come across the misconception that ()
means “no parameters”. For example, I see code like:
void thisCFunctionTakesNoParameters() {
puts("Hello, world!");
}
typedef void (^plainCallback_t)();
In fact just after block support was added, while still struggling with the syntax I discovered that the following compiled:
- (void)someMethod {
NSString *(^iAmClever)() = ^(NSString *foo, NSString *bar) {
return [foo stringByAppendingString:bar];
};
NSLog(@"joined: %@", iAmClever(@"John ", @"Doe"));
}
“Wow,” I thought, “I can shorten my syntax because it must infer the parameter types!” So I used this merrily, but then stuff went wrong and I learned the truth.
Reality
Using ()
in a function type declaration means unspecified parameters.
This is a very old feature of C that has been maintained for backward compatibility. In fact, it was noted as an “obsolescent feature” in C89 — yes, that’s 1989!1
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature”.
Arguments passed to a function (or block) declared with ()
will use “default argument promotions”, which means they will not be the type declared by the block:
int main(int argc, const char *argv[]) {
void (^block)() = ^(NSInteger arg) {
NSLog(@"arg = %ld", arg);
};
block(42); // arg = 42
block(-1); // arg = 4294967295
block((NSInteger)-1); // arg = -1
CGRect frame = CGRectMake(10, 10, 50, 50);
block(frame.origin.x); // arg = 4294984304
block(@"fifteen"); // arg = 4294984432
block(); // arg = 4294984304
block("somewhere", "over", "the", "rainbow"); // arg = 4294981825
return 0;
}
The above compiles with no warnings whatsoever, even with -Weverything
.
The warning that should catch this is -Wstrict-prototypes
, but this doesn’t seem functional in clang (it’s documented for GCC).
Because this warning is apparently unimplemented, huge amounts of otherwise high quality code has these turds lurking in both public API and implementation:
- ReactiveCocoa (e.g.
-[RACStream reduceEach:(id (^)())reduceBlock]
) - Mixpanel (e.g.
-[Mixpanel flushWithCompletion:(void (^)())handler]
) - Facebook FBSDKCoreKit (implementation only)
- AFNetworking (implementation only)
Even UIKit made this mistake!
- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)())completionHandler
The completionHandler
block has since been marked (void)
in the current online documentation, but the headers in the iOS 9.0 SDK still show it as ()
.
Let’s fix this!
I have filed Radar 23116994 (view 23116994 on Open Radar). LLVM bug 20796 has been open since August 2014 but hadn’t moved. I’ve just commented on it, and have begun submitting pull requests to projects when I come across it (e.g. facebook-ios-sdk).
You can start by searching your own code for )()
and () {
— not all results will be cases, and not all cases will be found by this but it’s a starting point.
-
From the C89 standard: “3.9.4 Function declarators: ↩