How to adhere to protocol method with a Raw type in method argument? - ios

protocol Measurement {
mutating func convert(#toUnit: String)
}
enum MassUnit : String {
case Milligram = "mg"
}
enum VolumeUnit : String {
case Milliliter = "ml"
}
struct Mass : Measurement {
mutating func convert(#toUnit: MassUnit)
// Build error: Does not adhere to 'Measurement'
}
struct Volume : Measurement {
mutating func convert(#toUnit: VolumeUnit)
// Build error: Does not adhere to 'Measurement'
}
func +<T: Measurement> (left:T, right:T) -> Measurement {
let newRightValue = right.convert(toUnit: left.unit)
return T(quantity: left.quantity + newRightValue.quantity , unit: left.unit)
}
How can I make Mass adhere correctly to Measurement? What change in the Measurement protocol is needed to get it to work with enum of type String?
Question updated with more information about why the convert method signature should say something about the argument given. The code is part of an open source Unit framework i'm building called Indus Valley

You probably confuse enum MassUnit : String with inheritance.
Contrary to class ChildClass : ParentClass which denotes that ChildClass inherits from ParentClass, enum MassUnit : String has a slightly different meaning, telling that the rawType of the enum is String, not that the enum inherits the String type.
So MassUnit is not of type String. You MassUnit's rawValues are of type String, but to access that you need to call the rawValue property of the enum to get that String equivalent.
Therefore, mutating func convert(#toUnit: String) and mutating func convert(#toUnit: MassType) are not compatible, as MassType is not a String itself. Only its rawValue is.

When dealing with your unit conversions, I advise not trying to use Strings to represent the units when converting the way you've got setup above. That's going to make your code complicated with checking the String can be converted to its respective enum every time you want to make a conversion. Also, what if you want to use the a MassUnit/VolumeUnit instead of a String?
I would recommend using the a setup similar to what I've outlined below. It references my previous answer - How to represent magnitude for mass in Swift?
(Note - I've excluded anything to do with the volume because it's basically the same as the implementation for the mass)
I'd make the units like so:
protocol UnitProtocol {
var magnitude: Int { get }
init?(rawValue: String)
}
// Taken from my previous answer.
enum MassUnit: String, UnitProtocol, Printable {
case Milligram = "mg"
case Gram = "g"
var magnitude: Int {
let mag: Int
switch self {
case .Milligram: mag = -3
case .Gram : mag = 0
}
return mag
}
var description: String {
return rawValue
}
}
// Not making this a method requirement of `UnitProtocol` means you've only got to
// write the code once, here, instead of in every enum that conforms to `UnitProtocol`.
func ordersOfMagnitudeFrom<T: UnitProtocol>(unit1: T, to unit2: T) -> Int {
return unit1.magnitude - unit2.magnitude
}
Then I'd make the masses/volumes like so:
protocol UnitConstruct {
typealias UnitType: UnitProtocol
var amount: Double { get }
var unit : UnitType { get }
init(amount: Double, unit: UnitType)
}
struct Mass : UnitConstruct {
let amount: Double
let unit : MassUnit
}
Now for the converting function! Using a global function means you don't need to rewrite the code for every type than conforms to UnitConstruct.
func convert<T: UnitConstruct>(lhs: T, toUnits unit: T.UnitType) -> T {
let x = Double(ordersOfMagnitudeFrom(lhs.unit, to: unit))
return T(amount: lhs.amount * pow(10, x), unit: unit)
}
// This function is for converting to different units using a `String`,
// as asked in the OP.
func convert<T: UnitConstruct>(lhs: T, toUnits unit: String) -> T? {
if let unit = T.UnitType(rawValue: unit) {
return convert(lhs, toUnits: unit)
}
return nil
}
You can then use the previous code like so:
let mass1 = Mass(amount: 1.0, unit: .Gram)
let mass2 = convert(mass1, toUnits: .Milligram) // 1000.0 mg
// Or, converting using Strings:
let right = convert(mass1, toUnits: "mg") // Optional(1000.0 mg)
let wrong = convert(mass1, toUnits: "NotAUnit") // nil

Related

Struct Value of type 'ViewController' has no member 'Continents'?

Struct can find in ViewController.
The error show Value of type 'ViewController' has no member 'Continents'?
this massage.
struct Continents {
var continents_name_list : String
}
This is a drop-down menu code:
func getDataToDropDown(cell: UITableViewCell, indexPos: Int, makeDropDownIdentifier: String) {
if makeDropDownIdentifier == "DROP_DOWN_NEW"{
let customCell = cell as! DropDownTableViewCell
customCell.countryNameLabel.text = self.Continents[indexPos]
print("test")
}
}
func numberOfRows(makeDropDownIdentifier: String) -> Int {
return self.Continents.count
}
func selectItemInDropDown(indexPos: Int, makeDropDownIdentifier: String) {
self.continente_text_label.text = "\(self.Continents[indexPos])"
self.dropDown.hideDropDown()
}
Frist:
Last:
Continents Is a type (like Int, String ecc...).
This means you can create arrays, variables etc. of type Continents.
Calling Continents.count is like calling Int.count which means nothing.
This is why it won't work. If you want to get the count of your array, you'll have to write the name of your array.count. In your case continents_list.count.
Anyway there are a few semantical errors in your code so I'll give you a few suggestions.
I assume you want the struct Continents to contain an array of strings. At the moment it will only receive a string. Try this instead:
struct Continents {
var continents_name_list : [String]
}
Furthermore, the continents_list array you created is of type String, you should declare it like this if you want it to be of type Continents:
var continents_list = Continents(continents_name_list: ["c1", "c2", "cn"])
My last advice would be to use an enum instead of a struct to store your continents.
enum Continents {
case Europe
case America
case Africa
case Antartica
case Asia
case Oceania
}

Swift: How to access variable element of an enum?

For hours I've been struggeling with getting an variable element of an enum.
The "Swifticons" - pod provides me with the following enum:
public enum WeatherType: Int {
static var count: Int {
return weatherIcons.count
}
public var text: String? {
return weatherIcons[rawValue]
}
case alien, barometer, celsius, owm300, owm301, owm302, and200moreOfTheseNames
}
private let weatherIcons = ["\u{f075}", "\u{f079}", and202moreOfTheseFontCharacters]
From an external API (openWeatherMap.org) I just get an weather code (let's say "300") - and I want to access Icon "owm300".
But how do I access this element of the enum without knowing the rawValue (which would be - say - 198)?
Here's the plan:
We need to enumerate all of the enum cases. We'll do that by iterating over raw values (luckily, WeatherType is backed by Int).
We will store lazily initialized dictionary that maps String to WeatherType.
And finally, we declare a static function that returns an optional WeatherType? because we can encounter an unknown value.
Here's the code:
extension WeatherType {
// just some little convenience
private typealias W = WeatherType
// 1. define the sequence of all cases
private static func allCases() -> AnySequence<W> {
return AnySequence { () -> AnyIterator<W> in
var raw = 0
return AnyIterator {
// Iterates while raw value can be converted to an enum case
if let next = W(rawValue: raw) {
raw += 1
return next
}
return nil
}
}
}
// 2. Static properties are lazy so we'll use them to store the dictionary with String to WeatherType mapping
private static let typeMap = W.allCases().reduce([String: W]()) { acc, next in
var acc = acc
acc[String(describing: next)] = next
return acc
}
// 3. Declare the mapping function
static func from(string: String) -> WeatherType? {
return W.typeMap[string]
}
}
Here's a little test:
let str = "301"
let type = WeatherType.from(string: "owm\(str)")
print(type == .owm301)
One of the easiest way I can think of is create some kind of mapping dictionary, where you would keep track of weather response code and WeatherType that it maps to like so,
let weatherCodeMapping: [Int: WeatherType] = [300: .owm300,
301: .owm301,
302: .owm302]
With this in place, you dont need to know any specific rawValue, you can simply get code by,
let weatherType = weatherCodeMapping[weatherCode]
And then create some other mapping for your image based on the weatherType.
let weatherIcon = weatherIconMapping[weatherType]
or create a single mapping directly from weather code to icon.
Swift doesn't currently have enumerable sequences of enum cases. One option that you have is to copy the list of icon names, then search for your icon's name, and use that index as the enum's rawValue:
let weatherIcons = [...]
let iconName = "owm300"
let possibleIconIndex = weatherIcons.index {
$0.caseInsensitiveCompare(iconName) == .orderedSame
}
if let iconIndex = possibleIconIndex {
let weatherIcon = WeatherIcon(rawValue: iconIndex)!
// ...
} else {
// graceful fallback for when the weather icon is missing
}
Of course, you need to figure out your own mapping between the data you get from the service and enum names, but that could be as simple as "owm\(weatherCode)".
When Swift 4.2 lands, you will be able to make your enums conform to a new protocol called CaseIterable. Enums that conform to it get a synthesized implementation of an allCases static variable. You will then be able to use that enumeration to build a string-to-enum dictionary automatically:
let nameToEnum = WeatherIcon.allCases.map { (String($0), $0) }
let mapping = Dictionary(uniqueKeysWithValues: nameToEnum)
That will however require WeatherIcon to be declared with the CaseEnumerable conformance, as adding it with an extension has no effect.

Why CustomStringConvertible protocol not working for Int?

public func +<T: CustomStringConvertible>(lhs: T, rhs: T)->String{
return lhs.description+rhs.description
}
let a:String = "A"
let i:Int = 0
print(a+i)
I am overloading '+' operator for CustomStringConvertible types. String and Int both confirms CustomStringConvertible protocol but it gives an error: "binary operator '+' cannot be applied to operands of type 'String' and 'Int' print(a+i)". It working fine when I apply it to 'String'+'NSNumber'.
don't know what is going behind the scene. why it is not working?
The problem is firstly (believe it or not) String doesn't conform to CustomStringConvertible. You'll therefore want to conform it yourself in order for it to return self for description (probably easier than writing another overload to deal with strings independently).
extension String:CustomStringConvertible {
public var description: String {
return self
}
}
Secondly, you need two generic parameters for your + overload, in order to allow it to accept different types for both parameters, while ensuring that both parameters conform to CustomStringConvertible:
public func +<T: CustomStringConvertible, U:CustomStringConvertible>(lhs: T, rhs: U)->String{
return lhs.description+rhs.description
}

Comparing type of Generic associated type in Swift

I have the next code:
protocol Flyable {
var airspeedVelocity: Double { get }
}
func topSpeed<T: CollectionType where T.Generator.Element == Flyable>(collection: T) -> Double {
return collection.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}
I understood, by reading the Swift Documentation that:
You write a where clause by placing the where keyword immediately after the list of type parameters, followed by constraints for associated types or equality relationships between types and associated types.
This is the example given on the docs:
func allItemsMatch<C1: Container, C2: Container where
C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer: C1, _ anotherContainer: C2) -> Bool {
// code
}
Note that when expressing that the associated type ItemType of the C1 type must conform to Equatable protocol you use : and not ==, why is that not the case in my example, where I have to use == to state that the Element associated type of T.Generator must conform with Flyable?
When changing == with : the compiler complains with this:
error: cannot invoke 'topSpeed' with an argument list of type '([Flyable])' note: expected an argument list of type '(T)'
You can achieve this without using Generics.
protocol Flyable {
var airspeedVelocity: Double { get }
}
func topSpeed(collection: [Flyable]) -> Double {
return collection.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}
class Airplane: Flyable {
var airspeedVelocity: Double = 10
}
class Rocket: Flyable {
var airspeedVelocity: Double = 50
}
topSpeed([Airplane(),Rocket()]) //50
I've found the reason here. The where clause has the next grammar:
conformance-requirement → type-identifier­:­type-identifier­
same-type-requirement → type-identifier­==­type­
So, when you want to state that your type parameter conforms to certain protocol you use :. BUT, when you want your type to be exactly of a certain type, you use ==. In my example, I wanted to take as a parameter a collection of Flyable. So, when trying to use : the compiler complained because Flyable doesn't conform to Flyable. Flyable IS Flyable, thus I must use == (or do something like what #Rahul Katariya answered)

How to achieve object merging through generics in swift?

Imagine I have a class Number:
class Number {
var val: Double?
}
and have two instances of that class, A and B.
Now imagine I want to merge Binto Athrough a statement like
merge(B, into: A)
Now of course I could write the function like this:
func merge(from: Number, into: Number){
into.val = from.val
}
But that isn't reusable at all. Is there a way I could write a generic merge class?
UPDATE: Although some of the answers offer good and viable solutions, none of them are "generic" enough (generic here is meant in a non-technical way).So looking at the answers, I got some inspiration, and here is the solution I am now considering: make Number a NSObject subclass and declare all the properties that can be merged as dynamic. For example:
class Number: NSObject {
//Put the required init and initWithCoder: here
dynamic var val: Double?
}
Then declaring a protocol that mergeable classes must respect
protocol Mergeable: class {
var mergeablePropertyKeys:[String] {get}
}
And then declaring a global function that performs a merge:
func merge<U: Mergeable, Mergeable where U.Type == V.Type>(from: U, into:V){
for property in U.mergeablePropertyKeys {
V.setValue(U.valueForKey(property), property)
}
}
And I know that this will not work because the arguments to merge are not necessarily NSObjects.
How can I make sure that the arguments to merge are both NSObjects?
Can avoid having to specify the names of all my mergeable values by simply obtaining a list of my object's dynamic values?
It sounds like what you want is a generic function that uses reflection to merge properties. Reflection is limited in Swift, but it is doable using the MirrorType. I have used this method before to build a generic json parser in swift - you could do something similar but instead of parsing a json dictionary to properties map your two object's properties.
An example of using reflection to do this in swift:
func merge<T>(itemToMerge:T) {
let mirrorSelf = Mirror(reflecting: self)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
This assumes the object defining the merge method is a subclass of NSObject (so it can take advantage of NSKeyValueCoding). You could also make this a static method that could merge any 2 objects of any NSObject type:
static func merge<T1: NSObject, T2: NSObject>(itemChanging:T1, itemToMerge:T2) {
let mirrorSelf = Mirror(reflecting: itemChanging)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
Im not sure what you are expecting but there is generic solution:
class Number<T> {
var val: T?
}
protocol Merge {
func merge(from: Self, into: Self)
}
extension Number: Merge {
func merge(from: Number, into: Number) {
into.val = from.val
}
}
Protocol
Lets define a HasValue protocol (available only for classes) like this
protocol HasValue: class {
typealias T
var val: T? { get set }
}
Merge
Now we can define a generic function
func merge<U: HasValue, V:HasValue where U.T == V.T>(from: U, into:V) {
into.val = from.val
}
The constraints in the function signature do guarantee that
Both params do conform to HasValue (therefore are classes)
val types for both params are equals
Scenario 1: params have the same type
class Number: HasValue {
var val: Double?
}
let one = Number()
one.val = 1
let two = Number()
two.val = 2
merge(one, into: two)
print(two.val) // Optional(1.0)
Scenario 2: params have different types but their values have the same type
I did not constrain the 2 params of Merge to having the same type, I am only checking that the val properties of the 2 params must have the same type.
So we could also merge different instances of different classes having val of the same type like
class Phone: HasValue {
var val: Int?
}
class Computer: HasValue {
var val: Int?
}
let iPhone = Phone()
iPhone.val = 10
let iMac = Computer()
iMac.val = 9
merge(iPhone, into: iMac)
print(iMac.val) // Optional(10)
Scenario 3: params have generic types
class Box<S>: HasValue {
var val: S?
}
let boxOfString = Box<String>()
boxOfString.val = "hello world"
let boxOfInt = Box<Int>()
boxOfInt.val = 12
merge(boxOfString, into: boxOfInt) // << compile error
let boxOfWords = Box<String>()
boxOfWords.val = "What a wonderful world"
merge(boxOfString, into: boxOfWords)
print(boxOfWords.val) // Optional("hello world")

Resources