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

Часть 2. Делим наш «pod» на модули. Используем чужой «pod» для разработки своего

Введение

Продолжая разрабатывать «pod» в определённый момент приходит понимание, что проект становится большим и похож на лапшу состоит из нескольких логических частей. Чем раньше приходит такое понимание, тем проще выделить различные сущности одного проекта в отдельные блоки. Например, одна часть проекта отвечает за установку соединения с сервером, другая сериализует объекты в JSON и наоборот, третья взаимодействует с UI и т.п. Каждую такую сущность можно выделить отдельным модулем, называемым «subspec» (для простоты изложения далее буду использовать слово «модуль»). Такой подход не только поможет гибче управлять разработкой «pod»'а, но так же даст пользователям вашего «pod»'а возможность использовать только нужные им модули, не захламляя свои проекты ненужным кодом.

Тестовый проект

Разрабатывать «pod» сам по себе занятие как минимум странное. Обычно его разработка ведётся в контексте какого-то проекта. А ещё лучше, когда для разработки «pod»'а ведётся специальный проект, покрытый тестами, и с полезными примерами использования в коде. Создадим же наш тестовый проект «Single View Application» в каталоге ~/Documents/PodSample/Project. После чего подключим к нему наш «pod». Для этого создаём в этом же каталоге файл Podfile. Результат должен выглядеть примерно так:
Часть 2. Делим наш «pod» на модули. Используем чужой «pod» для разработки своего
Заполняем Podfile:

platform :ios, '7.0'
pod 'MyLibrary', :path => '~/Documents/PodSample/MyLibrary.podspec'

Являясь разработчиком «pod»'а мы можем упростить себе жизнь и после имени явно указать путь к спецификации — файлу .podspec. В результате установщик не полезет по репозиториям «spec»'ов, а возьмёт явно указанную спецификацию по указанному пути. В принципе, можно не указывать имя файла MyLibrary.podspec — достаточно указать каталог где находится файл спецификации вашего «pod»'а, установщик сам его найдёт. Так же ничто не мешает использовать относительные пути, например:

platform :ios, '7.0'
pod 'MyLibrary', :path => '..'

Далее закрываем XCode, или хотя бы тестовый проект MyPodExample.xcodeproj.
Устанавливаем наш «pod»:

$ cd ~/Documents/PodSample/Project/
$ pod install
Analyzing dependencies
Fetching podspec for `MyLibrary` from `~/Documents/PodSample/MyLibrary.podspec`
Downloading dependencies
Using MyLibrary (0.0.1)
Generating Pods project
Integrating client project

Установщик генерирует нам «workspace», куда добавляет тестовый проект и новый проект «Pods». Открываем workspace MyPodExample.xcworkspace и наблюдаем нечто похожее:
Часть 2. Делим наш «pod» на модули. Используем чужой «pod» для разработки своего

Выделяем модули

Предположим наш полезный «pod» уже умеет соединяться с сервером, обрабатывать JSON и хранить некие данные в своём внутреннем хранилище. Создадим набор классов и объявим немного методов в их публичных интерфейсах. Результат лежит на GitHub с тэгом mixed [1].
Разделение «pod»'а на модули проходит в два этапа. Сначала приводится в порядок код, для чего мы разносим [2] различные сущности по собственным файлам. После чего обновляем «spec» — описываем получившиеся модули в блоках «subspec» и проставляем их зависимости друг от друга параметром «dependency»:

Pod::Spec.new do |s|
  s.name            = "MyLibrary"
  s.version         = "0.0.2"
  s.summary         = "Example of creating own pod."
  s.homepage        = "https://github.com/username/MyCustomPod"
  s.license         = { :type => 'MIT', :file => 'LICENSE' }
  s.author          = { "Username" => "username@mail.domain" }
  s.platform        = :ios, 7.0
  s.source          = { :git => "https://github.com/username/MyCustomPod.git", :tag => s.version.to_s }
  s.framework       = 'Foundation'
  s.requires_arc    = true
  s.default_subspec = 'Core' # Модуль по умолчанию называется Core

  s.subspec 'Core' do |core|
    core.source_files        = 'Classes/AKClass.{h,m}'
    core.public_header_files = 'Classes/*.h'
    core.dependency 'MyLibrary/Connection'
    core.dependency 'MyLibrary/Provider'
  end

  s.subspec 'Provider' do |provider|
    provider.source_files = 'Classes/AKProvider.{h,m}'
    provider.frameworks   = 'MapKit', 'CoreData' # Добавлены зависимости от фрэймворков
    provider.platform     = :ios, 5.0 # Этот модуль может запускаться и на iOS 5.0
  end

  s.subspec 'AccessToken' do |access_token|
    access_token.source_files = 'Classes/AKAccessToken.{h,m}'
    access_token.libraries    = 'xml2' # Зависимость от библиотеки
    access_token.xcconfig     = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' } # Где искать заголовочные файлы
  end

  s.subspec 'Parser' do |parser|
    parser.source_files = 'Classes/AKParser.{h,m}'
  end

  s.subspec 'Storage' do |storage|
    storage.source_files = 'Classes/AKStorage.{h,m}'
    storage.dependency 'MyLibrary/AccessToken'
  end

  s.subspec 'Connection' do |connection|
    connection.source_files = 'Classes/AKConnection.{h,m}'
    connection.dependency 'MyLibrary/Storage'
    connection.dependency 'MyLibrary/Parser'
  end
end

Для модуля «Provider» указываем необходимость подключить два фрэймворка — «MapKit» и «CoreData». А для модуля «AccessToken» необходимо подключить библиотеку «xml2», заголовочные файлы искать по указанном пути: $(SDKROOT)/usr/include/libxml2
Говорят [3], что с версии «CocoaPods v0.17» модули больше не наследуют неявно значения «source_files» у базового «spec». Чтобы вернуть данную возможность необходимо завести «Core» модуль который включает базовые исходники необходимые создаваемым модулям.
Так же существует возможность разбивать модули на подмодули:

…
  s.subspec 'Connection' do |сonnection|
    сonnection.source_files = 'Classes/AKConnection.{h,m}'
    сonnection.dependency 'MyLibrary/Storage'
    сonnection.dependency 'MyLibrary/Parser'
    сonnection.subspec 'Cache' do |cache|
      cache.source_files = 'Classes/AKCache/*.{h,m}'
    end
  end
…

Так же можно указать платформу и её версию на которых модуль сможет работать. В данном случае мы указали, что модуль Provider запустится даже на «iOS 5.0».

Проверяем синтаксис:

$ pod spec lint ~/Documents/PodSample/MyLibrary.podspec --quick

 -> MyLibrary (0.0.2)

Analyzed 1 podspec.

MyLibrary.podspec passed validation.

И коммитим изменения в git, ставим тэг и отправляем на GitHub:

$ git add MyLibrary.podspec && git commit -m "Spec sliced on subspecs"
$ git add -A && git commit -m "Sliced code"
$ git tag "0.0.2"
$ git push origin master --tags

Проверяем весь проект:

$ pod spec lint ~/Documents/PodSample/MyLibrary.podspec

 -> MyLibrary (0.0.2)

Analyzed 1 podspec.

MyLibrary.podspec passed validation.

Использование модуля

Чтобы использовать только необходимые модули достаточно их перечислить в Podfile:

platform :ios, '7.0'
pod 'MyLibrary/Storage'
pod 'MyLibrary/Parser'

Если не указать ни одного модуля:

platform :ios, '7.0'
pod 'MyLibrary'

то pod install установит исходники всех модулей. Если же в данном случае нужно установить только исходники «Core» модуля, то в «spec»'е указывается его имя в параметре «default_subspec» — см. пример выше.

Укажем в нашем Podfile зависимость тестового проекта от модуля «Provider» и заодно укажем нужную нам сейчас версию — «0.0.2»:

platform :ios, '7.0'
pod 'MyLibrary/Provider', :path => '..'
pod 'MyLibrary/Connection', :path => '..'

И обновим зависимости проекта:

$ cd ~/Documents/PodSample/Project/
$ pod update
Analyzing dependencies
Fetching podspec for `MyLibrary` from `..`
Fetching podspec for `MyLibrary` from `..`
Downloading dependencies
Installing MyLibrary (0.0.2)
Generating Pods project
Integrating client project

Подключение стороннего «pod»'а

Иногда бывает так, что разработка «pod»'а доходит до момента, когда хочется написать велосипед поиспользовать существующий «pod». Нетрудно догадаться, что для этого достаточно указать зависимости всего «spec»'а или его модуля(ей) от нужных «pod»'ов. Добавим зависимость от AFNetworking в модуль «Connection»:

Pod::Spec.new do |s|
  s.name            = "MyLibrary"
  s.version         = "0.0.3"
  s.summary         = "Example of creating own pod."
  s.homepage        = "https://github.com/username/MyCustomPod"
  s.license         = { :type => 'MIT', :file => 'LICENSE' }
  s.author          = { "Username" => "username@mail.domain" }
  s.platform        = :ios, 7.0
  s.source          = { :git => "https://github.com/username/MyCustomPod.git", :tag => s.version.to_s }
  s.framework       = 'Foundation'
  s.requires_arc    = true
  s.default_subspec = 'Core' # Модуль по умолчанию называется Core

  s.subspec 'Core' do |core|
    core.source_files        = 'Classes/AKClass.{h,m}'
    core.public_header_files = 'Classes/*.h'
    core.dependency 'MyLibrary/Connection'
    core.dependency 'MyLibrary/Provider'
  end

  s.subspec 'Provider' do |provider|
    provider.source_files = 'Classes/AKProvider.{h,m}'
    provider.frameworks   = 'MapKit', 'CoreData' # Добавлены зависимости от фрэймворков
    provider.platform     = :ios, 5.0 # Этот модуль может запускаться и на iOS 5.0
  end

  s.subspec 'AccessToken' do |access_token|
    access_token.source_files = 'Classes/AKAccessToken.{h,m}'
    access_token.libraries    = 'xml2' # Зависимость от библиотеки
    access_token.xcconfig     = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' } # Где искать заголовочные файлы
  end

  s.subspec 'Parser' do |parser|
    parser.source_files = 'Classes/AKParser.{h,m}'
  end

  s.subspec 'Storage' do |storage|
    storage.source_files = 'Classes/AKStorage.{h,m}'
    storage.dependency 'MyLibrary/AccessToken'
  end

  s.subspec 'Connection' do |connection|
    connection.source_files = 'Classes/AKConnection.{h,m}'
    connection.dependency 'MyLibrary/Storage'
    connection.dependency 'MyLibrary/Parser'
    connection.dependency 'AFNetworking' # Добавлена зависимость от внешнего "pod"'а
  end
end

При интеграции могут возникнуть проблемы с версиями платформ, используемых вами и внешними «pod»'ами. Успешно решив их обновляем зависимости (крайне желательно при закрытом XCode или проекте):

$ cd ~/Documents/PodSample/Project/
$ pod update
Analyzing dependencies
Fetching podspec for `MyLibrary` from `..`
Fetching podspec for `MyLibrary` from `..`
Downloading dependencies
Installing AFNetworking (2.0.1)
Installing MyLibrary (0.0.3)
Generating Pods project
Integrating client project

Скачаются и подключатся необходимые «pod»'ы, настроится workspace. Теперь можно в своём «pod»'е использовать силу всех прочих покемонов «pod»'ов!

Продолжение следует.

Запланировано:
Часть 3. Публикация своего «pod»'а. Общий репозиторий и личный.

Автор: Adnako

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ios-development/46547

Ссылки в тексте:

[1] mixed: https://github.com/pomozoff/MyCustomPod/tree/mixed

[2] разносим: https://github.com/pomozoff/MyCustomPod/tree/modules

[3] Говорят: https://github.com/CocoaPods/CocoaPods/blob/master/CHANGELOG.md#breaking

[4] Источник: http://habrahabr.ru/post/198904/