1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/knight2015-SwiftHttp

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Alamofire: элегантная работа с сетью на Swift

Alamofire — это библиотека для работы с HTTP-сетью, написанная на языке Swift. Создана автором библиотеки AFNetworking.

Функции

  • Цепные методы запроса / ответа;
  • Кодирование параметров URL / JSON / plist;
  • Загрузка файла / данных / потока;
  • Скачивание с использованием запроса или возобновления данных;
  • Аутентификация с помощью NSURLCredential;
  • Проверка HTTP-ответа;
  • Закрытие прогресса и NSProgress;
  • Вывод отладки cURL;
  • Комплексное модульное тестирование;
  • Полная документация.

Требования

  • iOS 7.0+ / Mac OS X 10.9+;
  • Xcode 6.0.

Для Xcode 6.1 используйте ветку xcode-6.1.

Общение

  • Если вам нужна помощь, используйте Stack Overflow. (Тег 'alamofire');
  • Если вы хотите задать общий вопрос, используйте Stack Overflow;
  • Если вы обнаружили ошибку, откройте проблему;
  • Если у вас есть запрос на функцию, откройте проблему;
  • Если вы хотите внести свой вклад, отправьте запрос на перенос.

Установка

Из-за отсутствия надлежащей инфраструктуры для управления зависимостями Swift использование Alamofire в вашем проекте требует выполнения следующих шагов:

  1. Добавьте Alamofire как подмодуль, открыв терминал, перейдя в каталог верхнего уровня проекта и выполнив команду git submodule add https://github.com/Alamofire/Alamofire.git.
  2. Откройте папку Alamofire и перетащите Alamofire.xcodeproj в навигатор файлов вашего проекта приложения.
  3. В Xcode перейдите к окну конфигурации проекта, щёлкнув по синему значку проекта и выбрав цель приложения под заголовком «Цели» на боковой панели.
  4. Убедитесь, что цель развёртывания Alamofire.framework совпадает с целью приложения.
  5. На панели вкладок в верхней части этого окна откройте панель «Фазы сборки».
  6. Разверните группу «Связать двоичный файл с библиотеками» и добавьте Alamofire.framework.
  7. Нажмите на кнопку + в левом верхнем углу панели и выберите «Новая фаза копирования файлов». Переименуйте эту новую фазу в «Копировать фреймворки», установите «Назначение» в «Фреймворки» и добавьте Alamofire.framework.

Использование

Создание запроса

import Alamofire

Alamofire.request(.GET, "http://httpbin.org/get")

Обработка ответа

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .response { (request, response, data, error) in
                     println(request)
                     println(response)
                     println(error)
                   }

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

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

Сериализация ответа

Встроенные методы ответа

  • response();
  • responseString(encoding: NSStringEncoding);
  • responseJSON(options: NSJSONReadingOptions);
  • responsePropertyList(options: NSPropertyListReadOptions).

Обработчик строки ответа

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseString { (_, _, string, _) in
                  println(string)
         }

Обработчик JSON ответа

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseJSON { (_, _, JSON, _) in
                  println(JSON)
         } **Ответ:**

#### Chained Response Handlers

Обработчики ответов могут быть связаны:

```swift
Alamofire.request(.GET, "http://httpbin.org/get")
         .responseString { (_, _, string, _) in
                  println(string)
         }
         .responseJSON { (_, _, JSON, _) в
                  println(JSON)
         }

HTTP-методы

Alamofire.Method перечисляет HTTP-методы, определённые в RFC 7231 §4.3:

public enum Method: String {
    case OPTIONS = "OPTIONS"
    case GET = "GET"
    case HEAD = "HEAD"
    case POST = "POST"
    case PUT = "PUT"
    case PATCH = "PATCH"
    case DELETE = "DELETE"
    case TRACE = "TRACE"
    case CONNECT = "CONNECT"
}

Эти значения можно передать в качестве первого аргумента метода Alamofire.request:

Alamofire.request(.POST, "http://httpbin.org/post")

Alamofire.request(.PUT, "http://httpbin.org/put")

Alamofire.request(.DELETE, "http://httpbin.org/delete")

Параметры

GET-запрос с параметрами, закодированными URL-адресом

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
// http://httpbin.org/get?foo=bar

POST-запрос с параметрами, закодированными URL-адресом

let parameters = [
    "foo": "bar",
    "baz": ["a", 1],
    "qux": [
        "x": 1,
        "y": 2,
        "z": 3
    ]
]

Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters)
// HTTP body: foo=bar&baz[]=a&baz[]=1&qux[x]=1&qux[y]=2&qux[z]=3

Кодирование параметров

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

enum ParameterEncoding {
    case URL
    case JSON
    case PropertyList(format: NSPropertyListFormat,
                      options: NSPropertyListWriteOptions)

    func encode(request: NSURLRequest,
                parameters: [String: AnyObject]?) ->
                    (NSURLRequest, NSError?)
    { ... }
}
  • URL: строка запроса, которая будет установлена или добавлена к любому существующему запросу URL для запросов GET, HEAD и DELETE, или установлена в качестве тела для запросов с любым другим HTTP-методом. Поле HTTP-заголовка Content-Type для кодированного запроса с телом HTTP установлено на application/x-www-form-urlencoded. Поскольку не существует опубликованной спецификации о том, как кодировать типы коллекций, используется соглашение о добавлении [] к значению ключа для массивов (foo[]=1&foo[]=2), и добавлении ключа, заключённого в квадратные скобки, для вложенных значений словаря (foo[bar]=baz).
  • JSON: использует NSJSONSerialization для создания представления JSON объекта параметров, которое устанавливается в качестве тела запроса. Поле HTTP-заголовка Content-Type кодированного запроса установлено на application/json.
  • PropertyList: использует NSPropertyListSerialization для создания списка свойств объекта параметров в соответствии со связанными значениями формата и опций записи, который устанавливается в качестве тела запроса. Поле HTTP-заголовка Content-Type кодированного запроса установлено на application/x-plist.
  • Custom: использует связанное значение замыкания для построения нового запроса на основе существующего запроса и параметров.

Ручное кодирование параметров для NSURLRequest

let URL = NSURL(string: "http://httpbin.org/get")
var request = NSURLRequest(URL: URL)

let parameters = ["foo": "bar"]
let encoding = Alamofire.ParameterEncoding.URL
(request, _) = encoding.encode(request, parameters)

POST-запрос с параметрами в формате JSON

let parameters = [
    "foo": [1,2,3],
    "bar": [
        "baz": "qux"
    ]
]

Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters, encoding: .JSON)
// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}

Кэширование

Кэширование обрабатывается на уровне системной инфраструктуры с помощью NSURLCache (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/cl/NSURLCache).

Загрузка

Поддерживаемые типы загрузки

  • Файл
  • Данные
  • Поток

Загрузка файла

let
``` **Загрузка с использованием Alamofire**

fileURL = NSBundle.mainBundle() .URLForResource("Default", withExtension: "png")

Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)


#### Загрузка с отслеживанием прогресса

```swift
Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
         .progress { (bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in
             println(totalBytesWritten)
         }
         .responseJSON { (request, response, JSON, error) in
             println(JSON)
         }

Скачивание

Поддерживаемые типы скачивания

  • Запрос
  • Данные для возобновления

Скачивание файла

Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: { (temporaryURL, response) in
    if let directoryURL = NSFileManager.defaultManager()
                          .URLsForDirectory(.DocumentDirectory,
                                            inDomains: .UserDomainMask)[0]
                          as? NSURL {
        let pathComponent = response.suggestedFilename

        return directoryURL.URLByAppendingPathComponent(pathComponent!)
    }

    return temporaryURL
})

Использование стандартного места назначения для скачивания

let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)

Alamofife.download(.GET, "http://httpbin.org/stream/100", destination: destination)

Скачивание файла с отслеживанием прогресса

Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination)
         .progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
             println(totalBytesRead)
         }
         .response { (request, response, _, error) in
             println(response)
         }

Аутентификация

Аутентификация обрабатывается на уровне системной инфраструктуры с помощью NSURLCredential и NSURLAuthenticationChallenge.

Поддерживаемые схемы аутентификации

HTTP Basic аутентификация

let user = "user"
let password = "password"

Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
         .authenticate(user: user, password: password)
         .response {(request, response, _, error) in
             println(response)
         }

Аутентификация с использованием NSURLCredential

let user = "user"
let password = "password"

let credential = NSURLCrediential(user: user, password: password, persistence: .ForSession)

Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
         .authenticate(usingCredential: credential)
         .response {(request, response, _, error) in
             println(response)
         }

Проверка

По умолчанию Alamofire считает любой завершённый запрос успешным, независимо от содержимого ответа. Вызов validate перед обработчиком ответа вызывает ошибку, если ответ имеет неприемлемый код состояния или тип MIME.

Ручная проверка

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .validate(statusCode: 200..<300)
         .validate(contentType: ["application/json"])
         .response { (_, _, _, error) in
                  println(error)
         }

Автоматическая проверка

Автоматически проверяет код состояния в диапазоне 200...299, а также соответствие заголовка Content-Type ответа заголовку Accept запроса, если он предоставлен.

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .validate()
         .response { (_, _, _, error) in
                  println(error)
         }
``` **let request = Alamofire.request(.GET, "http://httpbin.org/ip")**

println(request)
// GET http://httpbin.org/ip (200)

**let request = Alamofire.request(.GET, «http://httpbin.org/get», parameters: [«foo»: «bar»])**

debugPrintln(request)

#### Output (cURL)

$ curl -i \
    -H «User-Agent: Alamofire» \
    -H «Accept-Encoding: Accept-Encoding: gzip;q=1.0,compress;q=0.5» \
    -H «Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5» \
    «http://httpbin.org/get?foo=bar»

---

## Advanced Usage

> Alamofire is built on NSURLSession and the Foundation URL Loading System. To make the most of this framework, it is recommended that you be familiar with the concepts and capabilities of the underlying networking stack.

Recommended Reading

- [URL Loading System Programming Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html)
- [NSURLSession Class Reference](https://developer.apple.com/library/mac/documentation/Foundation/Reference/NSURLSession_class/Introduction/Introduction.html#//apple_ref/occ/cl/NSURLSession)
- [NSURLCache Class Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/cl/NSURLCache)
- [NSURLAuthenticationChallenge Class Reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLAuthenticationChallenge_Class/Reference/Reference.html)

### Manager

Top-level convenience methods like Alamofire.request use a shared instance of Alamofire.Manager, which is configured with the default NSURLSessionConfiguration.

As such, the following two statements are equivalent:

Alamofire.request(.GET, «http://httpbin.org/get»)

let manager = Alamofire.Manager.sharedInstance
manager.request(NSURLRequest(URL: NSURL(string: «http://httpbin.org/get»)))

Applications can create managers for background and ephemeral sessions, as well as new managers that customize the default session configuration, such as for default headers (HTTPAdditionalHeaders) or timeout interval (timeoutIntervalForRequest).

#### Creating a Manager with Default Configuration

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manager = Alamofire.Manager(configuration: configuration)

#### Creating a Manager with Background Configuration

let configuration = NSURLSessionConfiguration.backgroundSessionConfiguration("com.example.app.background")
let manager = Alamofire.Manager(configuration: configuration)

#### Creating a Manager with Ephemeral Configuration

let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let manager = Alamofire.Manager(configuration: configuration)

#### Modifying Session Configuration

var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders

let manager = Alamofire.Manager(configuration: configuration)

> This is not recommended for Authorization or Content-Type headers. Instead, use URLRequestConvertible and ParameterEncoding, respectively.

### Request

The result of a request, upload, or download method is an instance of Alamofire.Request. A request is always created using a constructor method from an owning manager, and never initialized directly.

Methods like authenticate, validate, and response return the caller in order to facilitate chaining.

Requests can be suspended, resumed, and cancelled:

* suspend(): Suspends the underlying task and dispatch queue
* resume(): Resumes the underlying task and dispatch queue. If the owning manager does not have startRequestsImmediately set to true, the request must call resume() in order to start. **Отмена (cancel())**: отменяет основную задачу, создавая ошибку, которая передаётся любым зарегистрированным обработчикам ответа.

### Сериализация ответа

#### Создание пользовательского сериализатора ответа

Alamofire предоставляет встроенную сериализацию ответов для строк, JSON и списков свойств, но можно добавить и другие в расширениях на `Alamofire.Request`.

Например, вот как может быть реализован обработчик ответа с использованием [Ono](https://github.com/mattt/Ono):

```swift
extension Request {
    class func XMLResponseSerializer() -> Serializer {
        return { (request, response, data) in
            if data == nil {
                return (nil, nil)
            }

            var XMLSerializationError: NSError?
            let XML = ONOXMLDocument.XMLDocumentWithData(data, &XMLSerializationError)

            return (XML, XMLSerializationError)
        }
    }

    func responseXMLDocument(completionHandler: (NSURLRequest, NSHTTPURLResponse?, OnoXMLDocument?, NSError?) -> Void) -> Self {
        return response(serializer: Request.XMLResponseSerializer(), completionHandler: { (request, response, XML, error) in
            completionHandler(request, response, XML, error)
        })
    }
}

Сериализация объекта ответа общего типа

Дженерики можно использовать для обеспечения автоматической и типобезопасной сериализации объектов ответа.

@objc public protocol ResponseObjectSerializable {
    init(response: NSHTTPURLResponse, representation: AnyObject)
}

extension Alamofire.Request {
    public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
        let serializer: Serializer = { (request, response, data) in
            let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
            if response != nil && JSON != nil {
                return (T(response: response!, representation: JSON!), nil)
            } else {
                return (nil, serializationError)
            }
        }

        return response(serializer: serializer, completionHandler: { (request, response, object, error) in
            completionHandler(request, response, object as? T, error)
        })
    }
}
class User: ResponseObjectSerializable {
    let username: String
    let name: String

    required init(response: NSHTTPURLResponse, representation: AnyObject) {
        self.username = response.URL!.lastPathComponent
        self.name = representation.valueForKeyPath("name") as String
    }
}
Alamofire.request(.GET, "http://example.com/users/mattt")
         .responseObject { (_, _, user: User?, _) in
             println(user)
         }

Тот же подход можно использовать и для обработки конечных точек, которые возвращают представление коллекции объектов:

@objc public protocol ResponseCollectionSerializable {
    class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}

extension Alamofire.Request {
    public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {
        let serializer: Serializer = { (request, response, data) in
            let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
            if response != nil && JSON != nil {
                return (T.collection(response: response!, representation: JSON!), nil)
            } else {
                return (nil, serializationError)
            }
        }

        return response(serializer: serializer, completionHandler: { (request, response, object, error) in
            completionHandler(request, response, object as? [T], error)
        })
    }
}

URLStringConvertible

Типы, соответствующие протоколу URLStringConvertible, могут использоваться для создания строк URL, которые затем используются для построения запросов URL. Методы удобства верхнего уровня, принимающие аргумент URLStringConvertible, обеспечивают типобезопасное поведение маршрутизации.

Приложениям, активно взаимодействующим с веб-приложениями, рекомендуется использовать URLStringConvertible или URLRequestConvertible для обеспечения согласованности запрашиваемых конечных точек.

Типобезопасная маршрутизация

enum Router: URLStringConvertible {
    static let baseURLString = "http://example.com"

    case Root
    case User(String)
    case Post(Int, Int, String)

    // MARK: URLStringConvertible

    var URLString: String {
        let path: String = {
            switch self {
            case .Root:
                return "/"
            case .User(let username):
                return "/users/\(username)"
            case .Post(let year, let month, let title):
                let slug = title.stringByReplacingOccurrencesOfString(" ", withString: "-").lowercaseString
                return "/\(year)/\(month)/\(slug)"
            }
        }()

        return Router.baseURLString + path
    }
}
Alamofire.request(.GET, Router.User("mattt"))

URLRequestConvertible**

Типы, использующие протокол URLRequestConvertible, могут быть использованы для создания URL-запросов. Как и URLStringConvertible, это рекомендуется для приложений со значительным взаимодействием между клиентом и сервером.

Методы верхнего уровня и методы экземпляра в Manager, принимающие аргументы URLRequestConvertible, предоставляют способ обеспечения типобезопасной маршрутизации. Такой подход может использоваться для абстрагирования несоответствий на стороне сервера, а также для управления учётными данными аутентификации и другим состоянием.

API-параметры абстракции

enum Router: URLRequestConvertible {
    static let baseURLString = "http://example.com"
    static let perPage = 50

    case Search(query: String, page: Int)

    // MARK: URLRequestConvertible

    var URLRequest: NSURLRequest {
        let (path: String, parameters: [String: AnyObject]?) = {
            switch self {
            case .Search(let query, let page) where page > 1:
                return ("/search", ["q": query, "offset": Router.perPage * page])
            case .Search(let query, _):
                return ("/search", ["q": query])
            }
        }()

        let URL = NSURL(string: Router.baseURLString)
        let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(path))
        let encoding = Alamofire.ParameterEncoding.URL

        return encoding.encode(URLRequest, parameters: parameters).0
    }
}
Alamofire.request(Router.Search(query: "foo bar", page: 1)) // ?q=foo+bar&offset=50

CRUD и авторизация

enum Router: URLRequestConvertible {
    static let baseURLString = "http://example.com"
    static var OAuthToken: String?

    case CreateUser([String: AnyObject])
    case ReadUser(String)
    case UpdateUser(String, [String: AnyObject])
    case DestroyUser(String)

    var method: Alamofire.Method {
        switch self {
        case .CreateUser:
            return .POST
        case .ReadUser:
            return .GET
        case .UpdateUser:
            return .PUT
        case .DestroyUser:
            return .DELETE
        }
    }

    var path: String {
        switch self {
        case .CreateUser:
            return "/users"
        case .ReadUser(let username):
            return "/users/\(username)"
        case .UpdateUser(let username, _):
            return "/users/\(username)"
        case .DestroyUser(let username):
            return "/users/\(username)"
        }
    }

    // MARK: URLRequestConvertible

    var URLRequest: NSURLRequest {
        let URL = NSURL(string: Router.baseURLString)
        let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
        mutableURLRequest.HTTPMethod = method.toRaw()

        if let token = Router.OAuthToken {
            mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        switch self {
        case .CreateUser(let parameters):
``` **Перевод текста на русский язык:**

return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0 case .UpdateUser(_, let parameters): return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 default: return mutableURLRequest } } }


* * *

## FAQ

### Когда следует использовать Alamofire?

Если вы начинаете новый проект на Swift и хотите в полной мере воспользоваться его соглашениями и языковыми функциями, Alamofire — отличный выбор. Хотя он не так многофункционален, как AFNetworking, с Alamofire гораздо приятнее работать, и он должен удовлетворить подавляющее большинство случаев использования сетей.

> Важно отметить, что две библиотеки не исключают друг друга: AFNetworking и Alamofire могут мирно сосуществовать в одной кодовой базе.

### Когда следует использовать AFNetworking?

AFNetworking остаётся главной сетевой библиотекой, доступной для OS X и iOS, и может легко использоваться в Swift, как и любой другой код Objective-C. AFNetworking стабилен и надёжен, и никуда не денется.

Используйте AFNetworking в следующих случаях:

- расширения UIKit, такие как асинхронная загрузка изображений в `UIImageView`;
- проверка TLS с помощью `AFSecurityManager`;
- ситуации, требующие `NSOperation` или `NSURLConnection`, с использованием `AFURLConnectionOperation`;
- мониторинг доступности сети с помощью `AFNetworkReachabilityManager`;
- создание многокомпонентных HTTP-запросов с использованием `AFHTTPRequestSerializer`.

### Откуда произошло название Alamofire?

Alamofire назван в честь цветка [Alamo Fire](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), гибридного варианта Bluebonnet, официального государственного цветка Техаса.

* * *

## Контакты

Следите за AFNetworking в Twitter ([@AFNetworking](https://twitter.com/AFNetworking))

### Создатель

— Мэтт Томпсон (Mattt Thompson) ([@mattt](https://twitter.com/mattt)).

## Лицензия

Alamofire выпущен под лицензией MIT. Подробнее см. в файле LICENSE.

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

Вы можете оставить комментарий после Вход в систему

Введение

Свифт HTTP доступ. Развернуть Свернуть
MIT
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/knight2015-SwiftHttp.git
git@api.gitlife.ru:oschina-mirror/knight2015-SwiftHttp.git
oschina-mirror
knight2015-SwiftHttp
knight2015-SwiftHttp
master