Использование кода и библиотек 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:
- В проект на Swift нужно добавить исходники на C++**
- Написать классы-обёртки для классов C++, используя Objective-C++
- Прописать заголовки обёрток в мостовом заголовочном файле (bridging header).
- Использовать классы-обёртки в 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
Всё работает.