We have a NSUUID class (we provide the declaration and implementation). We used it successfully up to iOS 6.0. We implemented it because UIDevice uniqueIdentifier was banned long before Apple deprecated it, and returning a NSUUID was a natural choice.
At iOS 6.0, we had to guard the define because Apple introduced the same class:
#if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_5_1
#interface NSUUID : NSObject {
...
}
#endif
iOS 5.1 and lesser are now broken. On iOS 5.1, we get back nil after alloc/init.
I tried to remove the #if/#end, but I get duplicate names when using the latest iPhone SDK.
Apple's lack of a stable API is a bug, not a feature. This "try it at runtime" crap is not cutting it. It makes it very difficult to write high integrity software.
From Tommy's response below, I can't instruct Apple's toolchain to use our implementation of NSUUID all the time. How do I provide an implementation of NSUUID for iOS 5.1 and lower (that might be compiled using the latest SDK)?
You can't. You've explicitly broken the rules:
Objective-C classes must be named uniquely [...] In order to keep
class names unique, the convention is to use prefixes on all classes.
You’ll have noticed that Cocoa and Cocoa Touch class names typically
start either with NS or UI. Two-letter prefixes like these are
reserved by Apple for use in framework classes.
You'll need to rename your own class. The quickest way is quite probably to right click on the class name, select "Refactor -> Rename..." and use a correct prefix this time. Xcode may not be able to refactor fully automatically since it'll obviously be ambiguous which NSUUID you're referring to in other parts of your code.
EDIT: regardless of grandstanding, if you want to implement code that provides a self-implemented replacement for NSUUID where it's not available then the solution is to "try it at runtime".
Assuming you've implemented NDRUUID, which implements the same interface as NSUUID then the quickest solution is to add something like this to your prefix header:
#define NSUUID (NSClassFromString(#"NSUUID") ? [NSUUID class] : [NDRUUID class])
You can then use [NSUUID UUID], etc, everywhere else in your code as though you were targeting iOS 6 only; the only difference is that when running under 5 you'll actually be addressing NDRUUID. Whenever you stop supporting 5 just remove that line from your prefix header and delete your own class from the project.
Hopefully you can see this is a much better way to handle introducing new APIs and backwards compatibility than, say, not using Apple's NSUUID at all anywhere until it's available everywhere.
I have built exactly what you asked for: an implementation of NSUUID for iOS 5.1 and lower that might be compiled using the latest SDK. See my NSUUID project on GitHub.
Related
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.
Let me start by saying I am not sure if this belongs here or at Super User. I started here.
Now, I am a very tidy person, and I like collapsing methods so that I can get around very easily. However, one thing that aggravates me is that all my #synthesize commands are always there. I can see no way to collapse them. (I have over 50 properties to synthesize) Is there a way I can hide these commands, while not messing up my build.
Thanks.
Unfortunately, from "available features" perspective, XCode is a very old IDE. Therefore no foldable code regions - see detailed discussion Xcode regions
Also note that with the latest compiler (LLVM 4.0), declaring #synthesize is optional. You can enable/disable it in compiler settings in your project settings.
However, if you are using GCC or an older LLVM version (for whatever reasons), this is not possible.
Edit:
After rereading your question... having 50 properties in one class smells very bad. Consider splitting your class into several smaller classes.
You can also put the #synthesize commands to the end of the #implementation file.
Since Xcode 4.4 you don't need to #synthesize properties if you don't want another name for it — it uses auto synthesise.
I'm trying to build a library that has convenience methods for dealing with the iOS and OSX AddressBook frameworks (Contact List on iOS and Contacts on OSX) and includes such methods as:
(BOOL)addressBookContainsRecordWithID:(NSString *)recordID (NSInteger in the case of OSX)
(id)newOrExistingGroupWithName:(NSString *)groupName
(id)addressBookRecordWithID:(NSString *)recordID
etc.
And I'd like to be able to call these methods on both OSX and iOS, but have that method execute the logic path respective for each device. For example, on OSX it would use a method that uses ABPerson and ABGroup whereas on iOS it would use a method that uses ABRecordRef.
My current plan is to have preprocessor directives (#if device-is-osx callOSXMethod #else callIOSMethod) which figure out whether I'm using iOS or OSX and use the correct method.
Is there a better way I could go about this? I feel like there is some design patter that could be used here.
It depends on how much shared code there will be between your two different implementations. If almost all of the code is shared except for a few specific method calls to the AddressBook frameworks then preprocessor directives with #if TARGET_OS_IPHONE are the way to go.
On the other hand, if you find that the code in your #if blocks is getting quite long, it usually makes more sense to split the Mac and iOS implementations into separate files that share an identical interface. Thus you'd have:
MyAddressBook.h - Shared by both
MyAddressBook_Mac.h - Mac implementation of MyAddressBook
MyAddressBook_iOS.h - iOS implementation of MyAddressBook
This is made easier by the fact that your implementations can even have different instance variables by using a class extension inside each implementation file. This second pattern leads to greater readability at the cost of having to maintain whatever common code there is in two places.
Preprocessor directives are the way to go. There are even predefined ones you can use:
#if TARGET_OS_IPHONE
// iOS code here
#else
// OS X code here
#endif
I've just started learning Obj-C and i'm a little confused. The videos I've been watching on Lynda.com were created with Xcode 4, but there are so many differences that I find it hard to believe that all of them occurred in 2 point releases. For instance:
In the video you could write:
#property NSString * myString
And it would be fine, but now in 4.2 it throws an error unless you write something like:
#property (nonatomic, retain) NSString * myString
In addition, there are no longer init or dealloc methods in the implementation code by default and NSAutoReleasePool is implemented completely differently. What gives?
While I can't guarantee that this list is exhaustive, the differences you'll find on the net are:
Objective-C 1.0 or 2.0
Old or modern runtime
Manual or automatic reference counting
My personal take on the main differences is:
Objective-C 2.0 brought properties and synthesized accessors among other things
The modern runtime has a different way of organizing instance variables (non-fragile instance variables), but you probably won't notice in day-to-day development work
The modern runtime also allows 64-bit apps if the OS supports it
Automatic reference counting lets you do away with retain/release code at the modest cost of following the coding and naming conventions
There are more differences, but these are the most important ones as I see it - personally I rarely have to use autorelease pools, and if I understand correctly the new syntax does not change the functionality.
If you create a project with "automatic reference counting" option "on" then there wouldn't be any init or dealoc methods.
When creating project
CHECK the Use Automatic Reference Counting.
When creating a project you can check the option "Use Automatic Reference Counting". If you do check this, then there won't be any init or dealloc methods, because Xcode automatically does the reference counting.
I have a project that targets both Mac OS X 10.4 and 10.5, where 10.5 is the base SDK.
Some methods like -[NSString stringByReplacingOccurrencesOfString:withString] are unavailable in 10.4. I could just implement the functionality by hand. Another option would be to implement the method as a category, but that would mess with the 10.5 implementation and that's something I'd like to avoid.
So how do I implement such methods in 10.4 without messing up 10.5 and in such a way that I can take out the implementation easily when I decide to stop supporting 10.4?
I think you have to use +load and +initialize to load a method at runtime if the method doesn't already exists.
if ([myString respondsToSelector: #selector(stringByReplacingOccurrencesOfString:withString:)])
{
// 10.5 implementation
}
else
{
// 10.4 implementation
}
Use a category, but put a tag on the method name; for example, stringByReplacingOccurrencesOfString_TigerCompatible:. In the implementation, call either Leopard's implementation or your own.
When you go Leopard-only, do a project search for “TigerCompatible”, then burninate all of those methods and un-tag all of their call sites.
Put all the missing implementation in categories in a bundle which is loaded on startup in main() if running under Tiger.
How about using a C preprocessor macro to insert the relevant methods if it's being built for 10.4? Maybe try doing something like this in a category, so those methods which don't exist on 10.4 are only included if it's being built for 10.4?
#if defined(MAC_OS_X_VERSION_10_4) && MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
// Put your implementations of the methods here
#endif
Do you need to support 10.4? If you're using 10.5 only methods in core parts of your app then it might be time to consider going 10.5 only.
Anyway, with the specific example given above, I suggest moving away from that and making a mutable copy of your string so you can use the similar method on NSMutableString which does work in 10.4