I am trying to define a protocol for which I would like to add conformance to several Foundation classes as well as some custom types of my own. I first tried doing this with a convenience initializer in the protocol, but that does not seem possible. I read in the linked thread on the Apple dev forums where they talked about using a class method that returns type Self, but I am unable to figure out how to go about this.
typealias JSONObject = AnyObject
protocol JSONParseable {
static func fromJSONObject(jsonObject: JSONObject) throws -> Self
}
extension NSURL: JSONParseable {
class func fromJSONObject(jsonObject: JSONObject) throws -> Self {
guard let jsonString = jsonObject as? String else {
throw JSONParseError.ConversionError(message: "blah")
}
guard let result = NSURL(string: jsonString) else {
throw JSONParseError.ConversionError(message: "blah")
}
return result // Error: cannot convert return expression of type 'NSURL' to return type 'Self'
}
}
I found a similar question but the answer there was to mark the class as final -- I obviously can't do that on a Foundation class.
Could someone explain how to fix my approach above? Or suggest a different approach for adding protocol conformance to Foundation classes?
Use self.init(....) instead of NSURL(....) since this also needs to work for NSURL subclasses.
protocol JSONParseable {
static func fromJSONObject(jsonObject: JSONObject) throws -> Self
}
extension NSURL : JSONParseable {
class func fromJSONObject(jsonObject: JSONObject) throws -> Self {
guard let jsonString = jsonObject as? String else {
throw JSONParseError.ConversionError(message: "blah")
}
guard let result = self.init(string: jsonString) else {
throw JSONParseError.ConversionError(message: "blah")
}
return result
}
}
Hack : define a typealias T, this will return an NSURL even for subclasses.
protocol JSONParseable {
typealias T = Self
static func fromJSONObject(jsonObject: JSONObject) throws -> T
}
extension NSURL : JSONParseable {
typealias T = NSURL
class func fromJSONObject(jsonObject: JSONObject) throws -> T {
guard let jsonString = jsonObject as? String else {
throw JSONParseError.ConversionError(message: "blah")
}
guard let result = NSURL(string: jsonString) else {
throw JSONParseError.ConversionError(message: "blah")
}
return result
}
}
Related
I noticed that I have a lot of repeated code for getting/sending JSON to my app's API, but the only thing that is different is what entity is being (de)serialized. So I came up with the following design (for brevity only the GET method):
HTTPClient.swift
func getEntityJSON<T: JSONSerializable>(
type: T.Type,
url: String,
completionHandler: #escaping (_ result: T?,
_ headers: [String: String]?,
_ statusCode: Int?,
_ error: Error?) -> Void) {
HttpClient.sharedInstance().getJSON(url, completionHandler: { (jsonData, headers, statusCode, error) in
if let error = error {
completionHandler(nil, headers, statusCode, error)
} else {
if let entityData = jsonData {
completionHandler(T.fromJSON(data: entityData), headers, statusCode, nil)
}
}
})
}
to intend to use it like this:
HTTPClient.sharedInstance().getEntity(type: Translation, url: url) { (translation, _, _, _) in
// do stuff with Translation instance
}
and here is the JSONSerializable protocol:
import Foundation
import SwiftyJSON
protocol Serializable: Codable {
static func deserialize<T: Codable>(data: Data) -> T?
func serialize() -> Data?
}
extension Serializable {
static func deserialize<T: Decodable>(data: Data) -> T? {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? decoder.decode(T.self, from: data)
}
func serialize() -> Data? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? encoder.encode(self)
}
}
protocol JSONSerializable: Serializable {
func toJSON() -> JSON?
static func fromJSON<T>(data: JSON) -> T?
}
extension JSONSerializable {
func toJSON() -> JSON? {
if let data = self.serialize() {
return try? JSON(data: data)
} else {
return nil
}
}
}
This allows me to define a struct like this:
Translation.swift
struct Translation: Hashable, Identifiable, JSONSerializable {
var id: UUID
...
static func fromJSON(data: JSON) -> Translation? {
and I will get serialize, deserialize, toJSON functions. But the compiler complains that Translation does not conform to JSONSerializable, due to lack of:
static func fromJSON<T>(data: JSON) -> T?. I thought I would be able to implement that function with a concrete type, Translation in this case.
I would like to be both able to do Translations.fromJSON(data: data) as well as T.fromJSON(data: data). How can I achieve this?
Generics (<T>) means that your method can work with any type that the caller (not the callee, not the implementer, not anyone else) specifies. In the case of fromJSON, this is clearly not the case. Any concrete implementation of fromJSON only works with T being the enclosing type, so fromJSON is not suitable to be generic.
When a method "only works with T being the enclosing type", it is the use case of Self types:
protocol JSONSerializable: Serializable {
func toJSON() -> JSON?
static func fromJSON(data: JSON) -> Self?
}
This way, your Translation implementation would conform to the protocol.
Similarly, deserialise should also be declared as:
static func deserialize(data: Data) -> Self?
If you actually want a JSONSerialisable that can turn JSON into any type that the caller wants, then it's less of a JSONSerialisable, and more of a JSONSerialiser, and it doesn't make much sense to inherit from Codable in that case.
OK I ended up implementing a JSONSerializer, because I couldn't get it to work for collections of items to be (de)serialized. This is how it currently looks:
import Foundation
import SwiftyJSON
protocol JSONSerializable {
static func fromJSON(data: JSON) -> Self?
}
class JSONSerializer {
static func deserialize<T: Decodable>(_ type: T.Type, data: Data) -> T? {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? decoder.decode(type, from: data)
}
static func serialize<T: Encodable>(_ object: T) -> Data? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? encoder.encode(object)
}
static func fromJSON<T: JSONSerializable>(type: T.Type, data: JSON) -> T? {
T.fromJSON(data: data)
}
static func toJSON<T: Encodable>(_ object: T) -> JSON? {
if let data = Self.serialize(object) {
return try? JSON(data: data)
} else {
return nil
}
}
}
I will look into the fromJSON function later to see if I can completely get rid of JSONSerializable and implemented it in the JSONSerializer like this:
static func fromJSON<T: Decodable>(type: T.Type, data: JSON) -> T? {
if let jsonData = try? data.rawData() {
return Self.deserialize(type, data: jsonData)
}
return nil
}
I wanna implement the function that drag a tableview cell on a "Delete Icon" to delete it.
Now my question is how can I cast my Type to NSItemProviderWriting/NSItemProviderReading to use drag and drop.
I'm following this tutorial: https://exploringswift.com/blog/creating-a-nsitemprovider-for-custom-model-class-drag-drop-api. While I failed and I still could not understand how it works.
It says that Type 'Task' does not conform to protocol 'Decodable'.('Task' is my custom model) and I also don't know what 'kUTTypeData' is in this tutorial...
Can anyone help to how to implement these protocol?
import Foundation
import CoreData
#objc(Task)
public class Task: NSManagedObject, NSItemProviderWriting, NSItemProviderReading, Codable {
public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) {
<#code#>
}
required public init(from decoder:Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
}
public static var writableTypeIdentifiersForItemProvider: [String] {
return []
}
public func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: #escaping (Data?, Error?) -> Void) -> Progress? {
let progress = Progress(totalUnitCount: 100)
do {
let encoder = JSONEncoder()
let data = try encoder.encode(self)
progress.completedUnitCount = 100
completionHandler(data, nil)
} catch {
completionHandler(nil, error)
}
return progress
}
public static var readableTypeIdentifiersForItemProvider: [String] {
return []
}
public static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
let decoder = JSONDecoder()
do {
let myJSON = try decoder.decode(Task.self, from: data)
return myJSON as! Self
} catch {
fatalError("Err")
}
}
}
Instead of making the NSManagedObject conform to the protocols, have you considered using .objectID.uriRepresentation() in the drag/drop handlers?
Here you can find an explanation for implementing Codable with Core Data entities:
https://stackoverflow.com/a/46917019
I am trying to understand how much I can use generics in protocols. The idea I have in mind is the following:
protocol Network {
func fetchCodable<T:Codable>(urlRequest:URLRequest, completion:#escaping (Result<T,Error>)->Void)
}
Then I create a class called AppNetwork that implements the network protocol.
extension AppNetwork:Network{
func fetchCodable<T>(urlRequest: URLRequest, completion: #escaping (Result<T, Error>) -> Void) where T : Decodable, T : Encodable {
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else{
completion(.failure(AppNetworkError.dataCorrupted))
return
}
do {
let response = try JSONDecoder().decode(T.self, from: data)
completion(.success(response))
}
catch let decodeError{
completion(.failure(decodeError))
}
}.resume()
}
This class is part of NetworkHelper that implements functions to retrieve data such as:
class NetworkHelper{
let network:Network = AppNetwork()
}
...
//MARK:- Public methods
extension NetworkHelper{
func getVenueDetails(inLocation:String, offset:Int, limit:Int, radius:Int = 1000,completion:(Result<VenueDetailsSearchResult, Error>)->Void){
guard let foursquareConfig = foursquareConfig else{
completion(.failure(NetworkHelper.NetworkHelperError.invalidFourSquareConfig))
return
}
var venuesURLString = EndPoints.venueSearch.rawValue + "?"
venuesURLString += foursquareConfig.getFormattedParams
venuesURLString += "&near=\(inLocation)&radius=\(radius)&offset=\(offset)&limit=\(limit)"
guard let venuesURL = URL(string: venuesURLString) else{
completion(.failure(NetworkHelper.NetworkHelperError.invalidURL))
return
}
let venuesURLRequest = URLRequest(url: venuesURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 10)
network.fetchCodable(urlRequest: venuesURLRequest) { result in
}
}
}
In this case a am trying to get a Result but I a get a message error: "Generic parameter 'T' could not be inferred"
How am I supposed to do it if I can do it like that?
Thank you
You'll have to tell the compiler about the type you're expecting.
network.fetchCodable(urlRequest: venuesURLRequest) { (_ result: Result<VenueDetailsSearchResult, Error>) in
// ...
}
So I'm writing my networking code using a router design pattern. I'm writing a new router for different components of my app (should i be doing this? I try to limit my objects lines of code). Heres my router enum. If I was using a class, I could define a method once to populate variables like HTTPMethod and override them if necessary. Is there a way to do this with enums? Is it worth implementing or should i repeat the same code. There are a few other places besides httpMethod such as URL construction where I think this could be helpful.
I was thinking i could do something with protocols but am not sure if I'm wasting my time.
enum PRRouter: URLRequestConvertible {
static let baseURLString = "http://localhost:8000/"
case get(Int)
case create([String : Any])
case delete(Int)
func asURLRequest() throws -> URLRequest {
var method: HTTPMethod{
switch self {
case .get:
return .get
case .create:
return .post
case.delete:
return .delete
}
}
let params : ([String : Any]?) = {
switch self {
case .get, .delete:
return nil
case .create(let newTodo):
return newTodo
}
}()
let url : URL = {
let relativePath: String?
switch self{
case .get(let number):
relativePath = "test/\(number)"
case .create:
relativePath = "test/"
case .delete:
relativePath = "test/"
}
var url = URL(string: PRRouter.baseURLString)!
if let relativePath = relativePath {
url = url.appendingPathComponent(relativePath)
}
return url
}()
var urlRequest = URLRequest(url:url)
urlRequest.httpMethod = method.rawValue
let encoding = JSONEncoding.default
return try encoding.encode(urlRequest, with: params)
}
Make the enum conform to a protocol with a default implementation.
protocol P {
func f()
}
extension P {
func f() { print("default implementation") }
}
enum E: P {
case Foo
}
let e = E.Foo
e.f()
I do something similar in my own project. Here is an example based on your code to get you started:
protocol APIProtocol {
var path: String { get }
var method: HTTPMethods { get }
var bodyParameters: [String: Any?]? { get }
}
enum HTTPMethods: String {
case get = "GET"
case post = "POST"
}
enum PRRouter: APIProtocol {
case get(Int)
case create([String : Any])
case delete(Int)
var path: String {
switch self {
case let .get(number):
return "test/\(number)"
default:
return "test"
}
}
var method: HTTPMethods {
return .get
}
var bodyParameters: [String : Any?]? {
return nil
}
}
extension APIProtocol {
func execute(completion: #escaping ((Data?) -> Void)) -> URLSessionDataTask? {
guard let url = URL(string: "http://localhost:8000/\(path)") else { return nil }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
if let bodyParameters = bodyParameters {
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: bodyParameters, options: [.prettyPrinted])
}
let task = URLSession.shared.dataTask(with: urlRequest) { (data, urlResponse, error) in
completion(data)
}
task.resume()
return task
}
}
Finally you can use it like this:
let dataTask = PRRouter.get(2).execute { (data) in
//
}
You could extend this further by changing the completion block in the execute function to return a deserialized object.
I am currently working my way through he Treehouse IOS Swift course, and we are building a weather app. I've gotten to a point where I keep getting an error that my class isn't conforming to my protocol, but I can't figure out why.
Here is my protocol declaration:
public protocol APIClient {
var configuration: URLSessionConfiguration { get }
var session: URLSession { get }
func JSONTaskWithRequest(request: URLRequest, completion: JSONTaskCompletion) -> JSONTask
func fetch<T: JSONDecodable>(request: URLRequest, parse: (JSON) -> T?, completion: (APIResult<T>) -> Void)
}
And then I have a protocol extension where I have default implementations of my two methods declared in the protocol, and one of the methods is calling the other method, as you can see below.
public extension APIClient {
func JSONTaskWithRequest(request: URLRequest, completion: #escaping JSONTaskCompletion) -> JSONTask {
let task = session.dataTask(with: request) { data, response, error in
guard let HTTPResponse = response as? HTTPURLResponse else {
let userInfo = [
NSLocalizedDescriptionKey: NSLocalizedString("Missing HTTP Response", comment: "")
]
let error = NSError(domain: BPSnetworkingErrorDomain, code: MissingHTTPReponseError, userInfo: userInfo)
completion(nil, response as! HTTPURLResponse, error)
return
}
if data == nil {
if let error = error {
completion(nil, response as! HTTPURLResponse, error as NSError?)
}
} else {
switch HTTPResponse.statusCode {
case 200:
do {
let JSON = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: AnyObject]
completion(JSON, HTTPResponse, nil)
} catch let error as NSError {
completion(nil, HTTPResponse, error)
}
default: print("Received HTTP Response \(HTTPResponse.statusCode) - not handled")
}
}
}
return task
}
public func fetch<T>(request: URLRequest, parse: #escaping (JSON) -> T?, completion: #escaping (APIResult<T>) -> Void) {
let task = JSONTaskWithRequest(request: request) { json, response, error in
DispatchQueue.main.async {
guard let json = json else {
if let error = error {
completion(.Failure(error))
} else {
let error = "Something is really wrong. There was no JSON object created, but there was no error either."
completion(.Failure(error as! Error))
}
return
}
if let value = parse(json) {
completion(.Success(value))
} else {
let error = NSError(domain: BPSnetworkingErrorDomain, code: unexpectedResponseError, userInfo: nil)
completion(.Failure(error))
}
}
}
task.resume()
}
}
Then I have my class declaration where I am getting my non conformity error.
final class ForecastAPIClient: APIClient {
let configuration: URLSessionConfiguration
lazy var session: URLSession = {
return URLSession(configuration: self.configuration)
}()
private let token: String
init(config: URLSessionConfiguration, APIKey: String) {
self.configuration = config
self.token = APIKey
}
convenience init(APIKey: String) {
self.init(config: URLSessionConfiguration.default, APIKey: APIKey)
}
func fetchCurrentWeather(coordinate: Coordinate, completion: #escaping (APIResult<CurrentWeather>) -> Void) {
let request = Forecast.Current(token: self.token, coordinate: coordinate).request
fetch(request: request, parse: { (JSON) -> CurrentWeather? in
if let currentWeatherDictionary = JSON["currently"] as? [String: AnyObject] {
return CurrentWeather(JSON: currentWeatherDictionary)
} else {
return nil
}
}, completion: completion)
}
}
I've done a lot of reading around for several hours trying to figure out what is going on here. From what I understand, I shouldn't need to define those two methods in my class since they have default implementations in the protocol extension. I came across the issue of public/internal types and things like that, that someone else was having here on StackExchange with their extensions, (as you an see by my labeling things public and what not), but that didn't seem to help in my case. The only way I've been able to get the error to go away, is by commenting out those method declarations in the original protocol declaration. Which seems to indicate to me that either the class, or the protocol, or something isn't seeing the extension for some reason, however, if I command click on the fetch method call in the class declaration, it takes me to the definition of it in the extension. I haven't been able to find a solution, or even someone who is doing this similar thing, there are several people on Treehouse that seem to be having this same issue as well.
Also, I download the teachers code, and converted it to Swift 3, and it was getting the same error as well, so maybe it's an issues with having a different version of Xcode the what he used when he made the video?
I feel like I'm kind of grasping at straws a little bit, but I really am eager to get this figured out, so any possible help would be so much appreciated.
Thank you!
I used Xcode's playground to test and play around with your code. I took your code (protocol declaration, protocol extension, and class declaration) and heavily simplified the JSONTaskWithRequest() and fetch() functions. The code compiled with no "non conformity error." Here is the code I used:
//: Playground :
import UIKit
// protocol declaration
public protocol APIClient {
var configuration: URLSessionConfiguration { get }
var session: URLSession { get }
func JSONTaskWithRequest()
func fetch()
}
// protocol extension
public extension APIClient {
func JSONTaskWithRequest() {
print("JSONTaskWithRequest here")
}
func fetch() {
print("fetch here")
}
}
// class declaration
final class ForecastAPIClient: APIClient {
let configuration: URLSessionConfiguration
lazy var session: URLSession = {
return URLSession(configuration: self.configuration)
}()
private let token: String
init(config: URLSessionConfiguration, APIKey: String) {
self.configuration = config
self.token = APIKey
}
}
I suspect that there is a bug in JSONTaskWithRequest and/or fetch. I suggest you isolate either function to figure out which one is giving you the error. Then debug from there.
Also, just another suspicion. In the extension's JSONTaskWithRequest function implementation, you have:
let task = session.dataTask(with: request) {...}
return task
JSONTaskWithRequest is required to return a JSONTask. Maybe you need to downcast task:
return task as! JSONTask
I couldn't use your provided code because things like JSONTaskCompletion and JSONDecodable aren't recognized by Swift. Are you using a third party JSON swift library?