- PVSM.RU - https://www.pvsm.ru -

В 2014 году был представлен Swift, новый язык для разработки приложений экосистемы Apple. Новинка принесла не только новые возможности и функции, но и проблемы — тем, кто хотел пользоваться старыми добрыми C-библиотеками. В этой статье я рассмотрю одну из них — бандлинг C-библиотеки в Swift-фреймворк. Существует несколько способов её решения; в данном случае я объясню, как сделать это при помощи clang explicit-модулей.
Для примера мы возьмём внешнюю C-библиотеку libgif [1] и встроим её в наш Swift-фреймворк GifSwift. Если вы хотите сразу увидеть результат, полностью проект можно посмотреть здесь [2].
Прежде чем внедрять в наш проект библиотеку libgif, её нужно собрать из исходников.
./configure && make check
Примечание: для простоты мы собираем библиотеку для платформы x86-64, а потому она будет работать только в iOS-симуляторе или на macOS. Построение мультиархитектурной статической библиотеки – отдельная тема, которой я не касаюсь в этой статье. Полезные инструкции вы найдёте здесь [4].
${lib_gif_source}/lib/.libs. Нас интересуют два файла:
lib/.libs/libgif.a # Статическая библиотека
lib/gif_lib.h # Интерфейс
Теперь настроим проект под наши нужды.
Итоговая структура проекта должна выглядеть примерно так:

Для того чтобы импортировать C-библиотеку в Swift, мы должны описать её как модуль [5]. Описание представляет собой файл .modulemap, содержащий список заголовочных файлов для импорта и статических библиотек для линковки. Полученный модуль может быть импортирован в Swift или Objective-C-код (при помощи @import).
Этот способ импорта библиотеки во фреймворк будет работать в большинстве случаев (более подробно об этом подходе читайте здесь [6]). Он отлично подходит, если вы создаёте внутренний фреймворк или просто разбиваете своё приложение на модули. Но такой способ также имеет и недостатки. Например, он неэффективен в том случае, если вы хотите передать свою библиотеку кому-то при помощи Carthage, Cocoapods или в виду бинарного артефакта. Причина в том, что получившийся фреймворк в общем случае не портируем, поскольку при компиляции он привязывается к конкретному расположению заголовочных файлов и библиотек из module map на вашем компьютере.
Чтобы обойти эти ограничения, воспользуемся ещё одним способом — explicit-модулем для библиотеки. Еxplicit-модуль — это модуль, который объявляется подмодулем при помощи ключевого слова explicit, помещается в родительский модуль и не импортируется автоматически. Он работает аналогично *_Private.h для фреймворков Objective-C. Если вы хотите использовать объявленные в нём API, необходимо импортировать модуль явно (explicitly).
Мы создаём явный модуль для C-библиотеки внутри фреймворка. Для этого нам нужно провести переопределение сгенерированного XCode-модуля. Также обратите внимание на то, что мы не указываем библиотеку libgif.a для линковки (link gif), а вместо этого сделаем это прямо в проекте, используя интерфейс XCode.
Примечание: узнать больше об explicit-модулях можно по ссылке [5]
framework module GifSwift {
umbrella header "GifSwift.h"
explicit module CLibgif {
private header "gif_lib.h"
}
export *
}
Этот файл содержит спецификацию для явного модуля CLibgif и состоит из одного объявленного заголовочного файла (поскольку в нашей библиотеке как раз один такой). Файл загружается в получившийся модуль для фреймворка.
Build Settings — Packaging — Module Map (MODULEMAP_FILE)
=
$SRCROOT/GifSwift/GifSwift.modulemap
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")
}
}
}
При запуске этого кода в консоли мы увидим следующее:
"Image has size: (250.0, 208.0) and contains 44 images"
Получившийся фреймворк содержит всё необходимое для использования, имеет Swift-интерфейс и по умолчанию скрывает C-код от клиентов. Впрочем, это не совсем правда. Как я писал выше, импортируя GifSwift.CLibgif, вы получите доступ ко всем закрытым модулям, однако по умолчанию такого метода инкапсуляции достаточно, чтобы скрыть детали реализации фреймворка.
Автор: lemsergey
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/304830
Ссылки в тексте:
[1] libgif: http://giflib.sourceforge.net/
[2] здесь: https://github.com/turbulem/GifSwift
[3] здесь: https://sourceforge.net/projects/giflib/files/
[4] здесь: https://medium.com/@hassanahmedkhan/a-noobs-guide-to-creating-a-fat-library-for-ios-bafe8452b84b
[5] модуль: https://clang.llvm.org/docs/Modules.html
[6] здесь: https://medium.com/swift-and-ios-writing/using-a-c-library-inside-a-swift-framework-d041d7b701d9
[7] Источник: https://habr.com/post/435650/?utm_campaign=435650
Нажмите здесь для печати.