I was surfing in swift STD particularly in the "Int structure", and there is a function named
func encode(to encoder: Encoder) throws
https://developer.apple.com/documentation/swift/int/2894461-encode.
I saw many examples using this function, however all the examples contained structs and classes I was wondering how can I use this function to encode a variable of type Int.
Thanks
As you may be aware, Int conforms to Encodable which defines the encode(to:) method that you have discovered. You can use an encoder such as a JSONEncoder or PropertyListEncoder to encode any type that conforms to the Encodable protocol. For example:
let encoder = JSONEncoder()
let jsonData = try encoder.encode(5)
You don't need to worry about calling encode(to:) on Int directly. Your encoder should handle all of that under the hood.
Related
I'm currently working on updating a large project from Objective-C to Swift and I'm stumped on how to mimic some logic. Basically we have a class with a protocol which defines a couple functions to turn any class into a JSON representation of itself.
That protocol looks like this:
#define kJsonSupport #"_jsonCompatible"
#define kJsonClass #"_jsonClass"
#protocol JsonProtocol <NSObject>
- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);
#end
I've adapted that to Swift like this
let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"
protocol JsonProtocol
{
func convertToJSON() -> NSDictionary
init(json: NSDictionary)
}
One of the functions in the ObjC class runs the convertToJSON function for each object in an NSDictionary which conforms to the protocol, and another does the reverse, creating an instance of the object with the init function. The output dictionary also contains two keys, one denoting that the dictionary in question supports this protocol (kJsonSupport: BOOL), and another containing the NSString representation of the class the object was converted from (kJsonClass: NSString). The reverse function then uses both of these to determine what class the object was converted from to init a new instance from the given dictionary.
All of the classes are anonymous to the function itself. All we know is each class conforms to the protocol, so we can call our custom init function on it.
Here's what it looks like in ObjC:
Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:#protocol(JsonProtocol)])
{
Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
[arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];
}
However, I'm not sure how to make this behavior in Swift.
Here's my best attempt. I used Swiftify to try and help me get there, but the compiler isn't happy with it either:
let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol
{
weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
arr.add(jsonClass.init(json: obj))
}
I get several errors on both the weak var line and the arr.add line, such as:
Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type
'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type
Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'
Extraneous argument label 'json:' in call
Is there any way for me to instantiate from an unknown class which conforms to a protocol using a custom protocol init function?
You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.
The most important thing is that all the objects must be #objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.
You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:
#objc(Person) // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}
extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}
This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).
With that, in Swift, this code would be written this way:
if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}
The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.
Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.
I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.
When I work on the opaque types, I read this section in the official documents of Swift.
Another problem with this approach is that the shape transformations
don’t nest. The result of flipping a triangle is a value of type
Shape, and the protoFlip(:) function takes an argument of some type
that conforms to the Shape protocol. However, a value of a protocol
type doesn’t conform to that protocol; the value returned by
protoFlip(:) doesn’t conform to Shape. This means code like
protoFlip(protoFlip(smallTriange)) that applies multiple
transformations is invalid because the flipped shape isn’t a valid
argument to protoFlip(_:).
This part made me consider about nested functions whose return type is protocol and I wanted to play about the protocol return types in the playground. As a result, I created a protocol called Example and also, a non generic and generic concrete types that conform to Example protocol. I kept "sample" method implementations which is protocol requirement as simple as possible because of focusing return types.
protocol Example {
func sample(text: String) -> String
}
struct ExampleStruct: Example {
func sample(text: String) -> String {
return text
}
}
struct ExampleGenericStruct<T: Example>: Example {
var t: T
func sample(text: String) -> String {
return t.sample(text: "\n")
}
}
After that, I created a generic function which has an argument constraint by Example protocol and returns Example protocol. Then, I tested my function as nested.
func genericTestExample<T: Example>(example: T) -> Example {
return ExampleGenericStruct(t: example)
}
genericTestExample(example: genericTestExample(example: ExampleStruct()))
I got this error:
Value of protocol type 'Example' cannot conform to 'Example'; only
struct/enum/class types can conform to protocols
This is what I expected. Function returns the protocol itself, not the concrete type that conforms it.
Finally, I wrote an another function.
func testExample(example: Example) -> Example {
if example is ExampleStruct {
return example
}
return ExampleGenericStruct(t: ExampleStruct())
}
When I run the code, I could nest this function successfully.
testExample(example: testExample(example: ExampleStruct()))
I can pass any value to both genericTestExample and testExample functions as long as it conforms to Example protocol. Also, they have the same protocol return type. I don't know why I could nest testExample function while I could not nest genericTestExample function or vise versa.
swift 5.4
2021-09-27 08:36 UTC
#ceylanburak
you should use the Opaque Types in swift.
// some Example is **Opaque Types** !!!
func genericTestExample<T: Example>(example: T) -> some Example {
return ExampleGenericStruct(t: example)
}
now follow your previous mind
genericTestExample(example: genericTestExample(example: ExampleStruct()))
and it equals to
let e1: some Example = genericTestExample(example: ExampleStruct())
genericTestExample(example: e1)
As far as I understand, a protocol is a blueprint for properties, methods, and other requirements without the actual implementation. The conformance to a protocol means providing the actual implementation of the blueprint. But, I frequently see classes and struct's adopting a protocol without actually providing the implementation.
For example:
class Item: Codable {
var title: String = ""
var done: Bool = false
}
var itemArray = [Item]()
let encoder = PropertyListEncoder()
do {
let data = try encoder.encode(itemArray)
try data.write(to: dataFilePath!)
} catch {
print("Error encoding item array, \(error)")
}
The Item class here adopts the Codable protocol and the instance of the class is used as an argument to the instance method of PropertyListEncoder(). However, no implementation for the Codable protocol was provided or used in this process.
There are protocols that offer what is called automatic synthesis. In the case of Codable that means that as long as the properties of Item conform to Codable, methods such as init(from:) or encode(to:) are automatically added to that type.
As long as all of its properties are Codable, any custom type can also be Codable.
Encoding and Decoding Custom Types
Another great example is Equatable, see Conforming to the Equatable Protocol
Automatic synthesis is done via protocol extensions.
The Codable protocol is one of a few protocols (Equatable, Hashable) that can be synthesized by the compiler so long as all properties of a type also conform to that protocol. This means that the compiler is generating the implementation for the protocol that you noticed was missing
Is there a way to keep Swift's default implementation for a Decodable class with only Decodable objects but one exception?
So for example if I have a struct/class like that:
struct MyDecodable: Decodable {
var int: Int
var string: String
var location: CLLocation
}
I would like to use default decoding for int and string but decode location myself.
So in init(from decoder:) i would like to have something like this:
required init(from decoder: Decoder) throws {
<# insert something that decodes all standard decodable properties #>
// only handle location separately
let container = try decoder.container(keyedBy: CodingKeys.self)
location = <# insert custom location decoding #>
}
Is there a way to keep Swift's default implementation for a Decodable class with only Decodable objects but one exception
Unfortunately no. To be Decodable all properties must be Decodable. And if you are going to write a custom init you must initialize (and therefore decode) all properties yourself.
Apple knows this is painful and has given some thought to the matter, but right now a custom init for a Decodable is all or nothing.
As has been suggested in a comment you might work around this by splitting your struct into two separate types. That way you could have a type with just one property, you initialize it manually, and you’re all done.
Please let me know what I am doing wrong in below statements, its not compiling (where I have casted the Int64 to Float)
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
NSLog("data came")
var expectedDownloadSize:Float = (Float) response.expectedContentLength
}
It looks like you are getting confused between Obj-C and Swift in a couple of places. As Robert says you need to place the value you want to case within parenthesis wrapped around the type, eg: Float(response.expectedContentLength). It is also worth noting that NSLog has been deprecated in Swift in favour of print(...). Additionally, you no longer need to explicitly declare the variable type as Swift will pick this up automatically.
Casting in Swift is done slightly differently:
var expectedDownloadSize = Float(response.expectedContentLength)
Rather than casting to a value, you actually use Float's initialiser, which takes an Int64 parameter.
in swift you can do like this:
var expectedDownloadSize:Float = Float (response.expectedContentLength)
try