Invert Objective-C #available? - objective-c

The recent #available addition to Objective-C can be handy but is apparently quite short-sighted..
Any app being around for more than a couple weeks will need to target e.g. multiple macOS versions.
Unfortunately the only #available construct for explicitly targeting older OS releases that appears to work (as of Xcode up to and including 12) is this cumbersome
if (#available(macOS 11.0, *))
{
// all hunky-dory: no-op
}
else
{
// legacy work arounds
...
...
}
Using just the else branch with a negation emits the dreaded #available does not guard availability here compiler warning..
Is there any way to simplify this ghastly code and remove the dummy no-op branch?

You could use the preprocessor, define a macro:
#define ifNotAvailable(A, B) if (#available(A, B)) {} else
and then write your code as:
ifNotAvailable(macOS 11.0, *)
{
// legacy work arounds
...
...
}
Whether this is less "ghastly" is in the eye of the beholder, YMMV!

Related

Mark method to never return nil

I'm developing an API using Objective-C, this API has protocol with some fictional method:
- (NSString *)gimmeString; // Want implementations to never return nil
I'm a big fan of providing context, so I heavily use everything for that purpose including attributes like __attribute__((nonnull)) and friends. What I'm asking is if there a way to provide context and possibly add a compile time check for method implementation saying "this method never returns nil" when compiled with clang?
i.e. I'd love to have something like:
#protocol MyProtocol
- (NSString *)gimmeString __attribute__((no_I_never_really_really_return_that_weird_nil));
#end
#implementation MyProtocolAdopter
- (NSString *)gimmeString
{
return nil; // WARNING! You're returning nil, YOU PROMISED!
}
#end
instead of just:
#protocol MyProtocol
// This method should never return nil
- (NSString *)gimmeString;
#end
#implementation MyProtocolAdopter
- (NSString *)gimmeString
{
// muvahaha, I lied!
return nil;
}
#end
I understand it is impossible to fully determine that at compile time, but detecting return nil; or functions which for sure evaluate to nil is fine.
Idea with something like __attribute__((objc_method_family(copy))) seems weird and unacceptable, but I didn't manage to find anything better then just adding a comment leaving my API users in a bit scarier and more unreliable world.
- XCode 6.3 -
Since XCode 6.3 you, actually, can annotate pointers to be nullable and nonnull and get compile time checks.
- (nonnull NSString *)gimmeString;
- Pre XCode 6.3 -
After some research, which included digging into clang and gcc docs I found that there's no way to achieve what I want in Objective-C.
Thoughts why is that so:
I believe that's because there's no way to determine that at compile time at some sophisticated level of quality. You can't tell for sure if method always returns not-nil at compile time. And determining if method could return nil is something shaky, guess, it's possible to evaluate all
return %something_which_evaluates_to_nil_at_compile_time%;
and warn on all methods which depend on that, but, for example, you can't be sure that some -init method does not always return nil or some web request get's you a request without additional context provided everywhere and you'll end up with false-negatives.
Even -dequeueReusableCellWithIdentifier:forIndexPath: from UITableView which guaranties to always return valid cell is defined as just:
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
What I've came up with:
So, what I've came up in Objective-C is providing context without compile-time checks. I add comments like that:
#protocol MyProtocol
/**
* Creates and returns a string.
*
* #note Implementations should never return nil.
*
* #return A string, guarantied not to be nil.
*/
- (NSString *)gimmeString;
#end
Also one more thing could be done to make this more readable, we can define an empty define and append it to the end of declaration, like this:
// Marks method to never return nil
#define MD_RETURNS_NONNULL
#protocol MyProtocol
/**
* Creates and returns a string.
*
* #note Implementations should never return nil.
*
* #return A string, guarantied not to be nil.
*/
- (NSString *)gimmeString MD_RETURNS_NONNULL;
#end
So eye catches this line making your code more readable, making your code users happier because they understand what you want to emphasise easier.
Swift
And the last but not the least is suggestion to move to Swift. In Swift this is possible and built in, you just define your method to return not an optional type:
protocol MyProtocol {
func gimmeString -> String
}
And you're good to go.
Conclusion
If you really want checks like this in Objective-C there's no magic, you only do it at the run time, at the same time you could provide context, which is great. But oh, wait, there's also Swift option, we'll anyway migrate to it at some time, so take your time and spend some of it learning this great new language. This need is a great example where Swift's safety features are really suitable.
UPD: Played with OCLint a bit, looks like something like this could be achieved using it (or just writing a custom Clang extension).

How to dynamically typecast objects to support different versions of an application's ScriptingBridge header files?

Currently I'm trying to implement support for multiple versions of iTunes via ScriptingBridge.
For example the method signature of the property playerPosition changed from (10.7)
#property NSInteger playerPosition; // the player’s position within the currently playing track in seconds.
to (11.0.5)
#property double playerPosition; // the player’s position within the currently playing track in seconds
With the most current header file in my application and an older iTunes version the return value of this property would always be 3. Same thing goes the other way around.
So I went ahead and created three different iTunes header files, 11.0.5, 10.7 and 10.3.1 via
sdef /path/to/application.app | sdp -fh --basename applicationName
For each version of iTunes I adapted the basename to inlcude the version, e.g. iTunes_11_0_5.h.
This results in the interfaces in the header files to be prefixed with their specific version number.
My goal is/was to typecast the objects I'd use with the interfaces of the correct version.
The path to iTunes is fetched via a NSWorkspace method, then I'm creating a NSBundle from it and extract the CFBundleVersion from the infoDictionary.
The three different versions (11.0.5, 10.7, 10.3.1) are also declared as constants which I compare to the iTunes version of the user via
[kiTunes_11_0_5 compare:versionInstalled options:NSNumericSearch]
Then I check if each result equals NSOrderedSame, so I'll know which version of iTunes the user has installed.
Implementing this with if statement got a bit out of hand, as I'd need to do these typecasts at many different places in my class and I then started to realize that this will result in a lot of duplicate code and tinkered around and thought about this to find a different solution, one that is more "best practice".
Generally speaking, I'd need to dynamically typecast the objects I use, but I simply can't find a solution which wouldn't end in loads of duplicated code.
Edit
if ([kiTunes_11_0_5 compare:_versionString options:NSNumericSearch] == NSOrderedSame) {
NSLog(#"%#, %#", kiTunes_11_0_5, _versionString);
playerPosition = [(iTunes_11_0_5_Application*)_iTunes playerPosition];
duration = [(iTunes_11_0_5_Track*)_currentTrack duration];
finish = [(iTunes_11_0_5_Track*)_currentTrack finish];
} else if [... and so on for each version to test and cast]
[All code directly entered into answer.]
You could tackle this with a category, a proxy, or a helper class, here is a sketch of one possible design for the latter.
First create a helper class which takes and instance of your iTunes object and the version string. Also to avoid doing repeated string comparisons do the comparison once in the class setup. You don't give the type of your iTunes application object so we'll randomly call it ITunesAppObj - replace with the correct type:
typedef enum { kEnumiTunes_11_0_5, ... } XYZiTunesVersion;
#implementation XYZiTunesHelper
{
ITunesAppObj *iTunes;
XYZiTunesVersion version;
}
- (id) initWith:(ITunesAppObj *)_iTunes version:(NSString *)_version
{
self = [super self];
if (self)
{
iTunes = _iTunes;
if ([kiTunes_11_0_5 compare:_version options:NSNumericSearch] == NSOrderedSame)
version = kEnumiTunes_11_0_5;
else ...
}
return self;
}
Now add an item to this class for each item which changes type between versions, declaring it with whatever "common" type you pick. E.g. for playerPosition this might be:
#interface XYZiTunesHelper : NSObject
#property double playerPosition;
...
#end
#implementation XYZiTunesHelper
// implement getter for playerPosition
- (double) playerPosition
{
switch (version)
{
case kEnumiTunes_11_0_5:
return [(iTunes_11_0_5_Application*)_iTunes playerPosition];
// other cases - by using an enum it is both fast and the
// compiler will check you cover all cases
}
}
// now implement the setter...
Do something similar for track type. Your code fragment then becomes:
XYZiTunesHelper *_iTunesHelper = [[XYZiTunesHelper alloc] init:_iTunes
v ersion:_versionString];
...
playerPosition = [_iTunesHelper playerPosition];
duration = [_currentTrackHelper duration];
finish = [_currentTrackHelper finish];
The above is dynamic as you requested - at each call there is a switch to invoke the appropriate version. You could of course make the XYZiTunesHelper class abstract (or an interface or a protocol) and write three implementations of it one for each iTunes version, then you do the test once and select the appropriate implementation. This approach is more "object oriented", but it does mean the various implementations of, say, playerPosition are not together. Pick whichever style you feel most comfortable with in this particular case.
HTH
Generating multiple headers and switching them in and out based on the application's version number is a really bad 'solution': aside from being horribly complicated, it is very brittle since it couples your code to specific iTunes versions.
Apple events, like HTTP, were designed by people who understood how to construct large, flexible long-lived distributed systems whose clients and servers could evolve and change over time without breaking each other. Scripting Bridge, like a lot of the modern 'Web', was not.
...
The correct way to retrieve a specific type of value is to specify your required result type in the 'get' event. AppleScript can do this:
tell app "iTunes" to get player position as real
Ditto objc-appscript, which provides convenience methods specifically for getting results as C numbers:
ITApplication *iTunes = [ITApplication applicationWithBundleID: #"com.apple.itunes"];
NSError *error = nil;
double pos = [[iTunes playerPosition] getDoubleWithError: &error];
or, if you'd rather get the result as an NSNumber:
NSNumber *pos = [[iTunes playerPosition] getWithError: &error];
SB, however, automatically sends the 'get' event for you, giving you no what to tell it what type of result you want before it returns it. So if the application decides to return a different type of value for any reason, SB-based ObjC code breaks from sdp headers onwards.
...
In an ideal world you'd just ditch SB and go use objc-appscript which, unlike SB, knows how to speak Apple events correctly. Unfortunately, appscript is no longer maintained thanks to Apple legacying the original Carbon Apple Event Manager APIs without providing viable Cocoa replacements, so isn't recommended for new projects. So you're pretty much stuck with the Apple-supplied options, neither of which is good or pleasant to use. (And then they wonder why programmers hate everything AppleScript so much...)
One solution would be to use AppleScript via the AppleScript-ObjC bridge. AppleScript may be a lousy language, but at least it knows how to speak Apple events correctly. And ASOC, unlike Cocoa's crappy NSAppleScript class, takes most of the pain out of gluing AS and ObjC code together in your app.
For this particular problem though, it is possible to monkey-patch around SB's defective glues by dropping down to SB's low-level methods and raw four-char codes to construct and send the event yourself. It's a bit tedious to write, but once it's done it's done (at least until the next time something changes...).
Here's a category that shows how to do this for the 'player position' property:
#implementation SBApplication (ITHack)
-(double)iTunes_playerPosition {
// Workaround for SB Fail: older versions of iTunes return typeInteger while newer versions
// return typeIEEE64BitFloatingPoint, but SB is too stupid to handle this correctly itself
// Build a reference to the 'player position' property using four-char codes from iTunes.sdef
SBObject *ref = [self propertyWithCode:'pPos'];
// Build and send the 'get' event to iTunes (note: while it is possible to include a
// keyAERequestedType parameter that tells the Apple Event Manager to coerce the returned
// AEDesc to a specific number type, it's not necessary to do so as sendEvent:id:parameters:
// unpacks all numeric AEDescs as NSNumber, which can perform any needed coercions itself)
NSNumber *res = [self sendEvent:'core' id:'getd' parameters: '----', ref, nil];
// The returned value is an NSNumber containing opaque numeric data, so call the appropriate
// method (-integerValue, -doubleValue, etc.) to get the desired representation
return [res doubleValue];
}
#end
Notice I've prefixed the method name as iTunes_playerPosition. Unlike objc-appscript, which uses static .h+.m glues, SB dynamically creates all of its iTunes-specific glue classes at runtime, so you can't add categories or otherwise patch them directly. All you can do is add your category to the root SBObject/SBApplication class, making them visible across all classes in all application glues. Swizzling the method names should avoid any risk of conflict with any other applications' glue methods, though obviously you still need to take care to call them on the right objects otherwise you'll likely get unexpected results or errors.
Obviously, you'll have to repeat this patch for any other properties that have undergone the same enhancement in iTunes 11, but at least once done you won't have to change it again if, say, Apple revert back to integers in a future release or if you've forgotten to include a previous version in your complicated switch block. Plus, of course, you won't have to mess about generating multiple iTunes headers: just create one for the current version and remember to avoid using the original -playerPosition and other broken SB methods in your code and use your own robust iTunes_... methods instead.

Warning for assignment in an if statement?

Is there a way I can get a warning when I'm assigning a variable instead of checking equality? There have been many times where I have accidentally assigned, rather than compared, and it would be great to have a warning!
Is there a way Xcode can warn me without having to change coding styles to:
if (YES == aVariable) {...}
Xcode already warns you if you use = instead of == in an if statement in most cases. If you're not getting the warning, tell us what version of Xcode you're using, how old your project is, and what build settings you have changed from their defaults.
My test: I created a brand new iOS app in Xcode 4.5.2 and didn't change any build settings. I just added a little code to application:didFinishLaunchingWithOptions: to trigger the warning. Here it is:
You can disable the warning by setting the compiler's -Wno-parentheses flag (but why would you want to?). You can suppress the warning in a particular case by adding an extra set of parentheses around the assignment:
if ((x = 7)) {
There are two cases where you don't get the warning by default. First, in an init method, you can assign to self, like this:
- (id)init {
if (self = [super init]) { // no warning by default
...
Second, in any context, you can assign the result of the nextObject selector, like this:
while (object = [enumerator nextObject]) { // no warning by default
You can enable warnings in these cases by setting the compiler's -Widiomatic-parentheses flag.
The compiler flag Wparentheses should do the trick. It will force you to place parentheses around assignments in a conditional in order to compile. Clang has this flag set by default.

How to know what Mac OS the app is running on?

I've seen in some projects something like:
#if .....
code...
#endif
but i can't find it now...
Let's say, for example, if the app is running on 10.8 the app does 1 thing, if not the app does other thing.
Whats to code to check if it's running on 10.8?
Thanks.
You're probably asking the wrong question. Except in very rare cases, you should not care what system version the user is running. Instead, you should be checking if the specific thing you're interested in is available.
For instance, if Apple introduces a MagicHologram class in Mac OS X 10.9 that you want to use, you don't check if the user is running Mac OS X 10.9. Instead, you check if the MagicHologram class is available. If it is, you can use it. If not, it's not available. It doesn't even matter why. Maybe they're running 10.8. But maybe it's five years later, and Apple's decided to drop the MagicHologram class entirely.
(Also, keep in mind that you'd need to weak link to HologramKit, the library that provides the MagicHologram class.)
Likewise, if they introduce a new method to NSString, instead of checking the OS version you'd check if NSString knows about the new method.
That said, NSApplication.h includes an external constant called NSAppKitVersionNumber. You can compare this to constants like NSAppKitVersionNumber10_7 which (it should be noted) are numbers like 1138, not 10.7. There's only a few places this is appropriate, mostly where classes were private and undocumented but got major changes before being documented and becoming a part of the public parts of the SDK. Also, it might be helpful if you want to avoid a specific bug that's been fixed since.
To recap:
Detect individual classes and methods, which should cover 99.44% of your cases.
Use NSAppKitVersionNumber and NSAppKitVersionNumber10_7 to cover those cases where class or method detection would lie to you.
Those first two points cover all normal cases. You should go no further. But if you must have behaviour based on humane version, look at abarnert's answer below. It's the sanest way to get them.
Don't use operatingSystemVersionString, which is specifically listed as not safe for parsing.
References/more information:
SDK Compatibility Guide "Read this document if you want your application to target a specific version or multiple versions of iOS or Mac OS X."
Using SDK-Based Development Describes how to use weakly linked classes, methods, and functions to support running on multiple versions of an operating system.
A quick way to do it is:
if ( NSAppKitVersionNumber >= NSAppKitVersionNumber10_7 ) {
// Do stuff for Lion or later
}
More here.
You can see all the constants available in NSApplication.h, which you can get to by using Open Quickly... (Cmd-Shift O) in Xcode.
The header files provide for some surprisingly interesting reading material, if you are so inclined.
As others have said above (and I'd pick Steven Fisher's answer), you usually do not actually want to get the version number.
And if you only need to do comparisons against a major OS X version up to the version of the current SDK you're using, NSAppKitVersionNumber (as in Monolo's answer) is the right way to do it.
If you actually do need to get the version number for some reason (e.g., for recording analytics about your users, so you can decide when to stop supporting 10.6.0-10.6.5), here's how to do it:
#import <CoreServices/CoreServices.h>
SInt32 majorVersion, minorVersion, bugFixVersion;
Gestalt(gestaltSystemVersionMajor, &majorVersion);
Gestalt(gestaltSystemVersionMinor, &minorVersion);
Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);
For 10.7.3, this gives majorVersion = 10, minorVersion = 7, bugFixVersion = 3.
The 10.7 documentation removed the paragraph that directly suggested Gestalt as the way to get OS version, but it's still not deprecated or legacy, and there's no other suggestions. In fact, every other way to get this information (parsing -[NSProcessInfo operatingSystemVersionString], calling sysctlbyname on "kern.osrelease" and converting Darwin kernel version to OS X version, etc.) is explicitly counter-indicated somewhere. So, this is the way to do it, if you really want to.
Just keep in mind that, as the release notes for System 6.0.4 said back in 1989, this new API may not be permanent and could be removed in a future version of the OS.
You can get the current release from the uname -r command (that's actually the kernel release, though it's easy to map onto Mac OS X versions), from the sw_vers command which gives you the release name, version and build identifier, or from the Gestalt() function (on current versions of Mac OS X, anyway). The safest way is probably to read the output of sw_vers which is in a stable format that's easy to parse.
Notice that you probably don't want to know what version of the OS you're on, though. What you probably want to do is to test whether a particular feature is available. You can do this by weak-linking frameworks, by weak class references, or by inspecting whether a class responds to selectors appropriate to the feature you're interested in.
As Steven Fisher said, you should not check for the system version but for the availability of the class or method you want to use.
The check if a specific class is available use
if ([NSHologram class]) {
// Create an instance of the class and use it.
} else {
// The Hologram class is not available.
}
To check if a specific method is available use
NSString* hologramText = #"Hologram";
if ([hologramText respondsToSelector:#selector(convertHologram)]) {
[hologramText convertHologram];
}
Yet for the method checking, the method must be available on the system where you build your app, or else you will get a compile error.
Here is code for how I do it. I love it this way, mainly because I don't have to A) Rely on an NSTask or B) Rely on any File I/O that many processes have access to.
static NSString* const kVarSysInfoVersionFormat = #"%#.%#.%# (%#)";
static NSString* const kVarSysInfoKeyOSVersion = #"kern.osrelease";
static NSString* const kVarSysInfoKeyOSBuild = #"kern.osversion";
- (NSString *) _strControlEntry:(NSString *)ctlKey {
size_t size = 0;
if ( sysctlbyname([ctlKey UTF8String], NULL, &size, NULL, 0) == -1 ) return nil;
char *machine = calloc( 1, size );
sysctlbyname([ctlKey UTF8String], machine, &size, NULL, 0);
NSString *ctlValue = [NSString stringWithCString:machine encoding:[NSString defaultCStringEncoding]];
free(machine); return ctlValue;
}
- (NSString *) getOSVersionInfo {
NSString *darwinVer = [self _strControlEntry:kVarSysInfoKeyOSVersion];
NSString *buildNo = [self _strControlEntry:kVarSysInfoKeyOSBuild];
if ( !darwinVer || !buildNo ) return nil;
NSString *majorVer = #"10", *minorVer = #"x", *bugFix = #"x";
NSArray *darwinChunks = [darwinVer componentsSeparatedByCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
if ( [darwinChunks count] > 0 ) {
NSInteger firstChunk = [(NSString *)[darwinChunks objectAtIndex:0] integerValue];
minorVer = [NSString stringWithFormat:#"%ld", (firstChunk - 4)];
bugFix = [darwinChunks objectAtIndex:1];
} return [NSString stringWithFormat:kVarSysInfoVersionFormat, majorVer, minorVer, bugFix, buildNo];
}
Enjoy!
You can get OS version like this:
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
NSLog(version);
Output:
And I see You want to get just version. It can be done like this:
NSString *version = [[NSProcessInfo processInfo] operatingSystemVersionString];
NSRange range = NSMakeRange(8, 4);
NSString *justVersion = [version substringWithRange: range];
NSLog(#"%#", justVersion);
Result:
And for checking:
if ([justVersion isEqualToString:#"10.7"]) {
code...
}
else {
...
}

Simple inline function call equivalent in Objective-C - how?

I've been learning Obj-C since getting a MBP about a month ago. I'm fairly comfortable with what I'm learning & things are slotting in to my rusty old brain pretty well. Except there's one thing I'm just not sure if I'm overlooking, or if just going over my head, or I'm looking for something that isn't there.
Most languages I've used have a way of slotting in an inline function call to simplify the coding, & I'm just not sure how this translates in Obj-C. Especially I'm referring to when the function being called is in a separate file, for the coding purposes of keeping similar functions together.
So far, the only way I've seen in Obj-C guides & tutorials is to create a class with methods & then instantiate that class (within the class you're working) to access the method in a [message]. Is this the way it's done in Obj-C? The only way? The best way for some reason? I know classes have their place in many languages & I use them myself, but I'm referring to simple little inline function calls where I usually wouldn't go to the trouble of creating a complete class.
To use a simple C++ console example of my point (only showing the .cpp files):
// example mainFile.cpp
#include <iostream>
#include "mainFile.h"
#include "functionsFile.h"
using namespace std;
void theMainFunction () {
int resultBeforeAltering = 100;
// alterTheResult() = simple inline function call I'm referring to
cout << "The result is " << alterTheResult(resultBeforeAltering);
}
.
// example functionsFile.cpp - could contain many similar functions
#include "functionsFile.h"
int alterTheResult (int resultToAlter) {
int alteredResult;
if (resultToAlter < 100) {
alteredResult = resultToAlter * 2;
} else {
alteredResult = resultToAlter * 3;
}
return (alteredResult);
}
Is there an equivalent approach to do alterTheResult() in Obj-C (assuming mainFunction() was an Obj-C method)?
I've seen reference to functions within Obj-C, but they seem to be C functions being referred to. C functions are not what I'm asking about here.
Thanks in advance, answers much appreciated.
Yes, the way to inline is to use C or C++ inlining -- that's perfectly legal (for C++, that will require compiling as ObjC++). An ObjC method will never be inlined (until LLVM produces a JIT compiler =p).
If you simply want to organize methods in another file, you may want to try an ObjC category:
// NSString_MONStuff.h
#interface NSString (MONStuff)
- (BOOL)mon_isPalindrome;
#end
// NSString_MONStuff.m
#implementation NSString (MONStuff)
- (BOOL)mon_isPalindrome { return ...; }
#end
Again, those will not be inlined.
You can also use C or C++ external functions or classes instead of categories for organization - the benefit is speed, size, reduced dependencies, and safety. The choice is yours, but there's no way to inline an objc method (it's a very dynamic langauge).