Last active
March 28, 2022 08:16
-
-
Save nicklockwood/d63c69ba2f40a33d7aa4 to your computer and use it in GitHub Desktop.
Revisions
-
nicklockwood revised this gist
Jul 24, 2014 . 1 changed file with 11 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -26,7 +26,9 @@ So let's tackle the problems individually. Supposed we want to write code that w This solution is perfectly acceptable in an ordinary app, where we control the SDK version and deployment target. As long as the SDK is set to iOS 8 or above, and the deployment target is set to iOS 7 or below, this will work as intended. But for framework code, we don't control those settings, and if we are releasing code around the time of a new iOS release, it's reasonable to assume that not everyone will be using or targeting the same SDK and OS versions. But if we try to compile the code above using the iOS 7 SDK it will fail to compile because the `setQualityOfService:` method is undefined. So how do we fix that? We use conditional compilation: #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 @@ -46,11 +48,11 @@ But for framework code, we don't control those settings, and if we are releasing We've used `__IPHONE_OS_VERSION_MAX_ALLOWED` to check if the SDK is >= iOS 8. Now, if we are using the iOS 8 SDK, the code will still do a runtime check for `setQualityOfService:` and fall back to `setThreadPriority:` if it doesn't exist. But if we compile using the iOS 7 SDK, the code to do that check is omitted completely. This arrangement of having the `else` inside the `#if` block may seem a bit weird, but it means we don't have to duplicate the fallback code between the run-time and compile-time checks. Technically the `{` and `}` in the else clause aren't needed here, but if "`goto fail;`" means anything to you, you'll know why they're there. OK, so that handles the late adopters who are still using iOS 7 SDK. But what about the early adopters who want to drop support for iOS 7 completely? If they set their deployment target to iOS 8, they'll get a warning for the `setThreadPriority:` line because it's deprecated. It will never actually be called at runtime, but the compiler isn't smart enough to figure that out. So how do we supress the warning (without cheating using `#pragma GCC diagnostic ignored …`)? We use conditional compilation again: #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 @@ -68,9 +70,9 @@ Conditional compilation again: [operation setQualityOfService:NSQualityOfServiceUserInteractive]; } This time we've used `__IPHONE_OS_VERSION_MIN_REQUIRED` to check if the deployment target is >= than iOS 8. If it is, then there's no way the code can be running on iOS 7, which means we don't need to do the runtime check. We've also inverted the `respondsToSelector:` test so that we avoid doing an unnecessary runtime check when we already know we must be on iOS 8. OK, so what if we want to support both the late adopters _and_ the early adopters at once? This is a little bit more challenging, if we don't want repeat ourselves. Here's the best solution I could come up with: #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 @@ -100,4 +102,6 @@ OK, so what if we want to support both the late adopters _and_ the early adopter } This approach repeats the `__IPHONE_OS_VERSION_MAX_ALLOWED` test three times, but avoids repeating the actual application logic, which seems like the lesser of two evils (especially if the logic spanned multiple lines, unlike this trivial example). of course, if you were doing this check a lot, you could define a shorter macro for "is SDK >= than iOS 8", but that wouldn't improve things very much. I'm open to better alternatives. -
nicklockwood revised this gist
Jul 24, 2014 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -24,7 +24,7 @@ So let's tackle the problems individually. Supposed we want to write code that w [operation setThreadPriority:1.0]; } This solution is perfectly acceptable in an ordinary app, where we control the SDK version and deployment target. As long as the SDK is set to iOS 8 or above, and the deployment target is set to iOS 7 or below, this will work as intended. But for framework code, we don't control those settings, and if we are releasing code around the time of a new iOS release, it's reasonable to assume that not everyone will be using or targeting the same SDK and OS versions. But if we try to compile the code above using the iOS 7 SDK it will fail to compile because the `setQualityOfService:` method is undefined. So how do we fix that? We use conditional compilation: -
nicklockwood revised this gist
Jul 24, 2014 . 1 changed file with 6 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,14 +1,14 @@ Suppose we want to add support for a new iOS 8 API in our framework that replaces an older iOS 7 API. There are a few problems we might face: 1. The new API will crash if we call it on iOS 7 2. The new API won't compile if we build it using the iOS 7 SDK 3. The old API will raise a deprecation warning if built with a deployment target of iOS 8 and up These three problems require three different technical solutions: 1. We can avoid calling the new API on an old OS version by using runtime detection (e.g. `respondsToSelector:`) 2. We can avoid compiling new APIs on old SDKs using the `__IPHONE_OS_VERSION_MAX_ALLOWED` macro 3. We can avoid compiling deprecated code on new SDKs by using the `__IPHONE_OS_VERSION_MIN_REQUIRED` macro So let's tackle the problems individually. Supposed we want to write code that will use the new API if available, but the old API if not. In this case we want to set the priority of an `NSOperation` in a queue: -
nicklockwood revised this gist
Jul 24, 2014 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -70,7 +70,7 @@ Conditional compilation again: This time we've used `__IPHONE_OS_VERSION_MIN_REQUIRED` to check if the deployment target is >= than iOS 8. If it is then there's no way the code can be running on iOS 7, which means we don't need to do the runtime check. We've also inverted the `respondsToSelector:` test so that we avoid doing an unnecessary runtime check when we already know we must be on iOS 8. OK, so what if we want to support both the late adopters _and_ the early adopters at once? This is a little bit more challenging if we don't want repeat ourselves. Here's the best solution I could come up with: #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 @@ -94,8 +94,10 @@ OK, so what if we want to support both the late adopters _and_ the early adopter #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 [operation setQualityOfService:NSQualityOfServiceUserInteractive]; #endif } This approach repeats the `__IPHONE_OS_VERSION_MAX_ALLOWED` test three times, but avoids repeating the actual application logic, which seems like the lesser of two evils. If you were doing this check a lot, you could define a shorte macro for "is SDK >= than iOS 8", but that wouldn't improve things very much. I'm open to better alternatives. -
nicklockwood created this gist
Jul 24, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,101 @@ Suppose we want to add support for a new iOS 8 API in our framework that replaces an older iOS 7 API. There are a few problems we might face: 1) The new API will crash if we call it on iOS 7 2) The new API won't compile if we build it using the iOS 7 SDK 3) The old API will raise a deprecation warning if built with a deployment target of iOS 8 and up These three problems require three different technical solutions: 1) We can avoid calling the new API on an old OS version by using runtime detection (e.g. `respondsToSelector:`) 2) We can avoid compiling new APIs on old SDKs using the `__IPHONE_OS_VERSION_MAX_ALLOWED` macro 3) We can avoid compiling deprecated code on new SDKs by using the `__IPHONE_OS_VERSION_MIN_REQUIRED` macro So let's tackle the problems individually. Supposed we want to write code that will use the new API if available, but the old API if not. In this case we want to set the priority of an `NSOperation` in a queue: //set NSOperation thread priority if ([operation respondsToSelector:@selector(setQualityOfService:)]) { //use the new API if available [operation setQualityOfService:NSQualityOfServiceUserInteractive]; } else { //fall back to the old API [operation setThreadPriority:1.0]; } This solution is perfectly acceptable in an ordinary app, where we control the SDK version and deployment target. As long as SDK is set to iOS 8 or above, and the deployment target is set iOS 7 or below, this will work as intended. But for framework code, we don't control those settings, and if we are releasing code around the time of a new iOS release, it's reasonable to assume that not everyone will be using or targeting the same SDK and OS versions. But if we try to compile the code above using the iOS 7 SDK it will fail to compile because the `setQualityOfService:` method is undefined. So how do we fix that? We use conditional compilation: #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 if ([operation respondsToSelector:@selector(setQualityOfService:)]) { //use the new API if available [operation setQualityOfService:NSQualityOfServiceUserInteractive]; } else #endif { //fall back to the old API [operation setThreadPriority:1.0]; } We've used `__IPHONE_OS_VERSION_MAX_ALLOWED` to check if the SDK is >= iOS 8. Now, if we are using the iOS 8 SDK, the code will still do a runtime check for `setQualityOfService:` and fall back to `setThreadPriority:` if it doesn't exist. But if we compile using the iOS 7 SDK, the code to do that check is omitted completely. This arrangement of having rhe `else` inside the `#if` block may seem a bit weird, but it means we don't have to duplicate the fallback code between the runtime and compile-time checks. Technically the `{` and `}` in the else clause aren't needed here, but if "`goto fail;`" means anything to you you'll know why they're there. OK, so that handles the late adopters who are still using iOS 7 SDK. But what about the early adopters who want to drop support for iOS 7 completely? If they set their deployment target to iOS 8, they'll get a warning for the `setThreadPriority:` line because it's deprecated. It will never actually be called at runtime, but the compiler isn't smart enough to figure that out. So hwo do we supress the warning (without cheating)? Conditional compilation again: #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 if (![operation respondsToSelector:@selector(setQualityOfService:)]) { //if the new API is not available, use the old API [operation setThreadPriority:1.0]; } else #endif { //use the new API [operation setQualityOfService:NSQualityOfServiceUserInteractive]; } This time we've used `__IPHONE_OS_VERSION_MIN_REQUIRED` to check if the deployment target is >= than iOS 8. If it is then there's no way the code can be running on iOS 7, which means we don't need to do the runtime check. We've also inverted the `respondsToSelector:` test so that we avoid doing an unnecessary runtime check when we already know we must be on iOS 8. OK, so what if we want to support both the late adopters _and_ the early adopters at once? This is a little bit more challenging if we don't want repeat ourselves. Here's the best solution I could come up with, but I'm open to alternatives: #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 if (![operation respondsToSelector:@selector(setQualityOfService:)]) { #endif [operation setThreadPriority:1.0]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 } else #endif #endif { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 [operation setQualityOfService:NSQualityOfServiceUtility]; #endif }