iOS разработчик
0,0
рейтинг
18 ноября 2015 в 13:47

Разработка → Использование Carthage для управления зависимостями перевод tutorial



iOS разработчики это — фантастическое сообщество, которое предлагает широкий спектр доступных сторонних библиотек.

Если вы разрабатывали для этой платформы, тогда, скорее всего пользовались хотя бы одной из этих библиотек. Будь это AFNetworking, SDWebImage, SSKeychain или CocoaLumberjack, и уже должны понимать важность использования чужого кода, если вы не любите «изобретать свой велосипед».

Есть CocoaPods. Если по какой-то причине вы не знакомы с этим прекрасным инструментом, так это самый популярный менеджер зависимостей, который упрощает процесс интеграции такого рода библиотек в ваш проект.

CocoaPods широко используется в iOS сообществе, и даже Google использует его для распространения различных iOS SDK.

Хоть CocoaPods и потрясающее средство для управления зависимостями, есть и другие варианты. Carthage является одним из таких вариантов; это безжалостно простой менеджер зависимостей для Mac и iOS, созданный группой разработчиков из Github.

Это был первый менеджер зависимостей для работы с Swift; фактически, сам Carthage написан на Swift'e! Он использует исключительно динамические фреймворки вместо статических библиотек – это только способ распространения двоичных файлов Swift, которые поддерживаются в iOS 8 и выше.

В этой статье о Carthage узнаете следующее:
  • Зачем и когда использовать менеджер зависимостей, и чем отличие Carthage от других менеджеров зависимостей
  • Как установить Carthage
  • Объявление Ваших зависимостей, установка и интегрирование их в проект
  • Обновление зависимостей к различным версиям
  • Создадите приложение, которое для поиска определения загаданного слова использует DuckDuckGo API

Примечание: Это статья для тех кто уже знаком с основами разработки под iOS и с языком Swift, и знаком с средой разработки Xcode, и умеет работать с командной строкой.

Давайте начнем


Сперва, загрузите стартовый проект над которым будите работать.

Он включает в себя «скелет» DuckDuckDefine, простой инструмент для поиска определений слов с помощью DuckDuckGo API. На данный момент есть всего одна проблема: он еще фактически не выполняет поиск!

Откройте проект в Xcode и ознакомьтесь с ним. Обратите внимание на два контроллера: SearchViewController предоставляет панель поиска для пользователя для выполнения поиска, и DefinitionViewController отображает определение искомого термина.

Главная работа происходит в файле DuckDuckGo.swift  — или по крайней мере она будут к тому времени там, когда закончите работу над проектом! В данный момент, метод performSearch(_:completion:) ленивый, бесполезный блок кода.

Чтобы выполнить поиск, вам будет нужно сделать две вещи:
  • Сделать запрос с помощью DuckDuckGo API
  • Проанализировать/разобрать данные в ответ на запрос которые получите в JSON формате

Существует ряд библиотек с открытым исходным кодом, которые могут помочь вам справится с этими двумя задачами. Alamofire большая Swift библиотека, которая упрощает создание запросов, и SwiftyJSON позволяет получить более приятные впечатления от работы с JSON в Swift. И угадайте, что?

Вы будете использовать Carthage, чтобы добавить эти зависимости в проект.

Управление зависимостями


Чтобы добавить Alamofire и SwiftyJSON в проект, можно конечно просто посетить их соответствующих страниц на Github, загрузить zip-файл с исходным кодом и скопировать их в проект. Итак, зачем использовать инструменты такие как Carthage?

Менеджеры зависимостей выполняют ряд полезных функций:
  • Они упрощают и стандартизируют процесс выборки стороннего кода и включения его в ваш проект. Без такого инструмента это можно сделать вручную, путем копирования файлов исходного кода, путем копирования скомпилированных двоичных файлов или используя механизм как подмодули Git.
  • Они облегчают обновление сторонних библиотек. Представьте, что Вам нужно посетить каждую из зависимостей на github, загрузить исходный код, и скопировать его в ваш проект каждый раз, когда есть обновление. Почему бы не сделать это для себя?
  • Они выбирают соответствующие и совместимые версии зависимостей, которые вы используете. Например, если вы вручную добавляете зависимости, ситуация может оказаться непростой, когда они зависят друг от друга или совместно используют другую зависимость.

image

Большинство менеджеров зависимостей создадут граф зависимости из зависимостей Вашего проекта и каждую из этих подзависимостей, и затем определят лучшую версию каждой для использования.

Вы можете сделать то же самое вручную, но какой ценой? Вашим здравомыслием?

Carthage vs CocoaPods


Так как именно Carthage отличается от CocoaPods, и зачем вы используете другое средство помимо самого популярного менеджера зависимостей для iOS?

Разработчики Carthage посчитали, что хотя CocoaPods обычно прост в использовании, это не так. Главные принципы работы Carthage заключается в том, то, что этот инструмент должен быть безжалостно простым.

CocoaPods добавляет сложность и к разработке приложений и к процессам дистрибуции библиотеки:
  • Библиотеки должны создавать, обновлять и содержать Podspec файлы (или разработчики приложений должны писать свои собственные файлы, если их нет в библиотеке).
  • При добавлении в проект «pod», CocoaPods создает новый Xcode проект с таргетом для каждого отдельного pod. Затем необходимо использовать workspace и полагаться, что CocoaPods проект работает правильно. Поговорим о большом количестве дополнительных параметров настройки сборки для поддержки.
  • Хранилище данных CocoaPods Podspecs централизовано, которое может быть проблематичным, если по некоторым причинам оно исчезло или стало недоступным.

image

Цель проекта Carthage состоит в том, чтобы предоставить более простой инструмент, чем CocoaPods; тот, который проще понять, проще в обслуживании и чтобы от был более гибкий.

Это достигается несколькими способами:

  • Carthage не изменяет ваш проект и не вынуждает Вас использовать workspace.
  • Нет необходимости для Podspecs или централизованного хранилища данных, чтобы авторы библиотеки представляли свои pods. Ваш проект может быть разработан как фреймворк, он может быть использован с Carthage. Он использует существующую информацию прямо из Git и Xcode.
  • Carthage не делает ничего волшебного; вы всегда контролируете ситуацию. Вручную добавляете зависимости в проект и Carthage, извлекаете и создаете их.

Примечание: Carthage использует динамические фреймворки, чтобы достигнуть простоты. Это означает, что ваш проект должен поддерживать iOS 8 или более поздней версии.

Теперь, когда вы знаете основные понятия, пришло время узнать на практике каким же является Carthage.

Установка Carthage


В “ядре” Carthage есть инструмент для командной строки, который помогает с выборкой и созданием зависимостей.

Существует два способа установки: загрузите и запустите .pkg инсталлятор последней версии, или используйте диспетчер пакетов Homebrew. Точно так же, как Carthage помогает установить пакеты для разработки Cocoa, Homebrew помогает установить полезные инструменты для Unix и ОS Х.

Сегодня вы будете использовать инсталлятор .pkg. Загрузите последнюю версию Carthage и дважды щелкните по Carthage.pkg, чтобы запустить установщик. Нажмите Continue, выберите место для установки, затем нажмите кнопку Continue еще раз, и наконец нажмите Install.

Примечание: При попытке запустить установщик, вы можете увидеть сообщение о том, что “файл Carthage.pkg  не может быть открыт, потому что он от неизвестного разработчика.” Если это так, щелкните кнопкой мыши на программу установки и выберите "Open" из контекстного меню.

Готово! Чтобы проверить, что Carthage правильно установлен, вы можете просто выполнить команду:
carthage version

Если все прошло по плану, вы увидите номер версии Carthage, который был установлен.
Примечание: На момент написания этой статьи текущая версия Carthage была 0.10.0.

Далее для Carthage необходимо указать, какие библиотеки нужно установить. Это делается с помощью файла Cartfile.

image

Создание файла Cartfile


Cartfile — простой текстовый файл, который описывает зависимости Вашего проекта для Carthage, таким образом, он определяет, что устанавливать. Каждая строка в Cartfile указывает, откуда извлечь зависимость, имя зависимости, и при необходимости, какая версия зависимости используется. Cartfile аналогичный файлу Podfile в CocoaPods.

Откройте Terminal и перейдите в корневой каталог вашего проекта (каталог, содержащий ваши файлы .xcodeproj) с помощью команды cd:
cd ~/Path/To/Starter/Project

Создайте пустой файл Cartfile командой touch:
touch Cartfile

И затем откройте этот файл в Xcode для редактирования:
open -a Xcode Cartfile

Если вы знакомы с другим текстовым редактором, как Vim, то не стесняйтесь использовать его. Однако, не используйте TextEdit для редактирования файла; при помощи TextEdit слишком просто использовать так называемые “автоматические кавычки” вместо прямых кавычек, и они приведут в беспорядок работу Carthage.

Добавьте следующие строки в файл Cartfile и сохранить его:
github "Alamofire/Alamofire" == 2.0
github "SwiftyJSON/SwiftyJSON" ~> 2.3.0

Они сообщают Carthage, что ваш проект требует Alamofire версии 2.0 и последнюю версию SwiftyJSON, которая совместима с версией 2.3.0.

Формат Cartfile


Cartfiles написаны в подмножестве OGDL: Ordered Graph Data Language. Это звучит необычно, но это действительно очень просто. Есть два ключевых фрагмента информации в каждой строке Cartfile:
  • Источник зависимости: Он сообщает Carthage, откуда извлечь зависимость. Carthage поддерживает два типа источников:

  1. github  для размещенных проектов Github (подсказка в имени!). Вы определяете проект Github в формате Username/ProjectName, как сделали это с Cartfile выше.
  2. git  для универсальных репозитариев Git, размещенных в другом месте. Ключевое слово git сопровождается путем к репозиторию git, будь то удаленным URL-адрес, используя git://http://, или ssh://, или локальным путем к репозиторию git на компьютере разработчика.

  • Версия зависимости: Это — то, как вы сообщаете Carthage, какую версию хотели бы использовать. В вашем распоряжении есть несколько вариантов, в зависимости от того, насколько точными вы хотите быть:

  1. == 1.0 означает “Использовать именно версию 1.0”
  2. >= 1.0 означает “Использовать версии 1.0 или выше”
  3. ~> 1.0 означает «использовать любую версию, которая совместима с 1.0», что означает, по сути, Вы можете использовать любую версию вплоть до следующей версии.
  4. Если укажете ~> 1.7.5, то любые версии от 1.7.5 и до, но не включая 2.0, считается совместимой.
  5. Кроме того, если укажете ~ > 2.0, то Carthage будет использовать версии 2.0 или более поздней версии, но меньше чем 3.0.
  6. Совместимость основывается на Семантическом Управлении версиями – для получения дополнительной информации ознакомьтесь с нашим руководством Использование CocoaPods с помощью Swift.

  • branch name / tag name / commit name означает “Используйте git branch / tag / commit”. Например, можно указать master, или commit как 5c8a74a.


Если не укажете версию, то Carthage будет просто использовать последнюю версию, которая совместима с другими зависимостями. Вы можете увидеть примеры каждого из этих вариантов на практике в Carthage в файле Carthage README

Создание зависимостей


Так как теперь у вас есть готовый Cartfile файл, пришло время его использовать и установить некоторые зависимости!

Закройте файл Cartfile в Xcode и вернитесь в Terminal и выполните следующую команду:
carthage update --platform iOS

Это дает Carthage команду клонировать репозитарии с Git с указанных путей в Cartfile, и затем создать для каждой зависимости фреймворк. Вы должны получить результат, который показывает, что произошло:
*** Fetching SwiftyJSON
*** Fetching Alamofire
*** Checking out Alamofire at "2.0.0"
*** Checking out SwiftyJSON at "2.3.0"
*** xcodebuild output can be found in /var/folders/h8/qp5163ln7mz2d7_l_yn6hd0m0000gp/T/carthage-xcodebuild.HBRNcq.log
*** Building scheme "Alamofire iOS" in Alamofire.xcworkspace

Параметры iOS гарантирует, что фреймворк создан только для iOS. Если не указать платформу, то по умолчанию Carthage будет создавать для всех фреймворков (часто это Mac и iOS) поддерживаемые библиотекой.

Если хотите взглянуть на дополнительные опции, можно запустить справку об обновление carthage.

По умолчанию, Carthage выполнит отладку и создаст новый каталог под названием Carthage в том же месте где находится Cartfile файл. Теперь откройте этот каталог, с помощью команды:
open Carthage

Увидите всплывающее окно Finder, который содержит два каталога: Build и Checkouts. Воспользуйтесь моментом, чтобы посмотреть, что Carthage создал для вас.

image

Artifacts


Если вы знакомы с CocoaPods, вы знаете, что он вносит некоторые изменения в ваш проект и связывает его вместе со специальным проектом Pods в Xcode workspace.

Carthage немного отличается. Он просто проверяет код для ваших зависимостей, и встраивает его в фреймворки, и затем вам решать нужно ли интегрировать его в проект или нет. Похоже, у Вас будет больше дополнительной работы, но это выгодно. Это займет всего несколько минут, но вы будете более осведомлены об изменениях в проекте.

Когда вы выполняете обновление carthage, Carthage создает несколько файлов и каталогов для вас:

image

  • Cartfile.resolved: Этот файл создается в качестве дополнения к Cartfile. Он определяет, какие именно версии зависимостей Carthage выбрал для установки. Настоятельно рекомендуется передать этот файл Вашему каталогу управления версиями, потому что его присутствие гарантирует, что другие разработчики могут быстро приступить к работе с помощью точно же такой версии зависимости, как и у Вас.
  • Каталог Carthage содержит два подкаталога:

  • Build: Здесь содержится встроенный фреймворк для каждой зависимости. Они могут быть интегрированы в ваш проект, который вы создадите в ближайшее время. Каждый фреймворк либо создан из исходников или загружен со страницы проекта “Releases” на Github.
  • Checkouts: Здесь, Carthage проверяет исходный код для каждой зависимости, которую можно встроить в фреймворк. Carthage поддерживает свой собственный внутренний кэш репозиториев зависимости, таким образом, он не должен клонировать тот же источник несколько раз для разных проектов.

Скопируете ли каталоги Build и Checkouts в свой репозиторий, полностью ваше дело. Это не обязательно, но это означает, что тот, кто клонирует Ваш репозиторий, всегда будет иметь двоичные файлы и/или источник для каждой зависимости в распоряжении.

Это может быть полезным своего рода страховым полисом, например, если Github недоступен, или репозиторий исходного кода полностью удален тогда, у Вас было бы чистое резервное копирование. Не изменяйте код в папке Checkouts, потому что ее содержание может быть перезаписано в любой момент будущим обновлением carthage или командой checkout, и ваш труд исчезнет в мгновение ока.

Если модификации к Вашим зависимостям необходимы, можете выполнить обновление carthage, используя опцию подмодулей.

При использовании этой опции Carthage добавляет каждую зависимость в папку Checkouts к вашему Git репозиторию как подмодуль, то есть, можете изменить источник зависимостей, фиксировать и перемещать те изменения в другом месте без страха перезаписи.

Примечание: Если другим пользователям необходимо использовать ваш проект, и вы не закоммитили созданные фреймворки, то они должны будут выполнить начальную загрузку carthage после клонирования проекта.

Команда начальной загрузки загрузит и создаст точные версии из ваших зависимостей, которые определены в Cartfile.resolved.
С другой стороны обновление carthage, обновило бы проект для использования новейших совместимых версий для каждой зависимости, которая может быть необходимой.


Теперь, как насчет того, чтобы использовать эти артефакты, над созданием которых так упорно работали?

Добавление фреймворков в проект


Откройте стартовый проект в Xcode, если вы этого еще не сделали, и щелкните по project DuckDuckDefine в Навигаторе Проекта. Выберите таргет DuckDuckDefine, затем выберите вкладку «General» наверху и прокрутите вниз к разделу Linked Frameworks и Libraries.

В окне Finder перейдите в каталог Carthage и затем в Build\iOS. Теперь, переместите Alamofire.framework и SwiftyJSON.framework в раздел Linked Frameworks и Libraries в Xcode:

image

Это сообщает что нужно связать ваше приложение с этими фреймворкам, позволяя использовать их в собственном коде.
Далее перейдите в Build Phases и добавьте новый Run Script. Добавьте следующую команду:
/usr/local/bin/carthage copy-frameworks


Нажмите на + под Input Files и добавьте запись для каждой платформы:
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/SwiftyJSON.framework


Строго говоря, этот этап построения не требуется для запуска проекта. Однако, это — хитрое обходное решение проблемы которая может возникнуть когда вы будите отправлять приложение в AppStore, при которой приложения с фреймворками, содержащих бинарных изображений для средства моделирования iOS, автоматически отклоняются.

Вы не увидите ничего нового, но нужно будет скомпилировать и запустить приложение, чтобы убедиться, что все продолжает работать, как и ожидалось. Когда приложение запустится, вы должны увидеть экран с возможностью поиска:

image

Отлично. Сейчас все выглядит хорошо. Далее, обновление зависимостей.

Обновление фреймворков


Я должен признаться

image

Помните, когда ранее вы создали Cartfile, и я сказал вам, какие версии Alamofire и SwiftyJSON нужно установить? Ну, как видите, я дал вам недостоверную информацию. Я велел вам использовать старую версию Alamofire.

image

Не злитесь! Это было сделано с самыми лучшими намерениями. Посмотрите на это как возможность… да, возможность узнать, как обновить зависимости. Это подарок.

image

Откройте свой Cartfile файл снова. Из каталога проекта, для этого в Terminal, выполните:
open -a Xcode Cartfile

Измените строку Alamofire на:
github "Alamofire/Alamofire" ~> 2.0

Это означает, что вы можете использовать любую версию Alamofire, которая совместима с 2.0, так что, любая версия вплоть до, но не включая 3.0.

Добавления зависимостей с помощью Carthage, это — хорошая идея, чтобы рассмотреть вопрос о совместимости и ограничить версию, на которую ориентируетесь. Таким образом, вы будете знать точное состояние его API и функциональности.

Например, версия 2.0 зависимости может включать в себя изменения приложение API — вероятно, не хотели бы автоматически обновляться до нее, если вы создали свой проект до версии 1.4.

Сохраните и закройте Cartfile файл, и вернитесь в terminal. Выполните еще одно обновление:
carthage update --platform iOS

Carthage будет проверять наличие новых версий каждой из зависимостей, выгружать их и создав их, при необходимости. Вы увидите, что он выбирает последнюю версию Alamofire.

Поскольку проект уже содержит ссылку на созданный .framework для Alamofire и Carthage создает новую версию в том же месте на диске, Вы можете расслабиться и позволить Carthage сделать свою работу; Ваш проект будет автоматически использовать последнюю версию Alamofire!

DuckDuckGo!


Теперь, когда добавили Alamofire и SwiftyJSON в проект, можете использовать их для поиска как и задумывалось. Готовы?

В Xcode откройте DuckDuckGo.swift. В верхней части файла добавьте оператор import для каждой зависимости:
import Alamofire
import SwiftyJSON

Затем, добавьте следующий метод к ResultType, чуть ниже двух записей оператора case:
func parseDefinitionFromJSON(json: JSON) -> Definition {
  switch self {
  case .Answer:
    let heading = json["Heading"].stringValue
    let abstract = json["AbstractText"].stringValue
    let imageURL = NSURL(string: json["Image"].stringValue)
 
    return Definition(title: heading, description: abstract, imageURL: imageURL)
  case .Exclusive:
    let answer = json["Answer"].stringValue
 
    return Definition(title: "Answer", description: answer, imageURL: nil)
  }
}

При этом используется SwiftyJSON, чтобы экстраполировать необходимые данные из ответа JSON и создать Definition, содержащий заголовок, описание и URL-адрес изображения.

DuckDuckGo API может возвращать различные типы результатов, но эти два ответа являются Answer, который дает простое определение искомого понятия, и Exclusive, что дает исчерпывающие ответы на вычисления.

Затем, все еще находясь в DuckDuckGo, замените существующее определение performSearch(_:completion:) на это:
func performSearch(searchTerm: String, completion: ((definition: Definition?) -> Void)) {
  // 1
  let parameters: [String:AnyObject] = ["q": searchTerm, "format": "json", "pretty": 1,
    "no_html": 1, "skip_disambig": 1]
 
  // 2
  Alamofire.request(.GET, "https://api.duckduckgo.com", parameters: parameters).responseJSON {
    _, _, result in
    // 3
    if result.isFailure {
      completion(definition: nil)
    }
 
    // 4
    if let jsonObject: AnyObject = result.value {
      let json = JSON(jsonObject)
 
      // 5
      if let jsonType = json["Type"].string, resultType = ResultType(rawValue: jsonType) {
 
        // 6
        let definition = resultType.parseDefinitionFromJSON(json)
        completion(definition: definition)
      }
    }
  }
}

Тут совсем немного, так что давайте разложим все по полочкам:
  1. Во-первых, создаете список параметров для отправки в DuckDuckGo. Наиболее важными здесь являются сам критерий поиска и формат, который сообщает веб-службе, что нужно ответить JSON.
  2. Затем выполняем запрос, используя Alamofire. Этот вызов выполняет Get запрос api.duckduckgo.com, с помощью словаря параметра, созданного выше.
  3. Как только ответ возвратится, проверьте, перестал ли запрос работать. Если так, тогда выходить рано.
  4. При необходимости, свяжите объект ответа JSON чтобы убедиться, что он имеет значение, и затем используйте его для создания JSON  структуры SwiftyJSON.
  5. Затем, захватите значение для ключа Type от JSON и используйте его, чтобы создать перечисление ResultType, который объявлен в верхней части DuckDuckGo.swift.
  6. Наконец, сообщите типу результата анализировать определение из предоставленных объектов JSON.


Примечание: Если вы задаетесь вопросом, почему существует параметр skip_disambig, то он сообщает DuckDuckGo не возвращать результаты однозначности.

Результаты неоднозначности похожи на те страницы, которые вы видите на Википедии: имели в виду Криса Эванса киноактера, Криса Эванс британского телеведущего или Криса Эванс грабителя?

skip_disambig означает, что API просто выберет наиболее вероятный результат и возвращает его.


Скомпилируйте и запустите проект! Как только приложение запускается, введите критерий поиска в панель поиска: можете попробовать ввести “Duck” или “Carthage”. Если все работает правильно, должны увидеть определение на следующем экране.

image

Однако чего-то не хватает: рисунок! Одно дело прочитать, что такое утка, но кто читает так много? А с рисунком совсем другое дело — ладно, я избавлю Вас от этих клише — вы знаете, что я имею в виду.

В любом случае, кому не нравится смотреть на картинки с утками? Или на котят как в прошлом сезоне, верно?

Откройте DefinitionViewController.swift, и добавьте import Alamofire чуть ниже существующего UIKit import вверху:
import Alamofire

Затем, в нижней части метода viewDidLoad(), добавьте следующее
if let imageURL = definition.imageURL {
  Alamofire.request(.GET, imageURL).response { _, _, data, _ in
    self.activityIndicator.stopAnimating()
 
    if let data = data {
      let image = UIImage(data: data)
      self.imageView.image = image
    }
  }
}

Этот код «раскрывает» URL изображения определения, если он имеет одно изображение и выполняет запрос GET для получения изображения. Если запрос успешно возвращает данные, то он используется для создания изображений и выводит их на экран в представлении изображения.

Скомпилируйте и запустите приложение, затем выполните поиск.

image

Куда дальше?


Вы можете загрузить завершенный проект.

Поздравляем, вы узнали об основных принципах управления зависимостями и о самом Carthage, получили опыт работы с Carthage, научившись добавлять зависимости в проект, и использовать эти зависимости, чтобы сделать приложение полезным!

Также узнали, как обновить зависимости для будущих релизов.

Если вы хотите узнать больше о Carthage, тогда сперва должны изучить Carthage README и документацию относительно Build Artifacts.
Джастин Спар-Саммерс (Justin Spahr-Summers), один из основателей проекта, сделал замечательный доклад в Realm.io о Carthage, под названием “Ruthlessly Simple Dependency Management.

Надеюсь, что вы узнали достаточно из этой статьи о Carthage. Если имеете какие-либо вопросы или комментарии, присоединяйтесь к обсуждению в комментариях!
Перевод: James Frost
Dima @yarmolchuk
карма
2,0
рейтинг 0,0
iOS разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (1)

  • 0
    Если укажете ~> 1.7.5, то любые версии от 1.7.5 и до, но не включая 2.0, считается совместимой.


    Разве это не означает, что будет использована любая версия >= 1.7.5 и < 1.8?

    В CocoaPods так:
    '~> 0.1.2' Version 0.1.2 and the versions up to 0.2, not including 0.2 and higher


    В RubyGems тоже:
    Had we said ~> 2.2.0, that would have been equivalent to ['>= 2.2.0', '< 2.3.0'].

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.