Tag Archives: Objective C

NSUserDefaults Mystery On Mac OS X

While working on my Mac app, I often faced the need to delete the NSUserDefaults file manually. The NSUserDefaults of a mac application are stored in a .plist file whose location depends on whether the application is sandboxed or not.

  • For sandboxed application, the .plist file is located at /Users/John/Library/Containers/<bundle-identifier>/Data/Library/Preferences
  • For non-sandboxed application, the .plist file is located at /Users/John/Library/Preferences

When deleting the .plist file, I faced a problem. Even after deleting the file, the application still seemed to be picking up values from it at runtime. A little bit of exploring led me to the fact that a process named cfprefsd retains a link to the .plist file even after it’s moved to trash and hence the deleted values still get served to the app.

I found that emptying the trash alone isn’t enough. The cfprefsd process must also be killed using the terminal command:

killall kill cfprefsd

Executing the above command delivers the behaviour we expect. Values are read from the new .plist file. Hope this comes handy to Mac OS X developers out there. Good Luck And Good Life!!!

 

Advertisements

Errors and Exceptions in Objective C

Undestanding The Difference

There are two types of bad situations in an Objective C program: errors and exceptions. Let us understand the difference between the two.

Error is an unwanted situation that can happen during program execution but you can recover from it. The program memory remains in a consistent state and further execution can continue.

eg1: Attempt to access a network resource timed out or returned 500

eg2: Attempt to write to disk failed due to lack of disk space or permission denied

Handling: You can handle errors any way you want. Show a user message, log it to a file/console or report it to your back-end.

Exception is an unwanted state in your program that should never have occured. Exceptions may corrupt the memory state of the program in a way that prevents further execution.

This can generally occur in two ways:

  1. A logic error that you overlooked is now causing an exception.

    eg: Accessing invalid array elements i.e. index out of bound exception.

    In the above example, you unconsciously made a programming mistake. You couldn’t have handled it because you overlooked it. This will crash the program.

    Handling: Your approach here should be to reproduce and fix it asap.

  2. While execution, your program comes across a situation that your business logic doesn’t permit.

    eg: A database entry that your business logic expects to be always present is missing. For example, let’s say you are writng a program that requires a currency table with currency names, unicode symbols for currencies and country names. The values from this table are to be shown in a drop down in your program UI. You may create a table upon first launch of the program and insert the values. Later you make an sqlite select query but no values are returned.

    This is where confusion may arises because you can recover from such situations by treating them as errors (because the program memory is not corrupted) and displaying a message to the end user. This approach is wrong because it’s an exception at the business logic level.

    Handling: In such cases, you should force crash the program using NSAssert both in development and production phase. This is an aggressive approach geared toward quickly finding and fixing such exceptions rather than disguising them as errors. Besides, treating them as fatal (crash) attributes them the urgency they deserve.

Some Other Tips

  1. Try to avoid using @try/catch in Objective C because experts don’t recommend it.
  2. Formalize an approach to error/exception handling and stick to it. Document your intent in the code. You will be better off finalizing an approach and employing it consistently rather than revisiting this subject again and again.

Xcode New API Usage Warning: SDK vs. Deployment Target

Update: This no longer works on Xcode SDK 10.10 due to changes by Apple to <Availability.h> If I find a new solution, I will update this post.

We know that Xcode warns us about deprecated API usage i.e. if we use an api in our code that was available in older versions of the OS X SDK but is now marked deprecated (and may disappear sometime in future) we get a warning. The warning is to let us know that we need to fix the deprecated API usage because in future when the API is removed, any reference to the API will result in BOOM!!! Cr@s*h**!!!. So far so good right.

But what happens when we want to support our app on older versions of OS X but we upgrade our Xcode which includes a newer OS X SDK. Let’s say we want to run our Mac app on OS X 10.7 (MACOSX_DEPLOYMENT_TARGET) onwards but the Xcode version we are using now for development comes with OS X 10.8 SDK. There are going to be certain new APIs (Methods / Classes / Enums) made available in 10.8. If we use these newer APIs in our code, the compilation will go on fine without Xcode giving any build warning about unavailability on 10.7 (MACOSX_DEPLOYMENT_TARGET) but when the app is run on OS X 10.7 and these new API symbols are referenced BOOM!!! Cr@s*h**!!!.

This leads us to our problem statement:

How do we generate warnings while using new API’s when our Deployment Target (Min OS X version we want to support) is lower than the Xcode SDK Version?

The original solution was given by Jeff Johnson here. The solution is to redefine certain <AvailabilityMacros.h> macros so that they start generating warnings when newer API’s are used. Adding the following code in our prefix header (.pch file) does the trick:

#ifdef __OBJC__
        
        #import <Foundation/NSObjCRuntime.h>
        
        #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_7
            #undef  AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER
            #ifdef __clang__
                #define AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER __attribute__((weak_import, deprecated("API is newer than Deployment Target.")))
            #else
                #define AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER WEAK_IMPORT_ATTRIBUTE
            #endif
        #endif
    
        #undef  NS_CLASS_AVAILABLE
        #define NS_CLASS_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER

        #undef  NS_ENUM_AVAILABLE
        #define NS_ENUM_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER

        #import <Cocoa/Cocoa.h>

#endif
  1. Redefining AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER takes care of methods that are introduced in 10.8 which are marked as NS_AVAILABLE() or NS_AVAILABLE_MAC() in the headers.
  2. Redefining NS_CLASS_AVAILABLE takes care of classes that are introduced in 10.8 which are marked as NS_CLASS_AVAILABLE() or NS_CLASS_AVAILABLE_MAC() in the headers.
  3. Redefining NS_ENUM_AVAILABLE takes care of methods that are introduced in 10.8 which are marked as NS_ENUM_AVAILABLE() or NS_ENUM_AVAILABLE_MAC() in the headers.

We are generating warnings because we want to conditionally use the newer API’s when running on 10.8 after factoring in the non availability on 10.7. To suppress the warnings after factoring in the non-availability, we need to use #pragma directives as shown below:

Class notifCenter = NSClassFromString(@"NSUserNotificationCenter");
if(notifCenter){//NSUserNotificationCenter class available. This is the factoring in of non availability on 10.7.
     #pragma clang diagnostic push
     #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
        [notification setTitle:@"My Notification"];
        [NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];        
     #pragma clang diagnostic pop
}

Note: This solution needs to be modified when the MACOSX_DEPLOYMENT_TARGET and / or the SDK changes.


Related Readings:
Importance of order of #import, Weak Linking Symbols