0

I currently have my app setup like this:

I set an extern variable in the AppDelegate.h because I've read they act as a global variable. Also added AudioToolbox framework.

AppDelegate.h

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

extern SystemSoundID tapSoundID;

@end

Next I added my code to load the tap sound in AppDelegate's didFinishLaunching method because I want the sound to be loaded as soon as the app's done starting.

AppDelegate.m

#import "AppDelegate.h"
@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    // Load Tap Sound
    NSURL *tapSoundPath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tap" ofType:@"mp3"]];
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)tapSoundPath, &tapSoundID);
    return YES;
}

@end

Made sure to import the AppDelegate.h class in my title screen view controller.

TitleScreen.h

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

And lastly, I'm using AudioServicesPlaySystemSound(tapSoundID); to play the sound when a button is tapped.

TitleScreen.m

#import "TitleScreen.h"
@interface TitleScreen ()
@end

@implementation TitleScreen

- (IBAction)buttonToGameScreen:(id)sender{
    AudioServicesPlaySystemSound(tapSoundID);
}

@end

I honestly thought this would work, and Xcode didn't show any warnings or errors until I tried to run the app:

Build Failed: Apple Mach-O Linker Errors

Undefined symbols for architecture armv7:
  "_tapSoundID", referenced from:
      -[AppDelegate application:didFinishLaunchingWithOptions:] in AppDelegate.o
      -[TitleScreen goToGameplay:] in TitleScreen.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I have no idea what these errors mean. I'm new to Objective-C/iOS programming, and I've spent hours trying to figure this out on my own but I just can't. Please help.

user3781632
  • 1,618
  • 2
  • 12
  • 15

2 Answers2

3

extern SystemSoundID tapSoundID doesn't define tapSoundID. It simply means that it is defined somewhere else.

So let's put it in your AppDelegate.m file.

#import "AppDelegate.h"

SystemSoundID tapSoundID; // Add this 

@interface AppDelegate ()
@end

And I think the link error will just go away.

You can also take a look at this post which explain the use of extern very well.

3 questions about extern used in an Objective-C project

Community
  • 1
  • 1
Yuchen
  • 30,852
  • 26
  • 164
  • 234
  • Thanks for the quick reply. The Mach-O errors go away, but now I'm getting a `Use of undeclared identifier 'tapSoundID'` on the `AudioServicePlaySystemSound(tapSoundID);` line in TitleScreen.h. – user3781632 Feb 08 '15 at 23:47
  • 1
    Oh I see now, I got that error thinking I had to delete `extern SystemSoundID tapSoundID;` from the .h file. I added it back to .h and also defined it in the .m file and everything works the way I want it to now! Thanks a ton for the help and the link~ – user3781632 Feb 08 '15 at 23:54
2

You are misunderstanding how to use global variables.

You declare a global variable without the extern keyword:

SystemSoundID tapSoundID;

However, you will get "duplicate symbol" linker errors if you simply change the definition to this. Here's why.

An #import directive is like a copy and paste. After preprocessing, AppDelegate.m will look like this:

//a few hundred thousand lines of code from UIKit and AudioToolbox here

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

SystemSoundID tapSoundID;

@end


@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    // Load Tap Sound
    NSURL *tapSoundPath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tap" ofType:@"mp3"]];
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)tapSoundPath, &tapSoundID);
    return YES;
}

@end

and TitleScreen.m will look like this:

//a few hundred thousand lines of code from UIKit and AudioToolbox here

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

SystemSoundID tapSoundID;

@end

@interface TitleScreen ()
@end

@implementation TitleScreen

- (IBAction)buttonToGameScreen:(id)sender{
    AudioServicesPlaySystemSound(tapSoundID);
}

@end

Notice how the project now contains 2 definitions for tapSoundID? That is the cause the "duplicate symbol" linker error. If you want a global variable to be accessible in the whole program, you need to keep the

extern SystemSoundID tapSoundID;

in AppDelegate.h, but you also have to declare the global variable (without extern) in AppDelegate.m:

SystemSoundID tapSoundID;

@implementation AppDelegate
//...

The extern keyword tells the linker that the global variable is defined in another file. When you declared the variable only with extern, the linker looked for the actual definition, but couldn't find it, causing an "undefined symbol" error. With the extern definition in AppDelegate.h and the global variable in AppDelegate.m, the linker sees the variable in AppDelegate.m and makes it accessible to all files that include AppDelegate.h.

NobodyNada
  • 7,529
  • 6
  • 44
  • 51
  • Thanks for that explanation. Yuchen mentioned it in his answer above, but it took me a minute to realized I needed both. Your post clarified it further. – user3781632 Feb 09 '15 at 00:00