Resolved dyld Symbol Not Found?

Discussion in 'Mac Programming' started by ArtOfWarfare, Oct 27, 2013.

  1. ArtOfWarfare, Oct 27, 2013
    Last edited: Oct 27, 2013

    ArtOfWarfare macrumors G3

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #1
    I use NSUserNotification when my app is run on an OS that has them and NSAlerts when it's run on an OS that lacks them. My code looks like this:

    Code:
    - (void)deployWarningWithTitle:(NSString*)title andMessage:(NSString*)message {
        if (NSClassFromString(@"NSUserNotification")) {
            NSUserNotification* notification = [[NSUserNotification alloc] init];
            notification.soundName = NSUserNotificationDefaultSoundName;
            notification.title = title;
            notification.informativeText = message;
            [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
            [notification release];
        } else {
            NSAlert* alert = [[NSAlert alloc] init];
    	alert.messageText = title;
    	alert.informativeText = message;
    	[alert runModal];
    	[alert release];
        }
    }
    My Deployment Target is 10.5 while my Base SDK is 10.9.

    Yet when my application is run on OS X 10.7.5 (and possibly other, older platforms) this error appears before any other part of my code is called:

    Code:
    dyld: Symbol not found: _OBJC_CLASS_$_NSUserNotification
    Referenced from: /Users/taylor/Dropbox/Battery Status Stuff/Battery Status Builds/1.4.1b/Battery Status.app/Contents/MacOS/Battery Status
    Expected in: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
    The only linked libraries I have are:

    SystemConfiguration.framework
    ServiceManagement.framework
    libz.dylib
    IOBluetooth.framework
    IOKit.framework
    Cocoa.framework

    What am I doing wrong? How can I fix this error without losing either compatibility with OS X 10.5 - 10.9 but still display NSUserNotifications in versions of OS X that allow it?
     
  2. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #2
    In this code:
    Code:
        if (NSClassFromString(@"NSUserNotification")) {
            NSUserNotification* notification = [[NSUserNotification alloc] init];
    
    you have a compile-time reference to the class NSUserNotification. What happens if that class doesn't exist at run-time, such as when the code is run on 10.5? When dyld tries to resolve that symbol, it will fail. And what is the symbol for the class? It's _OBJC_CLASS_$_NSUserNotification.

    While you may not be executing that branch on a 10.5 run-time, the symbol for the class is still present, and dyld will still try to resolve it. This is a side-effect of dyld loading the program: it tries to resolve all dependent libraries and symbols. Read the entire document on the Obj-C runtime, and how symbols and method-names are resolved. Also look at the Dynamic Loading docs.

    Also look at the sample code linked at the doc for NSClassFromString():
    https://developer.apple.com/library...Foundation_Functions/Reference/reference.html


    The way to fix this is to use the Class object returned by NSClassFromString. I'm pretty sure there's Apple documentation on how to do this (try looking in the Dynamic Loading docs), but it's pretty straightforward. A class's class methods are a Class object's instance-methods. So where NSUserNotification has a +alloc method, the Class object returned by NSClassFromString has a -alloc method. These are the exact same method.

    Also, you may have to eliminate the type declaration of notification as a NSUserNotification reference. Instead, declare it as 'id' type. You won't get compile-time checks that the methods are correct for NSUserNotification, but the run-time message-send checks will still be there.
     
  3. ArtOfWarfare, Oct 27, 2013
    Last edited: Oct 27, 2013

    ArtOfWarfare thread starter macrumors G3

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #3
    Thanks for the help chown :D

    The changes I made:
    1 - Moved NSClassFromString(@"NSUserNotification") outside of the if so that I could assign it to a variable of type Class.
    2 - Used the Class variable instead of a compile time symbol to access NSUserNotification. Stored the alloc/inited instance as an id instead of an NSUserNotification*.
    3 - Used the method notation instead of property notation for references to NSUserNotification (I don't understand why Xcode always has warnings about properties that might not exist but it's okay with methods that might not exist.)
    4 - I replaced the NSUserNotificationCenter symbol with NSClassFromString(@"NSUserNotificationCenter").

    I've checked and this code runs on every version of OS X that I want to support:

    Code:
    - (void)deployWarningWithTitle:(NSString*)title andMessage:(NSString*)message {
        [b]Class nsUserNotificationClass = NSClassFromString(@"NSUserNotification");[/b]
        if ([b]nsUserNotificationClass[/b]) {
            [b]id[/b] notification = [[[b]nsUserNotificationClass[/b] alloc] init];
            [notification [b]set[/b]SoundName:NSUserNotificationDefaultSoundName];
            [notification [b]set[/b]Title:title];
            [notification [b]set[/b]InformativeText:message];
            [[[b]NSClassFromString(@"NSUserNotificationCenter")[/b] defaultUserNotificationCenter] deliverNotification:notification];
            [notification release];
        } else {
            NSAlert* alert = [[NSAlert alloc] init];
    	alert.messageText = title;
    	alert.informativeText = message;
    	[alert runModal];
    	[alert release];
        }
    }
     

Share This Page