Andy Obusek
Andy Obusek

Reputation: 12842

How do static variables behave between multiple targets?

I'm having a problem with the behavior of a static variable defined in global scope in an Objective-C .m file. Specifically, I'm seeing different instances of the object referenced by the same variable from the same code depending on the scope when executing from an XCTest target.

How does a global static variable, defined in a .m file, behave between the main and XCTest targets? Here's an example of the problem I'm seeing:

Setup Code

Manager.m

#import "Manager.h"

// This is the variable of interest!
static Manager *sharedManager = nil;

@implementation Manager

+ (instancetype)sharedManager
{
    return sharedManager;
}

+ (void)setManager:(Manager *)manager
{
    sharedManager = manager;
}

@end

Here's an extremely simple ViewController:

- (void)viewDidLoad {
    [super viewDidLoad];
    Manager *tempManager = [[Manager alloc] init];
    [Manager setManager:tempManager];
}

The Meat Of The Confusion

I'm attempting to write XCTest unit tests that leverage Manager. The thing is, I'm seeing different instances of Manager from within the same flow of code execution depending on the context of the code. This was totally new and weird to me. For example, consider this unit test:

- (void)testManager {
    // 1
    ViewController *vc = [[ViewController alloc] init];

    // 2
    NSLog(@"manager %@", [Manager sharedManager]);

    Manager *tempManager = [[Manager alloc] init];
    [Manager setManager:tempManager];

    // 3
    NSLog(@"manager %@", [Manager sharedManager]);
    [vc viewDidLoad];

    // 4
    NSLog(@"manager %@", [Manager sharedManager]);
}

Here's some observed behavior:

  1. Breakpoint pause at line 1. If I po [Manager sharedManager] I will see a n object instance with a memory address. I'm assuming that is because ViewController is the initial view controller for the project's storyboard, and viewDidLoad() creates and sets the first Manager shared instance.
  2. This line prints null to the console, this is weird because a breakpoint and po on 1 showed an actual object in the console.
  3. This line prints an actual object instance, but a different instance than line 1. What's interesting though is that if I break at this line an [po Manager sharedManager] a different object instance is printed than the NSLog at this line prints, but the same instance as printed at the breakpoint at 1.
  4. viewDidLoad() runs an creates a new Manager. Breaking on this line with a po shows a new instance, but the NSLog prints the same instance as 3.

Important point Often the memory address of a NSLog'd object is different from the debugger po'd object. I don't know why. I'm guessing it is related to how XCTest executes in a different "app" instance?

The behavior I'm observing is that within the same flow of code, access of a static global variable within a .m file varies depending on which file is accessing it. Why?

I posted a fully functional, bare bones, project demonstrating this on GitHub at: https://github.com/obuseme/TestStatic

Upvotes: 4

Views: 1185

Answers (1)

Jon Reid
Jon Reid

Reputation: 20980

When I tried recreating the problem by hand in a fresh project, I got:

  1. Manager created by loading the initial view controller. (Incidentally, this is a test smell because tests should be in complete control of their environment. For unit tests, I use a separate application delegate to prevent this from happening.)
  2. The same manager is logged.
  3. The tempmanager.
  4. The manager set by viewDidLoad.

This is as expected. Your different results suggests that there's something strange in your project settings. I then downloaded your project and reproduced what you described. An important warning appears in the log:

objc[5304]: Class Manager is implemented in both /Users/jorei/Library/Developer/CoreSimulator/Devices/BEEDA9FD-5FDA-4347-8691-FD80B8C7A18D/data/Containers/Bundle/Application/020A6698-99B6-472A-8E77-330CBCB5AA1A/TestStatic.app/TestStatic and /Users/jorei/Library/Developer/Xcode/DerivedData/TestStatic-clulxcackwiypobvsqvfatqiznvi/Build/Products/Debug-iphonesimulator/TestStatic.app/PlugIns/TestStaticTests.xctest/TestStaticTests. One of the two will be used. Which one is undefined.

Here's the problem:

Incorrect Target Membership

There are two occurrences of Manager.m. Each has its own copy of the sharedManager static variable.

Upvotes: 2

Related Questions