dispatch_once weird behavior

Hi!

I have a xcode workspace with first objectiveC framework (let’s call it framework#1). This framework has some singletons (+(instancetype)shared using the dispatch_once idiom. This code is pretty straight forward and used everywhere :

+ (instancetype)shared {
    static dispatch_once_t onceToken;
    static OGAKeyManager *instance = nil;

    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });

    return instance;
}

I have a second framework (framework#2) in Swift that uses theses singletons (the framework#1 is added as do not embeed in the framework settings).

And I have an application that uses both frameworks.

If I make a breakpoint inside the dispatch_once alloc/init, I see that I enter 2 times : once when the shared method is called from framework#1 and another one when it’s called from framework#2.

How is that even possible ? Isn't dispatch_once supposed to handle this ? I asked chatGPT, it points out to some objC/Swift interoperability, but honestly, I don't see what I can do to make it work correctly.

There is no circular dependency (framwork#2 uses framwork#1, but framwork#1 has no clue of framwork#2 existence) Maybe it has something to do with sandbox, but I don't see how can it be.

Does anyone experienced some weird behavior like this ? Thanks

Replies

Are you sure that the debugger isn’t lying to do? To check this, add an NSLog to -[OGAKeyManager init] and see starting your app logs twice.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Unfortunately @eskimo it is not. I printed the memory addresses of the static instance iVar, and I get 2 different addresses, one for each time I enter the dispatch_once (called from framework #1 then #2). After that, I never enter the dispatch_once again :-/

  • + (instancetype)shared { static dispatch_once_t onceToken; static OGAKeyManager *instance = nil; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; NSLog(@"%@", instance); // prints twice with a different memory address each time (one for each Framework) }); return instance; }
Add a Comment

FYI, here are the imported framework. You can see that they are imported as local frameworks inside the workspace

@eskimo I just made a small sample project (1 objC framework with dispatch_once, 1 swift framework calling the #1 and an app including both and calling both) : it behaves as expected (dispatch_once is only called once). So it musts come from our project. We use, through a bridging header, some internal objC classes (non public) to do some work. Do you think that using some non public objects can lead to such a behavior (since Xcode would not find the Classes inside the framework, it loads a copy of the development local files, hence loading 2 copies of the framework in memory ? Thanks

I just made a small sample project … it behaves as expected

Right. That was gonna be my next question (-:

Given that, the most likely cause of this problem in your main app is that you have two different onceToken variables, protecting two difference singletons. It’s hard to say for sure how that came about, and it certainly is weird because the Objective-C runtime normally kvetches if you try to register two different copies of a class with the same name.

What I’d do next is tweak your code to look something like this:

+ (instancetype)shared {
    static dispatch_once_t onceToken;
    static OGAKeyManager *instance = nil;

    dispatch_once(&onceToken, ^{
        NSLog(@"%p", &onceToken);
        instance = [[self alloc] init];
    });

    return instance;
}

Then set a breakpoint on the log line, step over it, and see what gets logged. I have to presume it’s two different addresses. You can then look up what Mach-O images contain those addresses.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks a lot for your help. What do you mean by You can then look up what Mach-O images contain those addresses.?

Add a Comment

Ok, so as expected @eskimo, I have 2 dispatch_once_t, though I cannot figure out why. The first memory graph (the one from inside the app itself) seems very small IMO, at least compared to the other which have a lot of objects in memory. Maybe it's a lead ?

First call (shared inside the app):

Second call (shared inside the framework2):

What do you mean by “You can then look up what Mach-O images contain those addresses.”?

Consider this:

@implementation SomeSingleton

+ (SomeSingleton *)shared {
    static dispatch_once_t sOnceToken;
    static SomeSingleton * sShared;
    dispatch_once(&sOnceToken, ^{
        NSLog(@"%p", &sOnceToken);
        sShared = [[SomeSingleton alloc] init];
    });
    return sShared;
}

…

@end

If I set a breakpoint on the line after the NSLog, I see this printed:

2023-09-08 09:15:44.923100+0100 Test736851[4985:12779017] sOnceToken: 0x109f8dda8

Then I do this:

(lldb) p/a 0x100a13da8
(long) $0 = 0x0000000100a13da8 Test736851`shared.sOnceToken

See the Test736851 before the backquote (`)? That tells me the name of the Mach-O image containing the shared.sOnceToken static variable. In this case it the name of my test app, but I suspect that in your case you’ll see different values for the each ‘singleton’ that gets created.

If you see different addresses but the same Mach-O image value, you can use a link map to investigate. See Using a Link Map to Track Down a Symbol’s Origin.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks! Actually, as expected, the first Mach-O image is the app, the second is the framework#2. I'll try to investigate with the above link and let you know! Thanks 🙏

  • My bad, you said to use the link map if it had the same origin. It's not actually :-/ What's your next move master ? ^_^

  • Hi @eskimo, it did not work (see above). Any suggestions ? Thanks a lot!

Add a Comment