Skip to content

galudino/Swift_Language-Interop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Swift Language Interoperability Tests

This Xcode project demonstrates how to call C, Objective-C, and Objective-C++ code from within Swift.

Tested on macOS Big Sur 11.3.1 with Xcode 12.5.

Table of contents

Targets

We have three (3) separate examples/targets -- each target contains a Swift client source file (main.swift) that demonstrates the usage of a stack data structure of int (StackInt)

  • StackTest-C: a StackInt (typedef struct stack StackInt) type written in C, with procedural code acting upon instances of StackInt. This target demonstrates the invocation of C code from within Swift.

  • StackTest-ObjC: a StackInt class written in Objective-C. This target demonstrates the invocation of Objective-C code from within Swift.

  • StackTest-ObjCPP: a class stack type written in C++, wrapped in an Objective-C++ class, named StackInt. This target demonstrates the invocation of Objective-C++ code from within Swift.

Each main.swift does the following:

  • Creates and initializes a StackInt s of size 1
  • Push elements [10, 9, 8, ... 1] to s
  • While the stack isn't empty, peek/print the stack top, then pop s.

Main points

The bridging header file

Calling C, Objective-C, and Objective-C++ code requires the use of the bridging header file.

If you use Xcode to create a new .c, .cpp, or .m file -- Xcode will automatically ask you if you want a bridging header created for you. Say yes -- it will then be configured for your target. This is the easiest way to have a bridging header configured for your target.

C and Objective-C code will work natively with Swift, once a bridging header is configured. C++ code must be wrapped in an Objective-C API, requiring Objective-C++.

If you want to know how to create/configure the bridging header on your own, read below:

Creating the bridging header yourself

Let's say you have a C header, named c_stack_int.h. It contains a struct definition for a stack of int's, as well as forward-declarations of functions to use with instances of that struct.

You will need to create a bridging header file to use the declarations in c_stack_int.h. (For this example, we'll call this bridging header StackTest-C-Bridging-Header.h -- but it can be any name you'd like)

The contents of StackTest-C-Bridging-Header.h, should contain the following:

#import "c_stack_int.h"

Any other headers you would like to use in your Swift project should be #imported here as well.

Configuring your Xcode project

On the left hand side of your Xcode window, left-click the project icon. (The blue Xcode project icon). Then, to the center-left of the screen, under 'TARGETS', choose the desired target.

Next, click the 'Build Settings' tab. In the search bar on the right-hand side, type 'Bridging Header'. You will see the 'Objective-C Bridging Header' box.

Type the relative path of your bridging header in the box. For this project/target, the path provided is: StackTest-C/StackTest-C-Bridging-Header.h.

Language compatibility

C and Objective-C

C code and Objective-C classes can be called directly from within Swift.

Once you have configured a bridging header for your Xcode project, and the bridging header #imports your API's header file, you are now able to call your C/Objective-C code from within Swift.

Cpp

C++ classes must be wrapped inside an Objective-C class.

Source code using both the C++ and Objective-C languages must be stored in a (.mm) file. This denotes that the source is Objective-C++.

Summary:

  • Configure your bridging header. Insure that it imports the Objective-C header you intend to use.
  • Write your Objective-C header (.h). This header should not contain any C++ code.
  • Prepare the C++ files (header .hpp, source .cpp) for the class you will wrap.
  • Write your Objective-C++ source file (.mm). Now you may use C++ and Objective-C code together.
    • You may #include any C++ headers you require, including the .hpp file for your C++ class.
    • Store a 'private' field for your C++ class by creating a class extension.
    • When implementing the Objective-C wrapper methods, call your C++ member functions through the 'private' field.

Described below is the process of wrapping a C++ class in an Objective-C API.

Below is the contents of our bridging header, StackTest-ObjCPP-Bridging-Header.h. We import our Objective-C header file for our StackInt type here.

#import "objcpp_stack_int.h"

Here is the Objective-C header file for StackInt, objcpp_stack_int.h.

Notice that we have not introduced any C++ code in this header file -- the implementation details of this class will invoke C++ code.

#ifndef OBJCPP_STACK_INT_H
#define OBJCPP_STACK_INT_H

#import <Foundation/Foundation.h>

@interface StackInt : NSObject

- (id)init:(size_t)capacity;
- (id)deinit;

- (void)push:(int)val;
- (void)pop;

- (int)top;
- (BOOL)empty;

- (void)clear;

@end

#endif /* OBJCPP_STACK_INT_H */

Below is the Objective-C source file for StackInt, objcpp_stack_int.mm.

Notice the .mm extension -- this denotes that we are dealing with an Objective-C++ source file, as opposed to an Objective-C source file, which has a .m extension.

In this .mm file, we are able to use C++ code/libraries. We #include "cpp_stack_int.hpp", in addition to #import "objcpp_stack_int.h". (#include is used for C and C++ headers, #import is used for Objective-C headers)

stack (in namespace cpp) is our C++ class, _impl is a field within a class extension for StackInt. This class extension (and any fields/properties/methods within it) will not be accessible in the public interface -- our _impl field is effectively 'private'.

We use the _impl field to invoke cpp::stack's member functions.

#import "objcpp_stack_int.h"
#import <Foundation/Foundation.h>

#include "cpp_stack_int.hpp"

@interface StackInt() {
    cpp::stack _impl;
}
@end

@implementation StackInt

- (id)init:(size_t)capacity {
    if (self == [super init]) {
        auto s = cpp::stack(capacity);
        _impl = std::move(s);
    }
    
    return self;
}

- (id)deinit {
    return self;
}

- (void)push:(int)val {
    _impl.push(val);
}

- (void)pop {
    _impl.pop();
}

- (int)top {
    return _impl.top();
}

- (BOOL)empty {
    return _impl.empty();
}

- (void)clear {
    return _impl.clear();
}

@end

Our C++ header file, cpp_stack_int.hpp, is vanilla C++:

#ifndef CPP_STACK_INT_HPP
#define CPP_STACK_INT_HPP

#include <cstddef>

namespace cpp {
class stack;
}

class cpp::stack {
public:
    stack() = default;
    
    stack(size_t capacity);
    
    stack(const stack &s);
    stack(stack &&s);
    
    ~stack() {
        delete m_start;
        m_start = m_finish = m_end_of_storage = nullptr;
    }
    
    void push(int val);
    
    void pop() {
        if (m_finish > m_start) {
            --m_finish;
        }
    }
    
    int top() const { return *(m_finish - 1); }
    bool empty() const { return m_finish == m_start; }
    
    void clear();
    
    stack &operator=(const stack &s);
    stack &operator=(stack &&s);
private:
    static constexpr auto DEFAULT_CAPACITY = 16;
    
    size_t capacity() const {
        return m_end_of_storage - m_start;
    }
    
    size_t size() const {
        return m_finish - m_start;
    }
    
    void resize(size_t old_capacity, size_t new_capacity);
    
    int *m_start;
    int *m_finish;
    int *m_end_of_storage;
};

#endif /* CPP_STACK_INT_HPP */

Same with our cpp_stack_int.cpp source file:

#include "cpp_stack_int.hpp"

#include <memory>
#include <algorithm>
#include <iostream>

using cpp::stack;

stack::stack(size_t capacity) {
    auto *start = new int[capacity];
    
    m_start = m_finish = start;
    m_end_of_storage = start + capacity;
}

stack::stack(const stack &s) {
    auto *start = new int[s.capacity()];
    
    std::copy(s.m_start, s.m_finish, start);
    
    m_finish = m_start + s.size();
    m_end_of_storage = m_start + s.capacity();
}

stack::stack(stack &&s) :
    m_start(std::exchange(s.m_start, nullptr)),
    m_finish(m_start + s.size()),
    m_end_of_storage(m_start + s.capacity()) {

}

void stack::push(int val) {
    if (!m_start) {
        auto *start = new int[DEFAULT_CAPACITY];
        
        m_start = m_finish = start;
        m_end_of_storage = start + DEFAULT_CAPACITY;
    }
    
    if (size() == capacity()) {
        resize(capacity(), capacity() * 2);
    }
    
    *(m_finish++) = val;
}

void stack::clear() {
    std::fill(m_start, m_finish, 0);
    m_finish = m_start;
}

stack &stack::operator=(const stack &s) {
    auto *new_start = new int[s.capacity()];
    
    delete m_start;
    
    std::copy(s.m_start, s.m_finish, new_start);
    
    m_start = new_start;
    return *this;
}

stack &stack::operator=(stack &&s) {
    m_start = std::exchange(s.m_start, nullptr);
    m_finish = std::exchange(s.m_finish, nullptr);
    m_end_of_storage = std::exchange(s.m_end_of_storage, nullptr);
    
    return *this;
}

void stack::resize(size_t old_capacity, size_t new_capacity) {
    if (old_capacity == new_capacity) { return; }
    
    auto *new_start = new int[new_capacity];
    delete m_start;
    
    if (old_capacity < new_capacity) {
        std::copy(m_start, m_finish, new_start);
        
        m_start = new_start;
        m_finish = m_start + old_capacity;
        m_end_of_storage = m_start + new_capacity;
    } else {
        std::copy(m_start, m_start + new_capacity, new_start);
        
        m_start = new_start;
        m_finish = m_end_of_storage = m_start + new_capacity;
    }
}

Usage of bridged code

C

func useCStack() {
    // Declare a StackInt
    var s = StackInt();
    
    // Initialize the stack to a capacity of 1
    StackInt_init(&s, 1)
    
    // Push integers 10, 9, 8 ... 1 to the stack
    for i: Int32 in (1...10).reversed() {
        StackInt_push(&s, i)
    }
    
    // For all stack members,
    // peek the stack top, print the element, then pop it off
    while !StackInt_empty(&s) {
        let n = StackInt_top(&s)
        print("[useCStack] stack_int just popped: \(n)")
        
        StackInt_pop(&s)
    }
}

useCStack()

Objective-C

func useObjCStack() {
    // Declare a StackInt
    // Initialize the stack to a capacity of 1
    let s: StackInt = StackInt(1)

    // Push integers 10, 9, 8 ... 1 to the stack
    for i: Int32 in (1...10).reversed() {
        s.push(i)
    }
    
    // For all stack members,
    // peek the stack top, print the element, then pop it off
    while !s.empty() {
        let n = s.top()
        print("[useObjCStack] StackInt just popped: \(n)")
        
        s.pop()
    }
}

useObjCStack()

Objective-Cpp

func useObjCPPStack() {
    // Declare a StackInt
    // Initialize the stack to a capacity of 1
    let s: StackInt = StackInt(1)
    
    // Push integers 10, 9, 8 ... 1 to the stack
    for i: Int32 in (1...10).reversed() {
        s.push(i)
    }
    
    // For all stack members,
    // peek the stack top, print the element, then pop it off
    while !s.empty() {
        let n = s.top()
        print("[useObjCPPStack] StackInt just popped: \(n)")
        
        s.pop()
    }
}

useObjCPPStack()

About

Examples on calling C, Objective-C, and Objective-C++ code from within Swift.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published