Русский
Русский
English
Статистика
Реклама

Создаем Swift Package на основе Cбиблиотеки

Фото Kira auf der HeideнаUnsplashФото Kira auf der HeideнаUnsplash

Данная статья поможет вам создать свой первый Swift Package. Мы воспользуемся популярной C++ библиотекой для линейной алгебры Eigen, чтобы продемонстрировать, как можно обращаться к ней из Swift. Для простоты, мы портируем только часть возможностей Eigen.


Трудности взаимодействия C++ и Swift

Использование C++ кода из Swift в общем случае достаточно трудная задача. Все сильно зависит от того, какой именно код вы хотите портировать. Данные 2 языка не имеют соответствия API один-к-одному. Для подмножества языка C++ существуют автоматические генераторы Swift интерфейса (например,Scapix,Gluecodium). Они могут помочь вам, если разрабатывая библиотеку, вы готовы следовать некоторым ограничениям, чтобы ваш код просто конвертировался в другие языки. Тем не менее, если вы хотите портировать чужую библиотеку, то, как правило, это будет не просто. В таких ситуациях зачастую ваш единственный выбор: написать обертку вручную.

Команда Swift уже предоставляет interop дляCиObjective-Cв их инструментарии. В то же время,C++ interopтолько запланирован и не имеет четких временных рамок по реализации. Одна из сложно портируемых возможностей C++ шаблоны. Может показаться, что темплейты в C++ и дженерики в Swift схожи. Тем не менее, у них есть важные отличия. На момент написания данной статьи, Swift не поддерживает параметры шаблона не являющиеся типом, template template параметры и variadic параметры. Также, дженерики в Swift определяются для типов параметров, которые соблюдают объявленные ограничения (похоже на C++20 concepts). Также, в C++ шаблоны подставляют конкретный тип в месте вызова шаблона и проверяют поддерживает ли тип используемый синтаксис внутри шаблона.

Итого, если вам нужно портировать C++ библиотеку с обилием шаблонов, то ожидайте сложностей!


Постановка задачи

Давайте попробуем портировать вручную С++ библиотеку Eigen, в которой активно используются шаблоны. Эта популярная библиотека для линейной алгебры содержит определения для матриц, векторов и численных алгоритмов над ними. Базовой стратегией нашей обертки будет: выбрать конкретный тип, обернуть его в Objective-C класс, который будет импортироваться в Swift.

Один из способов импортировать Objective-C API в Swift это добавить C++ библиотеку напрямую в Xcode проект и написатьbridging header. Тем не менее, обычно удобнее, когда обертка компилируется в качестве отдельного модуля. В этом случае, вам понадобится помощь менеджера пакетов. Команда Swift активно продвигаетSwift Package Manager (SPM). Исторически, в SPM отсутствовали некоторые важные возможности, из-за чего многие разработчики не могли перейти на него. Однако, SPM активно улучшался с момента его создания. В Xcode 12, вы можете добавлять в пакет произвольные ресурсы и даже попробовать пакет в Swift playground.

В данной статье мы создадим SPM пакетSwiftyEigen. В качестве конкретного типа мы возьмем вещественную float матрицу с произвольным числом строк и колонок. КлассMatrixбудет иметь конструктор, индексатор и метод вычисляющий обратную матрицу. Полный проект можно найти наGitHub.


Структура проекта

SPM имеет удобный шаблон для создания новой библиотеки:

foo@bar:~$ mkdir SwiftyEigen && cd SwiftyEigenfoo@bar:~/SwiftyEigen$ swift package initfoo@bar:~/SwiftyEigen$ git init && git add . && git commit -m 'Initial commit'

Далее, мы добавляем стороннюю библиотеку (Eigen) в качестве сабмодуля:

foo@bar:~/SwiftyEigen$ git submodule add https://gitlab.com/libeigen/eigen Sources/CPPfoo@bar:~/SwiftyEigen$ cd Sources/CPP && git checkout 3.3.9

Отредактируем манифест нашего пакета,Package.swift:

// swift-tools-version:5.3import PackageDescriptionlet package = Package(    name: "SwiftyEigen",    products: [        .library(            name: "SwiftyEigen",            targets: ["ObjCEigen", "SwiftyEigen"]        )    ],    dependencies: [],    targets: [        .target(            name: "ObjCEigen",            path: "Sources/ObjC",            cxxSettings: [                .headerSearchPath("../CPP/"),                .define("EIGEN_MPL2_ONLY")            ]        ),        .target(            name: "SwiftyEigen",            dependencies: ["ObjCEigen"],            path: "Sources/Swift"        )    ])

Манифест является рецептом для компиляции пакета. Сборочная система Swift соберет два отдельных таргета для Objective-C и Swift кода. SPM не позволяет смешивать несколько языков в одном таргете. Таргет ObjCEigen использует файлы из папкиSources/ObjC, добавляет папкуSources/CPP в header search paths, и опеделяетEIGEN_MPL2_ONLY, чтобы гарантировать лицензию MPL2 при использовании Eigen. ТаргетSwiftyEigen зависит отObjCEigen и использует файлы из папкиSources/Swift.


Ручная обертка

Теперь напишем заголовочный файл для Objective-C класса в папкеSources/ObjCEigen/include:

#pragma once#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface EIGMatrix: NSObject@property (readonly) ptrdiff_t rows;@property (readonly) ptrdiff_t cols;- (instancetype)init NS_UNAVAILABLE;+ (instancetype)matrixWithZeros:(ptrdiff_t)rows cols:(ptrdiff_t)colsNS_SWIFT_NAME(zeros(rows:cols:));+ (instancetype)matrixWithIdentity:(ptrdiff_t)rows cols:(ptrdiff_t)colsNS_SWIFT_NAME(identity(rows:cols:));- (float)valueAtRow:(ptrdiff_t)row col:(ptrdiff_t)colNS_SWIFT_NAME(value(row:col:));- (void)setValue:(float)value row:(ptrdiff_t)row col:(ptrdiff_t)colNS_SWIFT_NAME(setValue(_:row:col:));- (EIGMatrix*)inverse;@endNS_ASSUME_NONNULL_END

У класса есть readonly свойства rows и cols, конструктор для нулевой и единичной матрицы, способы получить и изменить отдельные значения, и метод вычисления обратной матрицы.

Дальше напишем файл реализации вSources/ObjCEigen:

#import "EIGMatrix.h"#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wdocumentation"#import <Eigen/Dense>#pragma clang diagnostic pop#import <iostream>using Matrix = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>;using Map = Eigen::Map<Matrix>;@interface EIGMatrix ()@property (readonly) Matrix matrix;- (instancetype)initWithMatrix:(Matrix)matrix;@end@implementation EIGMatrix- (instancetype)initWithMatrix:(Matrix)matrix {    self = [super init];    _matrix = matrix;    return self;}- (ptrdiff_t)rows {    return _matrix.rows();}- (ptrdiff_t)cols {    return _matrix.cols();}+ (instancetype)matrixWithZeros:(ptrdiff_t)rows cols:(ptrdiff_t)cols {    return [[EIGMatrix alloc] initWithMatrix:Matrix::Zero(rows, cols)];}+ (instancetype)matrixWithIdentity:(ptrdiff_t)rows cols:(ptrdiff_t)cols {    return [[EIGMatrix alloc] initWithMatrix:Matrix::Identity(rows, cols)];}- (float)valueAtRow:(ptrdiff_t)row col:(ptrdiff_t)col {    return _matrix(row, col);}- (void)setValue:(float)value row:(ptrdiff_t)row col:(ptrdiff_t)col {    _matrix(row, col) = value;}- (instancetype)inverse {    const Matrix result = _matrix.inverse();    return [[EIGMatrix alloc] initWithMatrix:result];}- (NSString*)description {    std::stringstream buffer;    buffer << _matrix;    const std::string string = buffer.str();    return [NSString stringWithUTF8String:string.c_str()];}@end

Теперь сделаем Objective-C код видимым из Swift с помощью файла вSources/Swift(смотритеSwift Forums):

@_exported import ObjCEigen

И добавим индексирование для более чистого API:

extension EIGMatrix {    public subscript(row: Int, col: Int) -> Float {        get { return value(row: row, col: col) }        set { setValue(newValue, row: row, col: col) }    }}

Пример использования

Теперь мы можем воспользоваться классом вот так:

import SwiftyEigen// Create a new 3x3 identity matrixlet matrix = EIGMatrix.identity(rows: 3, cols: 3)// Change a specific valuelet row = 0let col = 1matrix[row, col] = -2// Calculate the inverse of a matrixlet inverseMatrix = matrix.inverse()

Наконец, мы можем составить простой проект, который продемонстрирует возможности нашего пакета, SwiftyEigen. Приложение позволит вносить значения в матрицу 2x2 и вычислять обратную матрицу. Для этого, создаем новый iOS проект в Xcode, перетаскиваем папку с пакетом из Finder в project navigator, чтобы добавить локальную зависимость, и добавляем фреймворк SwiftyEigen в общие настройки проекта. Далее пишем UI и радуемся:

Смотрите полный проект наGitHub.


Ссылки

Спасибо за внимание!

Источник: habr.com
К списку статей
Опубликовано: 13.01.2021 02:07:50
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка под ios

C++

Xcode

Swift

Ios development

Ios разработка

Spm

Разработка мобильных приложений

Cpp

Swift package manager

Категории

Последние комментарии

© 2006-2021, personeltest.ru