Хабрахабр

[Перевод] Как встроить С-библиотеку в Swift-фреймворк

Новинка принесла не только новые возможности и функции, но и проблемы — тем, кто хотел пользоваться старыми добрыми C-библиотеками. В 2014 году был представлен Swift, новый язык для разработки приложений экосистемы Apple. Существует несколько способов её решения; в данном случае я объясню, как сделать это при помощи clang explicit-модулей. В этой статье я рассмотрю одну из них — бандлинг C-библиотеки в Swift-фреймворк.

Если вы хотите сразу увидеть результат, полностью проект можно посмотреть здесь.
Для примера мы возьмём внешнюю C-библиотеку libgif и встроим её в наш Swift-фреймворк GifSwift.

Прежде чем внедрять в наш проект библиотеку libgif, её нужно собрать из исходников.

  1. Скачиваем последнюю версию тарболла здесь.
  2. Распаковываем архив, при помощи консоли заходим в папку и запускаем:

    ./configure && make check

    Примечание: для простоты мы собираем библиотеку для платформы x86-64, а потому она будет работать только в iOS-симуляторе или на macOS. Построение мультиархитектурной статической библиотеки – отдельная тема, которой я не касаюсь в этой статье. Полезные инструкции вы найдёте здесь.

  3. Если всё пройдёт без ошибок, файлы библиотеки можно будет найти в $/lib/.libs. Нас интересуют два файла:

    lib/.libs/libgif.a # Статическая библиотека
    lib/gif_lib.h # Интерфейс

Теперь настроим проект под наши нужды.

  1. Создаём новый проект при помощи шаблона Cocoa Touch Framework, даём ему имя GifSwift.
  2. Добавляем созданные нами файлы библиотеки libgif в отдельную группу внутри проекта.
  3. Добавляем в проект новый таргет для тестового приложения, чтобы посмотреть результат.

Итоговая структура проекта должна выглядеть примерно так:

Для того чтобы импортировать C-библиотеку в Swift, мы должны описать её как модуль. Описание представляет собой файл .modulemap, содержащий список заголовочных файлов для импорта и статических библиотек для линковки. Полученный модуль может быть импортирован в Swift или Objective-C-код (при помощи @import).

Он отлично подходит, если вы создаёте внутренний фреймворк или просто разбиваете своё приложение на модули. Этот способ импорта библиотеки во фреймворк будет работать в большинстве случаев (более подробно об этом подходе читайте здесь). Например, он неэффективен в том случае, если вы хотите передать свою библиотеку кому-то при помощи Carthage, Cocoapods или в виду бинарного артефакта. Но такой способ также имеет и недостатки. Причина в том, что получившийся фреймворк в общем случае не портируем, поскольку при компиляции он привязывается к конкретному расположению заголовочных файлов и библиотек из module map на вашем компьютере.

Чтобы обойти эти ограничения, воспользуемся ещё одним способом — explicit-модулем для библиотеки. Еxplicit-модуль — это модуль, который объявляется подмодулем при помощи ключевого слова explicit, помещается в родительский модуль и не импортируется автоматически. Он работает аналогично *_Private.h для фреймворков Objective-C. Если вы хотите использовать объявленные в нём API, необходимо импортировать модуль явно (explicitly).

Для этого нам нужно провести переопределение сгенерированного XCode-модуля. Мы создаём явный модуль для C-библиотеки внутри фреймворка. Также обратите внимание на то, что мы не указываем библиотеку libgif.a для линковки (link gif), а вместо этого сделаем это прямо в проекте, используя интерфейс XCode.

Примечание: узнать больше об explicit-модулях можно по ссылке

  1. Добавляем в корневую папку проекта файл с названием GifSwift.modulemap:

    framework module GifSwift { umbrella header "GifSwift.h" explicit module CLibgif { private header "gif_lib.h" } export *
    }

    Этот файл содержит спецификацию для явного модуля CLibgif и состоит из одного объявленного заголовочного файла (поскольку в нашей библиотеке как раз один такой). Файл загружается в получившийся модуль для фреймворка.

  2. Файл с описанием модуля не нужно добавлять в состав фреймворка, но он должен быть указан в настройках таргета:

    Build Settings — Packaging — Module Map (MODULEMAP_FILE)
    =
    $SRCROOT/GifSwift/GifSwift.modulemap

  3. libgif-файлы должны быть добавлены в таргет фреймворка в виде приватного хедера (gif_lib.h) и статической библиотеки (libgif.a). Обратите внимание на то, что заголовочный файл для C-библиотеки добавлен в таргет как private. Это необходимо для нашего explicit-модуля. Ничто не мешает добавить этот заголовочный файл как public, но наша задача – скрыть детали реализации как можно более простыми средствами.

  4. Теперь можно импортировать явный модуль внутри фреймворка при помощи import GifSwift.CLibgif

Теперь можно заняться интерфейсом нашего фреймворка. Достаточно одного класса, представляющего собой гифку с парой свойств:

import Foundation
import GifSwift.CLibgif public class GifFile { private let path: URL private let fileHandlePtr: UnsafeMutablePointer<GifFileType> private var fileHandle: GifFileType { return self.fileHandlePtr.pointee } deinit { DGifCloseFile(self.fileHandlePtr, nil) } // MARK: - API public init?(path: URL) { self.path = path let errorCode = UnsafeMutablePointer<Int32>.allocate(capacity: 1) if let handle = path.path.withCString({ DGifOpenFileName($0, errorCode) }) { self.fileHandlePtr = handle DGifSlurp(handle) } else { debugPrint("Error opening file \(errorCode.pointee)") return nil } } public var size: CGSize { return CGSize(width: Double(fileHandle.SWidth), height: Double(fileHandle.SHeight)) } public var imagesCount: Int { return Int(fileHandle.ImageCount) }
}

GifFile.swift оборачивает низкоуровневые программные интерфейсы для обработки файлов и получает доступ к некоторым свойствам, отображая их на более удобные типы Foundation.
Для того чтобы протестировать нашу библиотеку, я добавил в проект файл cat.gif:

import UIKit
import GifSwift class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() if let file = GifFile(path: Bundle.main.url(forResource: "cat", withExtension: "gif")!) { debugPrint("Image has size: \(file.size) and contains \(file.imagesCount) images") } }
}

При запуске этого кода в консоли мы увидим следующее:

0, 208. "Image has size: (250. 0) and contains 44 images"


Получившийся фреймворк содержит всё необходимое для использования, имеет Swift-интерфейс и по умолчанию скрывает C-код от клиентов. Впрочем, это не совсем правда. Как я писал выше, импортируя GifSwift.CLibgif, вы получите доступ ко всем закрытым модулям, однако по умолчанию такого метода инкапсуляции достаточно, чтобы скрыть детали реализации фреймворка.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть