Generic function with Alamofire - swift

I work with iOS app that use Alamofire, I want to write a generic function(s) which used to send and retrieve data from server to a decodable objects, my function was as below :
func pop <T : Codable> (_ Url: inout String, _ popedList: inout [T]) {
let url = URL(string:Url)
Alamofire.request(url!, method: .post).responseJSON { response in
let result = response.data
do {
let data = try JSONDecoder().decode(popedList, from: result!)// get error here
print(data[0])
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(data[0])
let jsonString = String(data: jsonData, encoding: .utf8)
print("jsonString: \(String(describing: jsonString))")
} catch let e as NSError {
print("error : \(e)")
}
}
}
and a function to send an object to server as below:
func push <T : Codable> (_ Url: inout String, _ pushObject: inout T) {
let jsonData = try! JSONEncoder().encode(pushObject)
let jsonString = String(data: jsonData, encoding: .utf8)
print("jsonString: \(String(describing: jsonString))")
let url = URL(string:Url)
Alamofire.request(url!,
method: .post,
parameters:jsonString)//it's need to creat a Dictionary instate of String
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
// response handling code
let result = response.data
print(response.data)
}
}
I get an error in first function,
"Cannot invoke 'decode' with an argument list of type '([T], from: Data)'"
and
"Escaping closures can only capture inout parameters explicitly by value"
What is the best way to write these to function as generic?

After a few searches and trying to edit my functions I capable to rewrite my two functions in such away that I get what I need:
func pop<T: Decodable>(from: URL, decodable: T.Type, completion:#escaping (_ details: [T]) -> Void)
{
Alamofire.request(from, method: .post).responseJSON { response in
let result_ = response.data
do {
let data = try JSONDecoder().decode([T].self, from: result_!)
//let data = try JSONDecoder().decode(decodable, from: result_!)// get error here
//print(data[0])
print("data[0] : \(data[0])")
completion(data)
} catch let e as NSError {
print("error : \(e)")
}
}
}
func push <T : Codable> (_ Url: String, _ pushObject: T)
{
let jsonData = try! JSONEncoder().encode(pushObject)
let jsonString = String(data: jsonData, encoding: .utf8)
print("jsonString: \(String(describing: jsonString))")
let url = URL(string:Url)
Alamofire.request(url!,
method: .post,
parameters:convertToDictionary(text: jsonString!))//it's need to creat a Dictionary instate of String
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
// response handling code
print(response.data!)
if let jsonData = response.data {
let jsonString = String(data: jsonData, encoding: .utf8)
print("response.data: \(String(describing: jsonString))")
}
}
}
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}

For the first function, JSONDecoder.decode() wants 2 parameters:
Type to decode to: the class/struct you want it to decode to. This is not an instantiated object, just the type.
Data to decode from: the generic Data object that will be converted to the type you specified.
So, in order to be able to write your function so that it has a generic URL and result object, you would need to pass it the object type and a callback to pass the result to, since network operations are asynchronous.
func dec<T: Decodable>(from: URL, decodable: T.Type, result: (T) -> Void) {
// your Alamofire logic
let data = try JSONDecoder().decode(popedList, from: result!)
result(data)
}
You can apply the same logic to the second function.
Note that this is not the best way to take care of eventual errors, just an example of how you can handle encoding with a generic function.

JSONDecoder().decode method takes type and data parameter. Pass type not popedList.
let data = try JSONDecoder().decode([T].self, from: result!)
Inout Paramaters
Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.
You are not changing value of popedList in both functions, so using inout is meaningless.

Related

Preserving Failure Type with Combine's tryMap

I'm using Combine to write a simple web scraper. I'm trying to map the returned data to a string of HTML, throwing ScraperErrors at each possible failure point. At the end, I want to pass this string to my htmlSubject, which is a PassthroughSubject<String, ScraperError>, for further processing.
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.tryMap { (data, response) -> String in
guard let html = String(data: data, encoding: .utf8) else {
throw ScraperError.readFailed
}
return html
}
.subscribe(htmlSubject) // <-- Not allowed because failure type is now Error
However, I'm finding that .tryMap is erasing my ScraperError to a regular Error, preventing me from chaining my htmlSubject to the end:
Instance method 'subscribe' requires the types 'Error' and
'ScraperError' be equivalent.
Is there an obvious way around this that I'm missing, or am I getting tripped up conceptually? I'm thinking of this chain as building blocks in a large function that maps <(Data, URLResponse), URLError> to <String, ScraperError>.
Any help is appreciated.
Use mapError to convert back to ScraperError after the tryMap:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.tryMap { (data, response) -> String in
guard let html = String(data: data, encoding: .utf8) else {
throw ScraperError.readFailed
}
return html
}
.mapError { $0 as! ScraperError }
.subscribe(htmlSubject)
If you don't want to use as!, you'll have to pick some other case to map to:
.mapError { $0 as? ScraperError ?? ScraperError.unknown }
If you don't like that either, you can use flatMap over Result<String, ScraperError>.Publisher:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.flatMap { (data, response) -> Result<String, ScraperError>.Publisher in
guard let html = String(data: data, encoding: .utf8) else {
return .init(.readFailed)
}
return .init(html)
}
.subscribe(htmlSubject)
I find the resulting code to be a bit more readable when wrapping Rob's flatMap approach into an extension:
extension Publisher {
func flatMapResult<T>(_ transform: #escaping (Self.Output) -> Result<T, Self.Failure>) -> Publishers.FlatMap<Result<T, Self.Failure>.Publisher, Self> {
self.flatMap { .init(transform($0)) }
}
}
The code example above would then become:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.flatMapResult { (data, response) -> Result<String, ScraperError> in
guard let html = String(data: data, encoding: .utf8) else {
return .failure(.readFailed)
}
return .success(html)
}
.subscribe(htmlSubject)

What is the type of struct.self in swift?

I have a structural variable that extends Codable protocol, and want to memorize its type, so that I can use it next time in JSONDecoder. However, when a private variable is declared, its class needs to be specified, but whatever class I'm trying use, I cannot use the result later in JSONDecoder. So my question is what is the type of myVariable.self?
I have this problem, because I want to specify the class when a view decoder is initialized. During the initialization, I call the following function (this part of the code works well):
func getData<T: Codable>(fromURL: String, onSuccess: #escaping (T) -> Void, onError: #escaping (String) -> Void) {
let url = URL(string: fromURL)!
let task = session.dataTask(with: url) {(data, response, error) in
DispatchQueue.main.async {
if let error = error {
onError(error.localizedDescription)
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
onError("Invalid data or response")
return
}
do {
if response.statusCode == 200 {
let ret = try JSONDecoder().decode(T.self, from: data)
onSuccess(ret)
} else {
let err = try JSONDecoder().decode(APIError.self, from: data)
onError(err.message)
}
} catch {
onError(error.localizedDescription)
}
}
}
task.resume()
}
This part of the code works well. However, if I need to upload more data, I need to know T.self for proper decoder, so I need to keep it. A solution could be to create a private variable:
private var type: ??? // what type should I put here?
and to write in the function's body
type = T.self
But whichever type I try, it doesn't work.
What type should I put there? Or, maybe, there are some other solutions?
You can declare the property
private var type: Codable.Type
or wrap the method in a generic struct or class and declare the property
class CodableWrapper<T : Codable> {
private var type: T.Type
...

Swift: Pass type from property to generic function

For my networking module, I have this protocol that I adopt for accessing different parts of the API:
protocol Router: URLRequestConvertible {
var baseUrl: URL { get }
var route: Route { get }
var method: HTTPMethod { get }
var headers: [String: String]? { get }
var encoding: ParameterEncoding? { get }
var responseResultType: Decodable.Type? { get }
}
I'm adopting this with enums that look like this:
enum TestRouter: Router {
case getTestData(byId: Int)
case updateTestData(byId: Int)
var route: Route {
switch self {
case .getTestData(let id): return Route(path: "/testData/\(id)")
case .updateTestData(let id): return Route(path: "/testDataOtherPath/\(id)")
}
}
var method: HTTPMethod {
switch self {
case .getTestData: return .get
case .updateTestData: return .put
}
}
var headers: [String : String]? {
return [:]
}
var encoding: ParameterEncoding? {
return URLEncoding.default
}
var responseResultType: Decodable.Type? {
switch self {
case .getTestData: return TestData.self
case .updateTestData: return ValidationResponse.self
}
}
}
I want to use Codable for decoding nested Api responses. Every response consists of a token and a result which content is depending on the request route.
For making the request I want to use the type specified in the responseResultType property in the enum above.
struct ApiResponse<Result: Decodable>: Decodable {
let token: String
let result: Result
}
extension Router {
func asURLRequest() throws -> URLRequest {
// Construct URL
var completeUrl = baseUrl.appendingPathComponent(route.path, isDirectory: false)
completeUrl = URL(string: completeUrl.absoluteString.removingPercentEncoding ?? "")!
// Create URL Request...
var urlRequest = URLRequest(url: completeUrl)
// ... with Method
urlRequest.httpMethod = method.rawValue
// Add headers
headers?.forEach { urlRequest.addValue($0.value, forHTTPHeaderField: $0.key) }
// Encode URL Request with the parameters
if encoding != nil {
return try encoding!.encode(urlRequest, with: route.parameters)
} else {
return urlRequest
}
}
func requestAndDecode(completion: #escaping (Result?) -> Void) {
NetworkAdapter.sessionManager.request(urlRequest).validate().responseData { response in
let responseObject = try? JSONDecoder().decode(ApiResponse<self.responseResultType!>, from: response.data!)
completion(responseObject.result)
}
}
}
But in my requestAndDecode method It throws an compiler error (Cannot invoke 'decode' with an argument list of type '(Any.Type, from: Data)'). I can't use ApiResponse<self.responseResultType!> like that.
I could make this function generic and call it like this:
TestRouter.getTestData(byId: 123).requestAndDecode(TestData.self, completion:)
but then I'd have to pass the response type everytime I want to use this endpoint.
What I want to achieve is that the extension function requestAndDecode takes it response type information from itself, the responseResultType property.
Is this possible?
Ignoring the actual error report you have a fundamental problem with requestAndDecode: it is a generic function whose type parameters are determined at the call site which is declared to return a value of type Result yet it attempts to return a value of type self.responseResultType whose value is an unknown type.
If Swift's type system supported this it would require runtime type checking, potential failure, and your code would have to handle that. E.g. you could pass TestData to requestAndDecode while responseResultType might be ValidationResponse...
Change the JSON call to:
JSONDecoder().decode(ApiResponse<Result>.self ...
and the types statically match (even though the actual type that Result is is unknown).
You need to rethink your design. HTH
Create a Generic function with Combine and AlomFire. You can use it for all method(get, post, put, delete)
func fatchData<T: Codable>(requestType: String, url: String, params: [String : Any]?, myType: T.Type, completion: #escaping (Result<T, Error>) -> Void) {
var method = HTTPMethod.get
switch requestType {
case "Get":
method = HTTPMethod.get
case "Post":
method = HTTPMethod.post
print("requestType \(requestType) \(method) ")
case "Put":
method = HTTPMethod.put
default:
method = HTTPMethod.delete
}
print("url \(url) \(method) \(AppConstant.headers) ")
task = AF.request(url, method: method, parameters: params, encoding: JSONEncoding.default, headers: AppConstant.headers)
.publishDecodable(type: myType.self)
.sink(receiveCompletion: { (completion) in
switch completion{
case .finished:
()
case .failure(let error):
// completion(.failure(error))
print("error \(error)")
}
}, receiveValue: {
[weak self ](response) in
print("response \(response)")
switch response.result{
case .success(let model):
completion(.success(model))
print("error success")
case .failure(let error):
completion(.failure(error))
print("error failure \(error.localizedDescription)")
}
}
)
}

Swift upload file via WCF

I've been fighting this for over 10 hours and done my best before coming here. Many tutorials I've looked out ended up being outdated by a version or two and/or missing some key knowledge in the explanation.
I'm trying to upload a PDF to a server using a WCF Rest Service. When I debug on the WCF service, the variable named document is a null stream. I've searched the web many hours trying many different things and I've exhausted MANY attempts to get such a little thing to work! What is really annoying is that this isn't the first time someone has needed to do this and yet I haven't found an answer anywhere.
If there is a better way to post the PDF other than what I've posted, I'm open to suggestions but I don't want to use third-party frameworks.
Requirements:
- Using the WCF service is required
- I'm not saving to a file system or UNC
I need to get a valid memory stream passed to the service and I can take care of the function code from there. I've tried using a Base64DataString before this and I could get that to work either. If you wanted to provide that as an option, I'm open to it.
Please help!
Swift 4 code:
import PDFKit
class FileUpload {
static func generateBoundaryString() -> String {
return "Boundary-\(UUID().uuidString)"
}
static func dataUploadBodyWithParameters(_ parameters: [String: Any]?, filename: String, mimetype: String, dataKey: String, data: Data, boundary: String) -> Data {
var body = Data()
// encode parameters first
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(dataKey)\"; filename=\"\(filename)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.append(data)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
print(body)
return body
}
static func uploadData(_ data: Data, toURL urlString: String, withFileKey fileKey: String, completion: ((_ success: Bool, _ result: Any?) -> Void)?) {
if let url = URL(string: urlString) {
// build request
let boundary = generateBoundaryString()
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// build body
let body = dataUploadBodyWithParameters(nil, filename: "iOSUpload.pdf", mimetype: "application/pdf", dataKey: fileKey, data: data, boundary: boundary)
request.httpBody = body
//UIApplication.shared.isNetworkActivityIndicatorVisible = true
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if data != nil && error == nil {
do {
let result = try JSONSerialization.jsonObject(with: data!, options: [])
print(result)
DispatchQueue.main.async(execute: { completion?(true, result) })
} catch {
DispatchQueue.main.async(execute: { completion?(false, nil) })
}
} else { DispatchQueue.main.async(execute: { completion?(false, nil) }) }
//UIApplication.shared.isNetworkActivityIndicatorVisible = false
}).resume()
} else { DispatchQueue.main.async(execute: { completion?(false, nil) }) }
}
}
extension Data {
mutating func appendString(_ string: String) {
let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true)
append(data!)
}
}
}
WCF Relevant Code:
<ServiceContract>
Public Interface IWebService
<OperationContract>
<WebInvoke(Method:="POST", UriTemplate:="UploadFile/{fileName}")>
Function UploadFile(fileName As String, document As Stream) As String
End Interface
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)>
Public Class MyWebService : Implements IWebService
Public Function UploadFile(fileName As String, document As Stream) As String Implements IWebService.UploadFile
'document is null
End Function

type (string , anyobject) has no subscript member

I use dataTaskWithRequest and get json of array with two objects in it (these objects are key,value) and I want check one value of key in two objects.
this is my code :
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
var jsonArray: [String:AnyObject]!
do {
jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as? [String:AnyObject]
} catch {
print(error)
}
for json in jsonArray {
print("object json reciver :",json)
//type (string , anyobject) has no subscript member
print("state :",json["state"])
}
})
json is a variable typed (String, AnyObject). You cannot subscript tuples.
Replace the following:
print("state :",json["state"])
with:
print("\(json.0) : \(json.1)")
You need to cast jsonArray to NSArray.