#available - make only available on OS X - ios

Is it possible to mark a function/computed variable as available on OS X and unavailable on iOS (and vice versa)? Something such as #available(OSX 10.0, iOS unavailable).
I know about the #if OSX statement, however, I'm trying to achieve something that Apple has done for the NSURLThumbnailKey - it's marked as NS_AVAILABLE_MAC(10_10); in ObjC and the compiler will complain that the symbol is unavailable rather than that the compiler cannot resolve the symbol.
Also, I've found that the #if statements in Swift are buggy, for example, defining func myFunc(_, arg1:) and func myFunc(_, somethingElse:) within the #if scope will result in an error saying that myFunc has already been defined. Which is why I'm trying to avoid using #if.

You should be able to do something like:
#available(OS X 10.10, *)
To force it for OS X 10.10 higher only, or:
if #available(OS X 10.10, *)
There's some more information available on Hacking With Swift.
Multiple specification:
#available(iOS, unavailable, message="nope")
#available(OS X 10.10, *)
func someFunc()

Related

Cannot add #if compiler(>=5.0) in `AppDelegate.m` file:

Trying to add a block in the AppDelegate.m file
#if compiler(>=5.0)
if (#available(iOS 13.0, *)) {
// do something...
}
#endif
The error showed is:
Function-like macro 'compiler' is not defined
The following is a Swift compile-time directive:
#if compiler(>=5.0)
...
#endif
It makes no sense to use this in an Objective-C file, AppDelegate.m. In an Objective-C source, it will result in the “Function-like macro 'compiler' is not defined” error.
So, if you want a runtime check for iOS 13, just remove the #if, e.g.:
if (#available(iOS 13.0, *)) {
...
}
See Marking API Availability in Objective-C.
A couple of observations (given that you tagged this with swift):
In Swift, the compile-time pattern is:
#if swift(>=5.0)
...
#endif
Or
#if compiler(>=5.0)
...
#endif
This pattern is generally only used when writing third-party libraries where you need to offer backward support to developers who are compiling your library with earlier versions of Swift.
See The Swift Programming Language: Compiler Control Statements
The following is an Objective-C runtime check for OS version:
if (#available(iOS 13.0, *)) {
// do something...
}
The equivalent Swift pattern is:
if #available(iOS 13.0, *) {
...
}
These are useful when writing code that is targeting multiple iOS versions, but you want to conditionally run code using iOS 13+ API.
To make it confusing, Swift does have an #available construct, but it is for marking a class, extension, or method as being available only for devices running a particular OS version, but is not used in conjunction with an if test. E.g. let’s say you are defining some function that should only be available to iOS 13 and later:
#available(iOS 13.0, *)
func someMethod() {
...
}
This is compile-time construct is to tell the compiler to not complain about iOS 13 API within this function if your app happens to support earlier iOS versions. You are effectively telling the compiler “hey, I know this has code inside it that only works with iOS 13 and later, but don’t worry, because I’ll only call it when the device in question is running iOS 13 or later.”
There are many things wrong that you did in your code example:
#if is a preprocessor macro. You can only use it outside the scope of anything, just like the import statement. If you are using it the way it should be used, then you can't use that if statement there.
#available cannot be used inside of an if statement. It can only be used to represent if a particular class/struct/func is available for said OS version and up, but not fragments of code. For that, use this instead:
if #available(iOS 13.0, *) {
// Do something.
}
If you want to check the Swift compiler version specifically, you can use #if swift. To check for iOS version during runtime, you need to use #available, the # version if used to mark types/functions only available on said versions.
#if swift(>=5.0)
if #available(iOS 13,*) {
//do something
}
#endif

Swift and Objc interoperability - availability not working only in objc

While here Apple states that the Swift available flag should be applied also in objc, it's not working for me. What am I doing wrong?
I've got following declarations in Swift files:
#objc protocol Readable: AnyObject {...}
#available(iOS 10.3, *)
#objc class Reader: NSObject, Readable {...}
So let's check if it produces an error when I try to initialize it in pre-ios-10 project without version check. If I write following code in Swift:
let tmp = Reader()
it returns an error:
'Reader' is only available on iOS 10.3 or newer
What is expected.
However if I write following code in objc:
// if (#available(iOS 10.3, *)) { // commeted out to test if it succeeds without version check
Reader *tmp = [[Reader alloc] init];
// }
The build is finished without any error, while I'd expect the same error as in Swift.
I've tried to mark the class with:
#available(iOS 11, *)
#available(iOS, introduced: 10.3)
Neither of these works (produces an error) in objc. Any help, please?
Objective-C has had __attribute__((availability)) for longer than it has had #available. To make it work, the Objective-C compiler weakly links symbols that are not available on the deployment target. This means that compiling always succeeds, and starting your app succeeds, but the symbol will be NULL at runtime if it is not available.
Depending on what it is, you'll get more or less graceful degradation when you try to use it:
calling a weakly-linked function that is missing is going to crash
reading or writing to a global variable that is missing is going to crash
using a class that is missing will be a no-op and all methods are going to return zero
The old way of testing whether a symbol is found at runtime is just to compare it to NULL:
NS_AVAILABLE_MAC(...) #interface Foo #end
int bar NS_AVAILABLE_MAC(...);
int baz(int frob) NS_AVAILABLE_MAC(...);
if ([Foo class]) { /* Foo is available */ }
if (&bar) { /* bar is available */ }
if (&baz) { /* baz is available */ }
In your case:
Reader *tmp = [[Reader alloc] init];
tmp will be nil, because that would be the same as [[nil alloc] init].
The #available directive has only relatively recently been added to Objective-C. It's now possible to use #available in Objective-C in the same way that you use #available in Swift. However, to preserve backwards compatibility, it possibly never will be a compile-time error (at default error levels) to try to use a symbol that might not be available on the deployment target in Objective-C.

What is accomplished by checking the address of a constant like SKStoreProductParameterAffiliateToken?

I have this code from a library I am using.
#ifdef __IPHONE_8_0
if (&SKStoreProductParameterAffiliateToken) {
if (self.affiliateToken) {
[appParameters setObject:self.affiliateToken forKey:SKStoreProductParameterAffiliateToken];
if (self.campaignToken) {
[appParameters setObject:self.campaignToken forKey:SKStoreProductParameterCampaignToken];
}
}
}
#endif
Xcode is saying that the first line will always evaluate to be true but what is this line doing exactly? I never saw a if with & and a constant in that way.
SKStoreProductParameterAffiliateToken is defined as
SK_EXTERN NSString * const SKStoreProductParameterAffiliateToken NS_AVAILABLE_IOS(8_0);
What is the developer trying to check, the address of a constant? Is he trying to check if the version of iOS has this constant defined and by doing that, he is trying to check the instruction inside the if should run? But he already has ifdef __IPHONE_8_0... (??!!)
I don't get it.
Anyway I am compiling for iOS 9.3, so I can delete the if and the ifdef, right?
It is a check to see if a weak-linked symbol is available. If the library/framework containing the symbol has been weakly linked and is not available its address will evaluate to NULL and the if condition will be false.
See Using Weakly Linked Methods, Functions, and Symbols in Apple's Using SDK-Based Development for full details.
#ifdef __IPHONE_8_0 checks if Xcode should compile code inside. Ohterwise older version of Xcode will show an error about unknown variable SKStoreProductParameterAffiliateToken.
But when using newer Xcode version (with iOS SDK 8+), we may still can set a lower minimum target for our project. In this case to avoid crash on devices with lower than iOS 8 version we should check first if variable, class, method or function exists.
In your case, we are checking if pointer to SKStoreProductParameterAffiliateToken is not NULL, which means app is currently running at least on iOS 8.

Don't compile iOS in code for OSX on a cross-platform iOS/OSX module

I'm building a module that will be used in a iOS/OSX app.
The client is insisting that the module work on both iOS and OSX.
I need to check the system version which I'm doing with UIDevice on iOS and on OSX I'm using FTWDevice.
The problem is that when I try to compile it on OSX it complains that OSX doesn't support UIDevice.
I'm wondering if there is a way I can tell the compiler to compile a line of code on iOS but not on OSX?
I know I can do this with this to compile something on production only:
#ifndef DEBUG
[Crashlytics startWithAPIKey:CRASHLYTICSKEY];
#endif
Is there a solution for this or should I tell the client they're gonna need 2 modules that are exactly the same except for one line?
Another acceptable solution would be a workaround for finding the system version on iOS that doesn't involve using UIDevice.
Look at TargetConditionals.h. Specifically:
#if TARGET_OS_MAC
// Mac-only code here
#endif
#if TARGET_OS_IPHONE
// iOS-only code here
#endif
Another acceptable solution would be a workaround for finding the
system version on iOS that doesn't involve using UIDevice.
You can do as follow to obtain system version (OS X and iOS):
func majorVersion() -> Int { return NSProcessInfo.processInfo().operatingSystemVersion.majorVersion }
func minorVersion() -> Int { return NSProcessInfo.processInfo().operatingSystemVersion.minorVersion }
func patchVersion() -> Int { return NSProcessInfo.processInfo().operatingSystemVersion.patchVersion }
func myOSVersion() -> String { return NSProcessInfo.processInfo().operatingSystemVersionString }

UIDevice class for Mac OS X?

I am trying to port one of my iOS applications to Mac OS X, and I am struggling to find the UIDevice-like object for OS X. I am interested in getting the name of the device, such as "MacBookAir".
EDIT/ANSWER
As Josh Caswell pointed out, you can use SCDynamicStoreCopyComputerName key.
Here is the code:
+ (NSString *)computerName {
return [(id)SCDynamicStoreCopyComputerName(NULL, NULL) autorelease];
}
Swift 4.0 OSX
Here is what I needed for a similar value to UIDevice name
iOS (swift 3.2)
let deviceName = UIDevice.current.name
OSX (swift 4.0)
let deviceName = Host.current().name
The Net Services Programming Guide says:
In Mac OS X on the desktop, calling the SCDynamicStore function from the System Configuration framework returns the computer name so you can manipulate it like any other string. In iOS, you can obtain the same information from the name property of the UIDevice class.
There doesn't seem to be a single function called SCDynamicStore (doc bug), but you probably want SCDynamicStoreCopyValue. Looks like you get the key you need using SCDynamicStoreKeyCreateComputerName.
EDIT: Or, even better, as you found, use the SCDynamicStoreCopyComputerName function, found in SCDynamicStoreCopySpecific.h
NSHost *host = [NSHost currentHost];
NSLog(#"hostName %#",[host localizedName]);
you can see the mac name
You can try NSHost. Heres the class documentation
With the deprecation of Host, you might consider ProcessInfo.hostName as an alternative. In my case it returned:
ExampleHostName.local

Resources