SwiftObjcCppDuplex

This is a tiny sample project to show the two-way communication between one Swift object and another in C++ via Objective-C.

Background and Motivation

The bridging header makes it possible for Swift objects to access Obj-C objects. And [Project]-Swift.h precompiled header makes it possible for Obj-C objects to access Swift objects, whose classes and functions are makred by @objc. The interoperability between Obj-C and C++ are well defined, though it is a bit cumbersome, if you want to completely isolate the C++ code from the Obj-C++.

A difficulty arises when you want to achieve the two-way communication between a Swift object and an Obj-C object. Apparently it is not easy to make the direct duplex communication between those two, in the sense that both sides have a reference to each other and have access to each other’s member functions and variables. It is by no means straightforward, but after some research and experiments, I have found a way to enable duplex communication. It involves 4 shim classes, two of which are singletons, and it is not pretty, but I could not find any better way to do it, and I though it was worth sharing.

Overview

  • SwiftEnd.swift : The Swift class that communicates with CppEnd. It instantiates and owns ShimObjc.

  • CppEnd.{hpp,cpp} : The C++ class that commnicates with SwiftEnd. It instantiates and owns ReverseShimCpp.

  • ShimObjc.{h,mm} : The hhim in Obj-C class in the direction from SwiftEnd to CppEnd. It instantiates and owns CppEnd.

  • ReverseShimCpp.{h,mm} : The shim in C++ class whose implementation uses Obj-C in the direction from CppEnd to ReverseShimObjc. It refers to the singleton ReverseShimObjc.

  • ReverseShimObjc.{h,mm} : The shim in Obj-C in the direction from ReverseShimCpp to ReverseShimSwift. It is implemented as a singleton, which is instantiated at the first call to getInstance() to avoid the race condition with ReverseShimSwift, which is also a singlton. This is implemented as a singleton so that the other Obj-C objects can discover it and can access SwiftEnd, but it can be a normal object instantiated and owned by ReverseShimCpp, if such an access is not needed. It refers to the singleton ReverseShimSwift.

  • ReverseShimSwift.swift : The shim in Swift in the direction from ReverseShimObjc to SwiftEnd. It is implemented as a singleton, which is instantiated in the initialization block of the object file. As it is a singleton, the object can be discovered by a class function getInstance() by both SwiftEnd and ReverseShimObjc. SwiftEnd calls getInstance() so that ReverseShimSwift can discover the corresponding SwiftEnd object.

Call Sequence

The SwiftEnd object is instantiated in App and it is pushed to SwiftUI as an environment object. It is picked up in ContentView. The button in the view calls SwiftEnd‘s callCpp() function, which eventually reaches to CppEnd::callBackToSwift(), which in turn calls ReverseShimCpp::callBackToSwift(), which eventually reaches again to SwiftEnd.callBackToSwift(), which reads the passed parameter and returns another value, which is returned all the way back to the original call to SwiftEnd.callCpp(). The call chain above shows a two-way communication between a Swift object (SwiftEnd) and a C++ object (CppEnd).

GitHub

View Github