I'm using the moya library to make a POST request. In TargetType, I couldn't able to see any property to pass the parameters[JSON body] along with POST request. Here, I attach the TargetType
public protocol TargetType {
/// The target's base `URL`.
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
var path: String { get }
/// The HTTP method used in the request.
var method: Moya.Method { get }
/// Provides stub data for use in testing.
var sampleData: Data { get }
/// The type of HTTP task to be performed.
var task: Task { get }
/// Whether or not to perform Alamofire validation. Defaults to `false`.
var validate: Bool { get }
/// The headers to be used in the request.
var headers: [String: String]? { get }
}
public extension TargetType {
var validate: Bool {
return false
}
}
Finally,I got the solution for my problem. In Moya 10.0, we can pass the http body JSON payload in task property [TargetType].
click here for reference
var task: Task {
switch self {
case .zen, .showUser, .showAccounts: // Send no parameters
return .requestPlain
case let .updateUser(_, firstName, lastName): // Always sends parameters in URL, regardless of which HTTP method is used
return .requestParameters(parameters: ["first_name": firstName, "last_name": lastName], encoding: URLEncoding.queryString)
case let .createUser(firstName, lastName): // Always send parameters as JSON in request body
return .requestParameters(parameters: ["first_name": firstName, "last_name": lastName], encoding: JSONEncoding.default)
}
}
Related
I am writing an application with networking capabilities for iOS 13.4 (Swift 5, Xcode 11) using Alamofire 5. I have created my custom type typealias KeyedParameters = [ParameterKeys: Any] to be able to use my API Parameter Keys in a 'swifty' short way (i.e. .login instead of KeyedParameters.login.rawValue).
The problem is when I try do convert this type back to default Alamofire's Parameters, I receive following error: Cannot convert return expression of type 'Dictionary<ParameterKeys, Any>' to return type 'Parameters' (aka 'Dictionary<String, Any>').
Casting:
extension KeyedParameters {
var parameters: Parameters {
Dictionary(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
ParameterKeys:
enum ParameterKeys: String {
// MARK: - Auth and User
case id, login, password, email, name
case createdAt = "created_at"
...
}
How error looks:
I think this might be just a case of a bad error message.
Your extension KeyedParameters (a typealias for [ParameterKeys: Any]) is actually equivalent to:
extension Dictionary where Key == ParameterKeys, Value: Any { ...
Swift has some odd behaviour when calling an initializer for a generic type within the declaration of that type itself. If the generic types are different, it won't handle that properly.
Here's a simpler example without so many red herrings (type aliases, enum raw values, etc.) and dependencies:
extension Dictionary {
func returnADifferentDict() -> [Character: String] {
let words = [
"apple", "anchovies",
"bacon", "beer",
"celery"
]
return Dictionary(uniqueKeysWithValues:
words.map { ($0.first!, $0) }
)
// fixed:
// return Dictionary<Character, String>(uniqueKeysWithValues:
// words.map { ($0.first!, $0) }
// )
}
}
The solution is to explicitly specify the generic type parameters of the generic type you're initializing. In your case,
extension KeyedParameters {
var parameters: Parameters {
Dictionary<String, Any>(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
You'd better explicitly highlight type like this:
extension KeyedParameters {
var parameters: Parameters {
return Parameters(uniqueKeysWithValues:
self.map { (key, value) in (key.rawValue, value) }
)
}
}
Worked for me.
I have data like this on server side.
For UserImagesTable, these are my parameters
{
"status" : false,
"id" : {
"userId" : 1,
"imageId" : 1
}
I need to post data through alamofire and retrofit.
I tried like this: I created one model class for userImages Table and one more model class for userImages.id table
My model classes are:
class UserImages
{
var status : Bool? = nil
var id : ImagesId? = nil
func toJson() -> [String:Any] {
return[
"status" : status as Any!,
"id" : id as ImagesId!
]
}
}
class ImagesId
{
var userId : Int16? = nil
var imageId : Int16? = nil
func toJson() -> [String:Any] {
return[
"userId" : userId as Any!,
"imageId" : imageId as Any!,
]
}
}
Then my Alamofire
let ImageParams = UserImages()
let ImageIdParams = ImagesId()
ImageIdParams.imageId = 2
ImageIdParams.userId = 3
ImageParams.status = true
favouriteVideoParams.id = ImageIdParams.toJson()-->this line coming some error like -->Cannot assign value of type '[String:any]' to type ImageId?
Alamofire.request(url, method: .post, parameters: ImageParams.toJson(), encoding: JSONEncoding.default, headers: Defines.Constants.Headers)
What I'm doing for post, is this approach correct or not? If it's possible give me some idea about retrofit post also.
For Alamofire, your .toJSON functions aren't really required a simpler solution would be that you create a parameter variable in your function that shapes the parameters with pre-defined keys and variable values given via the function, this would look something like this:
func postUserImageTable(with status: Bool?, and userImagesId: ImagesId?) {
let url = "your url"
let parameters = [
"status": status!
"id": [
"userId": userImagesId?.userId
"imageId": userImagesId?.imageId
]
]
Alamofire.request(url, method: .post, parameters: parameters)
}
as per the official documentation of Alamofire (Alamofire Post with JSON encoded Params)
For Retrofit you can make use of the #body tag to POST pojo classes to your server. So if you have equivalent java classes as you showed in Swift, being this:
public class UserImage {
private Boolean status;
private ImagesId imagesId;
//constructor
//getters and setters
}
public class ImagesId {
private int userId, imageId;
//constructor
//getters and setters
}
then you can add this call to your retrofit service
#Post("your/post/url")
Response postUserImageTable(#Body UserImage body);
And you can use this in your call with retrofit:
Call<Response> call = Service.postUserImageTable(new UserImage(false, new ImagesId(1,2));
call.enqueue(New Callback<Response> {
//obligatory overrides from retrofit
})
public protocol ResponseJSONObjectSerializable {
init?(json: SwiftyJSON.JSON)
}
public struct Response<Value, Error: ErrorType> {
...
}
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
...
}
So far I understand the last function to mean that the type declaration requires generic type T which follows the protocol ResponseJSONObjectSerializable which is used in a completionHandler which takes a Response struct which has a type declaration of <Value, NSError> -> Void and then returns a self?
I feel like I might grok all of it except the last self part.
You're right about the first two declarations.
The last one is a bit odd because of how Alamofire does response serializers. You can chain multiple serializers like this:
Alamofire.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
When that code gets called this is what happens:
Alamofire.request(myRequest) creates the request and a queue for handlers
.responseString & .responseArray get their response serializers added to the queue
The network call happens
When it's done (whether it fails or succeeds), the queue calls all of the response serializers that were added to it (i.e., .responseString & .responseArray)
When each serializer gets run, it's completion handler can be used to "return" results to the caller (which it can't do directly because it's async)
This code would does (almost) the same thing:
let manager = Alamofire.Manager.sharedInstance
manager.startsRequestImmediately = false
let alamofireRequest = manager.request(myRequest)
alamofireRequest.responseString { // handle response as string }
alamofireRequest.responseArray { // handle response as array }
alamofireRequest.resume()
But manager.startsRequestImmediately = false means that the network call doesn't get started until alamofireRequest.resume() is called. It's true by default so all of the response serializers have to be added as part of the same statement as manager.request(myRequest).
Since the response serializers return self, we can shorten this:
let alamofireRequest = manager.request(myRequest)
alamofireRequest.responseString { // handle response as string }
alamofireRequest.responseArray { // handle response as array }
To this:
let alamofireRequest = manager.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
And if we use manager.startsRequestImmediately = true then we don't need the local var for the request at all (since we don't have to call alamofireRequest.resume()):
manager.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
I am developing a library Restofire in which i want to keep a configuration object. I want to have a ResponseSerializer in the configuration object but the thing is ResponseSerializer is a generic.
public struct Configuration<M> {
/// The Default `Configuration`.
static let defaultConfiguration = Configuration<AnyObject>()
/// The base URL. `nil` by default.
public var baseURL: String!
/// The `ResponseSerializer`
public var responseSerializer: ResponseSerializer<M, NSError> = AlamofireUtils.JSONResponseSerializer()
/// The logging, if enabled prints the debug textual representation of the
/// request when the response is recieved. `false` by default.
public var logging: Bool = false
}
I set up the defaultConfiguration with baseUrl Configuration.defaultConfiguration.baseUrl = "http://httpbin.org/"
I have a protocol with associatedType requirement which uses the defaultConfiguration as default implementation. But i require to change the generic AnyObject to the associatedType Model so the responseSerializer of configuration object returns the type Model.
public protocol Configurable {
associatedtype Model
/// The Restofire configuration.
var configuration: Configuration<Model> { get }
}
public extension Configurable {
/// `Restofire.defaultConfiguration`
// Cannot convert return expression of Configuration<AnyObject> to return type Configuration <Self.Model>
public var configuration: Configuration<Model> {
return Restofire.defaultConfiguration
}
}
I get the error Cannot convert return expression of Configuration<AnyObject> to return type Configuration <Self.Model>
How can i downcast to use Model instead of AnyObject ?
I also have a protocol Requestable that inherits from Configurable
public protocol Requestable: Configurable {
/// The type of object returned in response.
associatedtype Model
/// The base URL.
var baseURL: String { get }
/// The path relative to base URL.
var path: String { get }
/// The request parameters.
var parameters: AnyObject? { get }
/// The logging.
var logging: Bool { get }
/// The Response Serializer
var responseSerializer: ResponseSerializer<Model, NSError> { get }
}
// MARK: - Default Implementation
public extension Requestable {
/// `configuration.BaseURL`
public var baseURL: String {
return configuration.baseURL
}
/// `nil`
public var parameters: AnyObject? {
return nil
}
/// `configuration.logging`
public var logging: Bool {
return configuration.logging
}
/// `configuration.responseSerializer`
var responseSerializer: ResponseSerializer<Model, NSError> {
return configuration.responseSerializer
}
}
RealCode at https://github.com/Restofire/Restofire/compare/Add/DefaultResponseSerializer
I could do directly below but then the user will not be able to set it using the configuration object.
// MARK: - Default Implementation
public extension Requestable {
/// `configuration.responseSerializer`
var responseSerializer: ResponseSerializer<Model, NSError> {
return AlamofireUtils.JSONResponseSerializer()
}
}
Is there any other way ?
What you are trying to do is not possible. This is because of the strong type safety system in Swift.
It looks like you want to restrict the configuration to the associated type Model, but you also want to provide a default configuration. The issue here is that your default configuration has no guarantee that its generic type will be the same as the type of Model.
The Swift type system won't let you pass <AnyObject> as type <Model>, as it has no way to ensure that the object actually IS of the Model type.
When you make something conform to Configurable and declare the type of the Model, you are saying, "My configuration must use the type I've given." The defaultConfiguration can't guarantee that, so it cannot be used in this way.
From looking at your code, you are using this to determine the type of the responseSerializer to use. But, if you are using different Configurable objects, they will all need different responseSerializers, so they can't all use the default configuration.
I've spent a lot of time thinking about this problem, and I don't see any way around it. You are going to have to change your design in some way.
If you move the responseSerializer on to the Configurable protocol, then you could make the Configuration non-generic. In that case, you would be able to create the responseSerializer in a protocol extension on Configurable. If you need to use the responseSerializer with a configuration independently of a Configurable object, then you would have to create a responseSerializer<AnyObject, NSError> wherever it is that you are using it. I'm not familiar with the entire design and intent of your library, so I'm not sure if this will work for what you are trying to achieve.
The one thing I can tell you with certainty is that your design has to change in order to use the defaultConfiguration.
Is there a way to call an async function from a lazy or computed property?
struct Item {
lazy var name: String = {
API.requestThing({ (string: String) in // Xcode didn't like this
return string // this would not work here
})
}()
}
class API {
class func requestThing(completion: String -> Void) {
completion("string")
}
}
Your completion handler in API.requestThing returns a String, yet it is supposed to have no return value:
(completion: String -> Void)
I got this to work:
struct Item {
lazy var name: String = {
API.requestThing({ (string: String) in
return string
})
}()
}
class API {
class func requestThing(completion: String -> String) -> String {
return completion("string")
}
}
There is no good reason to use "lazy" in this case. lazy is for initialization. Just create a normal func and pass a completion handler.
First, requestThing returns () (ie void) and not String. So the type of the following expression is also () and not String:
API.requestThing { string in
return string
}
Second, the call to requestThing is asynchronous, so even if you defined name as a lazy var, the call to the var body function is still synchronousand will return immediately.
So if you can transform name into a function like this:
func name(completion: String -> ()) {
API.requestThing { string in
completion(string)
}
}
// Later you call it in this way
myItem.name { name in
// use the value of name
}
If in addition you want to cache the retrieved value you can modify Item to a class and use the following code
class Item {
private var nameValue: String?
func name(completion: String -> ()) {
if let value = nameValue {
// return cached value
return completion(value)
} else {
// request value
API.requestThing { string in
// cache retrieved value
self.nameValue = string
// return retrieved value
completion(string)
}
}
}
}
There's probably no compelling reason to do this, but the following approach seems to be reasonable:
Instead having a variable of type String - we sometimes require a "Future" of that thing, e.g. Future<String>. A future represents the eventual result of an asynchronous operation - that is exactly what's given in your question.
The future itself is a "normal" variable and can be lazy evaluated, too. It just doesn't yet have its eventual value. That means, the underlying task will only be started when explicitly requested (e.g. lazily). From a design or architectural point of view, this may make sense.
func fetchString() -> Future<String> { ... }
lazy var name: Future<String> = fetchString()
Later in your code, you obtain the variable as follows:
item.name.map { string in
print(string)
}
If this is the first access to the lazy property, it will start the underlying asynchronous operation which calculates the string. Then, when the variable is available, the mapping function as provided in the map function will be called with the variable as an argument - possibly some time later, too.
Otherwise (if this is not the first access), it will just provide the string in the parameter when it is available, possibly immediately.
Since operations may fail, a "Future" also provides means to handle this:
item.name.map { string in
print(string)
}.onFailure { error in
print("Error: \(error)")
}
See also: https://en.wikipedia.org/wiki/Futures_and_promises
There are implementations for Futures in Swift and Objective-C, also often called "Promise".