Использование кода и библиотек cpp при создании приложений на языке Swift - lanit-tercom-school/grouplock GitHub Wiki

Вольный перевод статьи на ekreative.com с небольшими улучшениями.

##Objective-C и Swift

Swift — отличный высокоуровневый язык программирования. Однако, он появился совсем недавно, поэтому не может похвастаться обилием различных библиотек, коих предостаточно для Objective-C. Проблему можно обойти c помощью заголовка-моста (bridging header). Что для этого необходимо? Мы можем импортировать заголовки классов Objective-C, и они будут доступны в Swift.

Но что если нам нужно использовать код, написанный на C++? В случае написания приложения на Objective-C есть несколько способов прикрутить код на C++. Самый простой — изменить расширение файла реализации класса (*.m на *.mm) и использовать Objective-C++* как обёртку для C++. Оказывается, такое решение подойдёт и для Swift:

  1. В проект на Swift нужно добавить исходники на C++**
  2. Написать классы-обёртки для классов C++, используя Objective-C++
  3. Прописать заголовки обёрток в мостовом заголовочном файле (bridging header).
  4. Использовать классы-обёртки в Swift.

* Objective-C++ — это код, который совмещает использование классов C++ и Objective-C.

** На самом деле, конечно, необязательно добавлять исходники, можно добавить, например, статическую библиотеку, то есть *.a-файл.


Как это сделать?

Сейчас мы напишем простой класс на C++ с одним полем title типа string и двумя методами — getTitle() и setTitle(string) и попробуем использовать его в Swift.

Для начала откроем Xcode, выберем File → New → Project. В появившемся окне выберем OS X → Application → Command Line Tool. Нужно задать имя проекта — пусть это будет testApp. Язык — Swift. Сохраним проект в любом месте. Теперь мы в рабочей зоне проекта. Добавим файл C++ с помощью пункта меню File → New → File → C++ File. Назовём его CppClass, не снимая флажок Also create a header file. После сохранения файла Xcode, скорее всего, спросит, хотим ли мы создать заголовок-мост. Мы хотим, поэтому нажмём Create Bridging Header. Если же не спросит, то нужно перейти в настройки проекта, выбрать Target: testApp, Build Settings → Swift Compiler → Code Generation → Objective-C Bridging Header и удалить значение в этой строке, после чего пересоздать файл C++ (либо создать мост вручную и указать в этой строке его имя).

Наконец, откроем файл CppClass.hpp и вместо #include <stdio.h> вставим:

#include <string>
class CppClass {
public:
 CppClass();
 CppClass(const std::string &t);
 ~CppClass();
 
public:
 void setTitle(const std::string &t);
 const std::string &getTitle();
 
private:
 std::string title;

};

Теперь добавим реализацию класса в файл CppClass.cpp:

#include "CppClass.hpp"
CppClass::CppClass() {}
CppClass::CppClass(const std::string &t): title(t) {}
CppClass::~CppClass() {}
void CppClass::setTitle(const std::string &t)
{
 title = t;
}
const std::string &CppClass::getTitle()
{
 return title;
}

Создадим новый файл: File → New → File → OS X → Objective-C File. Назовём его CppClassWrapper. Теперь важно изменить его расширение с *.m на *.mm.

Пропишем в мостовом заголовке объявление нашего нового класса CppClassWrapper, который мы будем использовать в коде на языке Swift. А именно, добавим в файл testApp-Bridging-Header.h следующие строки:

#import <Foundation/Foundation.h>

// Объявляем наш класс в качестве наследника класса NSObject
@interface CppClassWrapper : NSObject

// Объявляем инициализатор (то, что в C++ называется конструктором)
- (instancetype)initWithTitle:(NSString*)t;

// Объявляем методы
- (NSString*)getTitle;
- (void)setTitle:(NSString*)t;
@end

Вернёмся к только что созданному файлу CppClassWrapper.mm. Удалим из него всё лишнее и напишем следующий код:

#import "testApp-Bridging-Header.h"
#include "CppClass.hpp"

// Добавим в класс CppClassWrapper поле-указатель на экземпляр класса CppClass.
@interface CppClassWrapper()
@property CppClass *cppItem;
@end

@implementation CppClassWrapper

// При вызове инициализатора из Swift с параметром t типа NSString вызывается конструктор класса CppClass
// с аргументом — строкой в стиле C. Мы должны выполнить преобразование типа NSString к типу const char*
// Для этого пользуемся cStringUsingEncoding
- (instancetype)initWithTitle:(NSString *)t {
    // Сначала вызываем инициализатор класса-родителя
    if (self = [super init]) {
        self.cppItem = new CppClass(std::string([t cStringUsingEncoding:NSUTF8StringEncoding]));
    }
    return self;
}

// Делаем всё то же самое для методов
- (NSString*)getTitle {
    return [NSString stringWithUTF8String:self.cppItem -> getTitle().c_str()];
}

- (void)setTitle:(NSString *)t {
    self.cppItem -> setTitle(std::string([t cStringUsingEncoding:NSUTF8StringEncoding]));
}

@end

Таким образом, мы «обернули» вызовы методов C++ кодом на Objective-C, обеспечив полную совместимость типов данных в Objective-C/Swift и C++. Теперь мы можем проверить работоспособность класса-обёртки.

Откроем файл main.swift и удалим из него всё лишнее. Добавим:

let wrapperItem = CppClassWrapper(title: "Init test text for cpp item wrapper class")
print("Title: \(wrapperItem.getTitle())")
wrapperItem.setTitle("Just yet another test text setted after cpp item wrapper class init")
print("Title: \(wrapperItem.getTitle())")

После сборки и запуска проекта мы увидим в консоли:

Title: Init test text for cpp item wrapper class
Title: Just yet another test text setted after cpp item wrapper class init
Program ended with exit code: 0

Всё работает.

⚠️ **GitHub.com Fallback** ⚠️