How to get warnings when using 4.x only APIs when the base SDK is 3.x - iphone-sdk-3.0

While i'm careful, there have been a few times when i have used iOS 4.x apis by mistake that have caused odd behaviour on the older devices. Is there anyway to get the compiler to flag their usage so i'm alerted when i have done so.
Many thanks as always

Unfortunately, there is no option in Xcode to warn you about an API that does not exists on your deployment target. However, there is a workaround to use the API:
Class TheClass = NSClassFromString(#"NewerClassName");
if(TheClass != nil)
{
NewerClassName *newClass = [[NewerClassName alloc] init];
if(newClass != nil)
{
//Now, you can use NewerClassName safely
}
}
This probably won't provide warnings for that API, but it will allow you to pass Apple's validation process.

Related

Why does objective-c not have API availability checking?

Swift 2 have API availability checking.
The compiler will give you an error when using an API too new for your
minimum target OS
Why can't the objective-c compiler do the equivalent?
I googled objective c API availability checking and only swift 2 results came out so I assume the compiler for objective c can't do that.
Xcode 9.0 brings the runtime availability checking syntax from Swift to Objective-C:
if (#available(macOS 10.9, *))
{
// call 10.9+ API
}
else
{
// fallback code
}
this comes complete with warnings for calling APIs that are newer than your deployment target (if those calls are not wrapped in checks).
finally ;)
The warning (Swift makes it an error) just hadn't been implemented in the Clang compiler for years, but it's not an inherent Objective-C limitation (although due to its dynamic nature, you won't be able to catch all cases), nor Swift terminology.
The Apple macros (e.g., NS_CLASS_AVAILABLE) and source attributes (__attribute__((visibility(...))), __attribute__((availability(...)))) to annotate headers with availability information have been there for years, and they are widely-used in Apple's SDKs. The macros are defined in Foundation's NSObjCRuntime.h, and the Availability.h/AvailabilityMacros.h system headers, and the compiler can (and does) read them.
In early 2015, the -Wpartial-availability warning has been added to Clang's master branch, but this commit/warning hadn't made its way into Apple's version of Clang until (including) Xcode 7.2. You will get an unknown warning option log when adding the warning flag to a project in Xcode 7.2, but the flag is available in Xcode 7.3. There's currently no predefined setting for it, but you can add the flag to Other Warning Flags under Build Settings.
There are other tools that use LLVM libraries to detect partially available APIs, e.g., Deploymate. For my diploma thesis, I developed a tool that integrates directly into Xcode and is based on a modification to the Clang compiler. The code is still online, but I haven't kept up with the general Clang development so it won't be of much use, except for learning purposes. However, the "official" code (linked above) is much cleaner and better.
Edit: Starting with Xcode 9, availability checking will work for Objective-C (and C), too. Instead of using the above-mentioned warning flag, which does not support raising the deployment target temporarily/locally and therefore causes plenty of false positives, there's -Wunguarded-availability, and if (#available(iOS 11, *)) {...} to check and raise the deployment target for the following block of code. It is off by default, but -Wunguarded-availability-new will be on by default, and starts checking anything beyond iOS/tvOS 11, watchOS 4, and High Sierra. More details on that can be found in the Xcode 9 beta release notes, which currently requires signing in with a developer account.
Objective C does not have availability checking as part of the language, as the same result is available via Objective C preprocessor.
That is the "traditional" way of doing that in C derived languages.
Want to know if compiled in debug mode?
#ifdef DEBUG
// code which will be inserted only if compiled in debug mode
#endif
Want to check at compile time for a minimum version?
Use the Availability.h header in iOS, and similar headers for Mac OS X.
This file reside in the /usr/include directory.
just test __IPHONE_OS_VERSION_MAX_ALLOWED with the preprocessor, e.g.:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil]];
}else{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert)];
}
#else
[[UIApplication sharedApplication] registerUserNotificationSettings: (UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert)];
#endif
As Swift does not have a preprocessor, they had to invent a way of doing these kind of checks within the language itself.
If you want to check availability of a method at runtime, please notice that the appropriate way is by using the method respondsToSelector:, or instancesRespondToSelector: (the latter at class level).
You will normally want to combine both approaches, compile time conditional compilation and runtime check.
Objective C method presence verification, e.g. at class level:
if ([UIImagePickerController instancesRespondToSelector:
#selector (availableCaptureModesForCameraDevice:)]) {
// Method is available for use.
// Your code can check if video capture is available and,
// if it is, offer that option.
} else {
// Method is not available.
// Alternate code to use only still image capture.
}
If you want to test if a C function exists at runtime, it is even simpler: if it exists, the function itself it is not null.
You can't use the same identical approach in both languages.
It does these days. Furthermore, with Xcode 11 (including the current Xcode 11.3.1), you can even get it from the snippets. Press the + button towards the top right of Xcode (as shown in the image below).
Then in the search box, type "API". All 3 versions of the snippet for API Availability Check will appear -- Objective C, C and Swift.
Of course, you will get errors in Objective-C code. But you won't find results in google for Objective-C, if you use a term defined for Swift as you will not find kisuaheli website in google if you search for a german word. ;-)
You will get an error linking Objective-C code against a too old SDK. This is simply because the used method or class or $whatever is not defined in the header for that SDK. Again, of course.
This is typical Swift marketing of Apple: Because of the incapability of Swift they have to extend the language to get something, which is quite easy in Objective-C. Instead of clarifying that this is the result of the poorness of Swift, they tell you that this is a great feature of Swift. It is like cutting your fingers and then saying: "We have the great plaster feature!!!!!!!!" And you have to wait only some days and one comes around on SO with the Q: "Why does Objective-C does not have the great plaster feature???????" The simple answer: It does not cut your fingers.
The problem is not to generate the errors. The problem is to have one source code for all versions, so you can simply change the SDK version and get new code (or errors). You need that for easier maintenance.
In Objective-C you simply can use the answer found here:
Conditionally Hide Code from the Compiler or you can do that at runtime as mentioned in the comments to the Q. (But this is a different solution of the problem by nature, because it a dynamic approach instead of a static one as you have to do in Swift.)
The reason for having a language feature in Swift is that Swift has no preprocessor, so the Objective-C solution would not work in Swift. Therefore conditional code would be impossible in Swift and they had to add the plaster, eh, language feature.

Support newer features in older versions of OS X SDK

I've been learning more about Cocoa, Objective-C, and Xcode by contributing to an open-source project (IPMenulet); the project originally supported OS X 10.5.
In my haste, it seems that I have added features using SDK elements (NSJSONSerialization and the #autoreleasepool compiler directive) that aren't supported by the older SDK. Now, I'm trying to determine what if anything I can do to restore support for 10.5
Options:
NSJSONSerialization - I suppose that I could switch to JSONKit
#autoreleasepool {} - ?
#properties - add #synthesize and IVARs
Questions:
is there a way (compiler directives?) to use newer SDK elements if the OS supports it, switching to the older element if necessary? if so, is it better to refactor the functionality in version-specific methods (e.g. getJSONlegacy, getJSON)?
would it be better to mark the original project as a separate branch (to allow it to be enhanced)?
Different features involve different OS components, which defines how a feature can be used in multiple OS X versions. Here is my rough classification:
functionality is completely provided by some framework. For example, NSJSONSerialization is available in Mac OS X 10.7+. You can use the same solution for all OS versions or check at runtime if some functionality is available. For example,
if ([view respondsToSelector:#selector(setAcceptsTouchEvents:)])
[view setAcceptsTouchEvents:YES];
More details regarding multiple SDKs support can be found in SDK Compatibility Guide. Using SDK-Based Development.
functionality is completely provided by compiler. For example, #autoreleasepool, literals.
functionality is provided by compiler and runtime. For example, default #property synthesis. See Objective-C Feature Availability Index for more details.
functionality which depends on SDK against which an application is linked. It is more about behavior changes, such a mechanism is described in Backward Compatibility section in AppKit Release Notes.
And now back to your question. There is a mechanism to check in runtime if a feature is available, pretty often respondsToSelector: can do the job. I recommend to expose a single method which works on all OS versions. And only inside this method differences between OS versions are present. For example,
- (NSString *)base64EncodingForData:(NSData *)data {
NSParameterAssert(data);
if ([data respondsToSelector:#selector(base64EncodedStringWithOptions:)]) {
return [data base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];
}
else {
// Manual encoding using <Security/SecEncodeTransform.h> and kSecBase64Encoding.
}
}
You can create some 1.1 maintenance branch, perform all work in master, and merge to maintenance branch only bugfixes. So from maintenance branch you'll release 1.1.1 and from master 1.2. It's a viable approach. But you cannot support Mac OS X 10.5 indefinitely, so you need to decide in which IPMenulet version you'll drop 10.5 support.
To the extent that it helps at all, the classic version of:
#autoreleasepool { ... code ... }
Was:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
... code ...
[pool drain];
drain is preferred to the normal release because it then all works properly with (also now deprecated) OS X garbage collection. But it counts as a release so there's no memory leak and you shouldn't also release.

Objective-C: Instance variable used while 'self' not set... but it is

I've been tasked with cleaning up some Clang errors in a code base. I am very new to iPhone development and Objective C, but have found most of the problems trivial... this one is stumping me though, when I'm sure its something embarrassing.
From a ZAttributedString class:
- (id)initWithAttributedString:(ZAttributedString *)attr {
NSParameterAssert(attr != nil);
if ((self = [super init])) {
_buffer = [attr->_buffer mutableCopy];
_attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES];
}
return self;
}
The clang warning is "Instance variable used while 'self' is not set to the result of '[super or self] init...]', with the dereferencing of attr's _buffer attribute being highlighted.
If it helps, the warning also seems to mention that the problem is found when calling from this method:
- (id)copyWithZone(NSZone *)zone {
return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self];
}
Can anyone please explain to me what exactly the defect is here?
TIA!
Do not use -> to access instance variables, especially when the ivar is from some other object.
Do this:
_buffer = [[attr string] mutableCopy];
Same goes for that nasty attr->_attributes. Apparently, ZAttributedStringexposesattributes` as a property in the private header.
That compiler warning does seem, at the very most optimistic, entirely misleading and, likely, quite wrong in description. Filing a bug to have that clarified would be useful.
Note that #maddy's claim that using -> to access the instance variables directly in the attr string passed as it acts like a copy constructor is incorrect.
The incoming attr may be a ZAttributedString instance or an instance of a subclass or, really, an instance of any class that implements the same interface as ZAttributedString. Thus, you really must go through the accessors to guarantee that you are grabbing the correct state.
Now, as an implementation detail, ZAttributedString could require that the inbound instance be a non-subclassed instance of ZAttributedString, but it should use isMemberOfClass: to assert that requirement (and, please, don't do that).
The only spot where direct ivar access is sometimes used to pull state from another object is in the implementation of copyWithZone:, but that is exceedingly fragile and oft leads to whacky broken behavior. In fact, copyWithZone: (outside of the various plist compatible value classes) has been rife with fragility and the source of many many many bugs.
It seems like you are seeing the exact same bug as this: "[Bug 15092] New: static analyzer false positive: reports instance variable used while 'self' is not set to the result of [(super or self)] init". It has a very similar code attached to reproduce the bug.
If you run that code in Xcode 4.6.3 you can verify that it gives the same false warning as you are seeing.
The bug was resolved with the comment:
This is fixed in trunk, or at least mostly fixed -- there are still a few edge
cases where the warning will fire, but not your project.
(Dave, for now all of the main analyzer engineers do work at Apple, so there's
no real need to file duplicates. The LLVM people who don't work at Apple don't
have access to Apple Clang, which ships with Xcode, and this fix didn't make
Xcode 4.6. You can also get newer checker builds from
http://clang-analyzer.llvm.org)
As you can see the bug is fixed but still present in Xcode 4.6. Hold out for the next version of Xcode and the analyzer warning should be gone.

Detect language in use by iOS application

How can I detect the current application language ? I am not talking about NSLocale user preferences.
In my app there are currently two supported languages. A default 'en' and a specific 'it'.
I just wanted to know which one is actually in use. If it is relevant, as a further explanation, I am providing content trough a web service only for the two supported languages. When invoking the service I need to pass a language parameter. Currently I am doing this, it works, but I totally dislike it:
NSString *preferredLanguage = [[NSLocale preferredLanguages] objectAtIndex:0];
if (![preferredLanguage isEqualToString:#"it"] && ![preferredLanguage isEqualToString:#"en"]) {
return #"en";
}
return preferredLanguage;
I have looked trough the NSLocale class and UIApplication, but didn't manage to find anything useful.
I also notice that NSBundle has some localization methods, but they all seems not specific about which one is in use.
[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] would be better, because it will always return a localization that your app supports. There is no need for an additional check afterwards.
In fact, your code has a bug. If the first language in the user's preferences is not one that your app supports, iOS will continue down the list until it finds one that your app does support. So if the user's preferences were "fr", "it", "en", ..., iOS would load the it versions of your resources. However, your code would fall back to en.
(This is perhaps more important on OS X, where it's easy for the user to change the language ordering. On iOS it's apparently possible to do that, but it's not as obvious how it works.)
I usually reserve a special key in Localizable.strings, such as "HTTPAcceptLanguage", which I set to "en", "fr", etc. Now telling your server the language displayed by the application is as simple as NSLocalizedString(#"HTTPAcceptLanguage", nil).

How to conditionally use a new Cocoa API

In 10.6 Apple added +[NSPropertyListSerialization dataWithPropertyList:format:options:error:] and labeled the older +[NSPropertyListSerialization dataFromPropertyList:format:errorDescription:] as obsolete and soon to be deprecated. One way to use the newer call on 10.6 and above, and still run on earlier OS releases, would be something like this:
if ([NSPropertyListSerialization respondsToSelector:#selector(dataWithPropertyList:format:options:error:)]) {
data = [NSPropertyListSerialization dataWithPropertyList:dict
format:NSPropertyListXMLFormat_v1_0
options:0
error:&err];
} else {
data = [NSPropertyListSerialization dataFromPropertyList:dict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&errorDescription];
}
Built against the 10.4 SDK (for compatibility with that release), this results in: warning: 'NSPropertyListSerialization' may not respond to '+dataWithPropertyList:format:options:error:' And, worse, since the compiler does not know about this selector, it may pass the arguments incorrectly.
Is NSInvocation the approved/best way to call new APIs that, as far as the SDK is concerned, don't yet exist?
IIRC, you want to use the 10.6 SDK and set your deployment target (MACOSX_DEPLOYMENT_TARGET) to 10.4 so the 10.5/10.6 symbols are weak-linked. Then you can use the respondsToSelector: stuff and not get warnings.
Make sure you're checking that the object can respond to the selector, of course, or you will crash on 10.4/10.5.
One other way of doing things is to declare the missing method yourself as a category of the class in question. This will get the compiler to stop complaining about not finding the method, though of course you'll still need the runtime check you're already doing to avoid actually calling the method. You might also want to wrap such a declaration using availability macros, so that it will be ignored once you do move up to using the 10.5/10.6 SDK and you won't get a different compiler complaint down the line. That would look something like this:
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 //ignore when compiling with the 10.5 SDK or higher
#interface NSPropertyListSerialization(MissingMethods)
+ (NSData *)dataWithPropertyList:(id)plist format:(NSPropertyListFormat)format options:(NSPropertyListWriteOptions)opt error:(NSError **)error;
#end
#endif