I would assume that I am aware of how to work with DispatchGroup, for understanding the issue, I've tried:
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        performUsingGroup()
    }
    func performUsingGroup() {
        let dq1 = DispatchQueue.global(qos: .userInitiated)
        let dq2 = DispatchQueue.global(qos: .userInitiated)
        let group = DispatchGroup()
        group.enter()
        dq1.async {
            for i in 1...3 {
                print("\(#function) DispatchQueue 1: \(i)")
            }
            group.leave()
        }
        group.wait()
        dq2.async {
            for i in 1...3 {
                print("\(#function) DispatchQueue 2: \(i)")
            }
        }
        group.notify(queue: DispatchQueue.main) {
            print("done by group")
        }
    }
}
and the result -as expected- is:
performUsingGroup() DispatchQueue 1: 1
performUsingGroup() DispatchQueue 1: 2
performUsingGroup() DispatchQueue 1: 3
performUsingGroup() DispatchQueue 2: 1
performUsingGroup() DispatchQueue 2: 2
performUsingGroup() DispatchQueue 2: 3
done by group
For using the Semaphore, I implemented:
func performUsingSemaphore() {
    let dq1 = DispatchQueue.global(qos: .userInitiated)
    let dq2 = DispatchQueue.global(qos: .userInitiated)
    let semaphore = DispatchSemaphore(value: 1)
    dq1.async {
        semaphore.wait()
        for i in 1...3 {
            print("\(#function) DispatchQueue 1: \(i)")
        }
        semaphore.signal()
    }
    dq2.async {
        semaphore.wait()
        for i in 1...3 {
            print("\(#function) DispatchQueue 2: \(i)")
        }
        semaphore.signal()
    }
}
and called it in the viewDidLoad method. The result is:
performUsingSemaphore() DispatchQueue 1: 1
performUsingSemaphore() DispatchQueue 1: 2
performUsingSemaphore() DispatchQueue 1: 3
performUsingSemaphore() DispatchQueue 2: 1
performUsingSemaphore() DispatchQueue 2: 2
performUsingSemaphore() DispatchQueue 2: 3
Conceptually, both of DispachGroup and Semaphore serve the same purpose (unless I misunderstand something).
Honestly, I am unfamiliar with: when to use the Semaphore, especially when workin with DispachGroup -probably- handles the issue.
What is the part that I am missing?