Circular singletons, EXC_BREAKPOINT, shared.unsafeMutableAddressor

Hello, this may be a code smell, but I have three different classes at the top of my code that are all singletons with references to each other.

e.g. here's a simple reducer:

import Foundation

@main
struct Alpha {
	static func main() {
		print(Date())
		print("Alpha main()")
		
		let bravo = Bravo.shared
		bravo.doSomething()
		
		RunLoop.main.run()
	}
}

class Bravo {
	//	Singleton
	public static let shared = Bravo()
	private init() {
		print("Bravo Singleton init()")
	}
	
	var charlie = Charlie.shared
	var delta = Delta.shared

	func doSomething() {
		print("Bravo \(#function)")
	}
}

class Charlie {
	//	Singleton
	public static let shared = Charlie()
	private init() {
		print("Charlie Singleton init()")
	}

	var delta = Delta.shared
	var bravo = Bravo.shared

	func doSomething() {
		print("Charlie \(#function)")
	}
}

class Delta {
	//	Singleton
	public static let shared = Delta()
	private init() {
		print("Delta Singleton init()")
	}

	var bravo = Bravo.shared
	var charlie = Charlie.shared

	func doSomething() {
		print("Delta \(#function)")
	}
}

If I try and set them all up then some kind of internal breakpoint is triggered and execution stops. (Thread 1: EXC_BREAKPOINT (code=1, subcode=0x100893adc))

I'm afraid that I'm not that familiar with multi-threading and tend to just avoid it if at all possible (as far as I can tell there's no multithreading in my code here.)

Xcode's Debug navigator appears to show that all my code is being run on one thread.

What's going on here? What have I got wrong? I assume that it's something to do with the circularity.
Thanks.

(I haven't marked any of the references to singletons as weak as they all last the lifetime of the executable. I know that this is wrong, but does it matter?)

By clicking on the #0 _dispatch_once_wait.cold.1 () in the Debug Navigator I was shown some assembly which mentioned BUG IN CLIENT OF LIBDISPATCH: trying to lock recursively"

I think that having all the shared instances as properties of each other caused some unwanted recursion. I managed to fix it by making all the singleton properties lazy.

You are correct. Global variable initializers cannot be mutually dependent, so the circular dependency between global initializers causes the program to fail dynamically when the circularity is detected.

3 Likes