I'm having trouble with understanding how to properly do unwrapping in Swift. Here is the situation:
I have these two classes:
class Alpha
{
var val: Int
init(x: Int)
{
val = x
}
func doSomething() -> Int
{
// does stuff and returns an Int value
return val
}
}
and a second class which is a subclass of the first
class Beta: Alpha
{
var str: [String] = []
override init(x: Int)
{
super.init(x)
}
func doSomething() -> String
{
// does stuff and returns a String
return str
}
}
So far so good, these two classes work as expected. But now I want to create a dictionary to store several of both these class objects. So I've created a class to manage the dictionary and add items to it.
class Gamma
{
var myDict: [String:AnyObject] = [:]
func addAlphaToDict(key: String, val: Int)
{
self.myDict[key] = Alpha(x: val).self
}
func addBetaToDict(key: String, val: Int)
{
self.myDict[key] = Beta(x: val).self
}
}
This also is working, but I think this could somehow be improved. The issue I'm having is now when I try to access values in the dictionary:
var foo = Gamma()
foo.addAlphaToDict(key: "blah", val: 45)
foo.addBetaToDict(key: "bluh", val: 23)
var result1 = (foo.myDict["blah"] as! Alpha).doSomething()
var result2 = (foo.myDict["bluh"] as! Beta).doSomething()
I find that the syntax here is very clunky and I feel like I'm doing it wrong, even though it works. I'm an experienced developer, but have only just started using Swift so I'm not really sure about certain things. Could someone with more Swift experience show me how to improve this code, or point me in the direction I should be going? Thanks!
You can use Alpha instead of AnyObject as your dictionary value in this case. Just downcast it to Beta when needed. Using AnyObject or Any as the dictionary key should be avoided as much as possible.
However, i have to say that this approach is bad. You need to have a clearer logic to decide when the key will be Beta other than just relaying on knowing which key you are passing to the dictionary.
In swift when you want a heterogeneous type with a finite number of possibilities, prefer an enum with associated values to Any or AnyObject. You can then use a switch to recover the type in an exhaustive and type safe way.
import UIKit
import PlaygroundSupport
enum DictionaryValues {
case integer(Int), string(String)
}
let dictionary: [String: DictionaryValues] = ["a": .integer(1), "b": .string("xyz")]
for (key, value) in dictionary {
switch value {
case .integer(let number):
print("Key \(key) is a number: \(number)")
case .string(let text):
print("Key \(key) is text: \(text)")
}
}
Related
I was reading through the wonderful blog post by Jon Sundell where he is trying to demonstrate how one can use custom raw values with Swift Enums.
I had a play around with his code and made up a bit of a contrived example to see how robust the Enums with custom raw types are.
I wondered if I can make a type, instance of which can be used as a raw value whilst being expressible by both String and Integer literals. When I implemented ExpressiblebyIntegerLiteral the String part did not work the way I expected and I'm not quite sure why.
Here is the code (the question follows the code):
import Foundation
struct Path: Equatable {
var string: String
var int: Int
}
enum Target: Path {
case content
case resources
case images = "resources/images"
}
extension Path: ExpressibleByIntegerLiteral, ExpressibleByStringLiteral {
init(stringLiteral: String) {
string = stringLiteral
int = 0
}
init(integerLiteral: Int) {
string = ""
int = integerLiteral
}
}
if let stringTarget = Target(rawValue: "content") {
print("stringTarget: \(stringTarget)")
}
if let intTarget = Target(rawValue: 1) {
print("intTarget: \(intTarget)")
}
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
If you remove the conformance to ExpressibleByIntegerLiteral protocol (and the appropriate block of code which initializes intTarget), the stringTarget automagically gets initialized as I expected: into Target.string case.
Could someone please explain to me what is going on here?
Solving your Question
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
They aren't nil. They are this: ""
This happens because both the .content and .resources cases are not explicitly defined by a String. And because of this, they both take the ExpressibleByIntegerLiteral route, and are hence defined as this ""
init(integerLiteral: Int) {
string = "" // see
int = integerLiteral
}
Solved for Int
Use this fancy method in place of IntValue(rawValue: 1):
func IntValue(_ this: Int) -> String? {
return Target(rawValue: 0) != nil ? String(describing: Target(rawValue: 0)!) : nil
}
Solved for String
First, conform your enum to CaseIterable, like so:
enum Target: Path, CaseIterable {
Next, use this fancy method in place of Target(rawValue: "content"):
func StringValue(_ this: String) -> String? {
return Target.allCases.contains { this == String(describing: $0) } ? this : nil
}
Truly solved for String
Now, I've removed a crucial bug where case foo = "bar" can be found both as 'foo' or 'bar'. If you don't want this, use this code instead:
func StringValue(_ this: String) -> String? {
var found: String? = nil
_ = Target.allCases.filter {
if let a = Target(rawValue: Path.init(string: this, int: 0)) {
found = String(describing: a)
return this == String(describing: a)
}
found = String(describing: $0)
return this == String(describing: $0)
}
return found
}
Custom Raw Values for Enums
Here's a quick tutorial:
I am wondering if enum can conform it's rawValue to the ClosedRange struct, just like it can conform to String.
enum Foo: ClosedRange<Int> {
case bar = 1...4
}
Obviously this is not an option, since 1...4 is not a literal
This seems to work:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
func overlaps(_ with: Foo) -> Bool { return self.rawValue.overlaps(with.rawValue) }
}
extension ClosedRange: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: String) {
let v = value.split(separator: ".")
switch Bound.self {
case is Int.Type: self = (Int(v[0])! as! Bound)...(Int(v[1])! as! Bound)
default: fatalError()
}
}
}
It allows you to do this:
print(Foo.foo.overlaps(Foo.bar))
You can add more types like Double or String using this technique
Side Note: My attempt allows for non-unique rawValues (SR-13212) which is a shame. But I'm not thinking that is fixable:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
case bar = "1...04" // Duplicate, but Swift allows it.
}
In the code below, when I try to let expectations = stuffToExpect.map({ $0.expectation }) the compiler says Value of tuple type '(key: _, value: HasExpectations)' has no member 'expectation'.
What is the correct way to use map in with a generic type?
import XCTest
import Foundation
protocol HasExpectations {
var expectation: XCTestExpectation { get }
}
public class A: HasExpectations {
var expectation: XCTestExpectation
init(expectation: XCTestExpectation) {
self.expectation = expectation
}
}
public class B: HasExpectations {
var expectation: XCTestExpectation
init(expectation: XCTestExpectation) {
self.expectation = expectation
}
}
func doit<T>(stuffToExpect: [T: HasExpectations]) {
let expectations = stuffToExpect.map({ $0.expectation })
}
</pre>
In your function
func doit<T>(stuffToExpect: [T: HasExpectations]) {
let expectations = stuffToExpect.map({ $0.expectation })
}
stuffToExpect is of type [T: HasExpectations] aka Dictionary<T: HasExpectations>. When you map over a dictionary it gives you a tuple of type (key: T, value: HasExpectations) back which is why you are seeing that error.
I think you instead wanted to constrain T instead and have stuffToExpect as an array, in which case the syntax is either of these (pick which you think looks best):
func doit<T: HasExpectations>(stuffToExpect: [T]) {
let expectations = stuffToExpect.map({ $0.expectation })
}
// or
func doit<T>(stuffToExpect: [T]) where T: HasExpectations {
let expectations = stuffToExpect.map({ $0.expectation })
}
You described Tas generic. That is correct. Now you want to say I am happy to accept any type T which conforms to protocol HasExpectations. That smeans <T: HasExpectation>.
So your function going to look like below
func doit<T: HasExpectations>(stuffToExpect: [T]) {
let expectations = stuffToExpect.map({ $0.expectation })
}
You got a compilation error because when you specify [T: HasExpections]. The compiler treats as a dictionary, which is not the case here.
Call mapValues over the expectation,
func doit<T>(stuffToExpect: [T: HasExpectations]) {
let expectations = stuffToExpect.mapValues { $0.expectation }
// here your expectations is a new dictionary of [Hashable: XCTestExpectation]
// also note that T is turned to Hashable since you cannot have stuffToExpect key with T, which is not actually Hashable.
// you can get all test expectations using values
let testExpectations = Array(expectations.values)
// here testExpectations is [XCTestExpectation]
}
My real problem was my function declaration:
func doit<T>(stuffToExpect: [T: HasExpectations]) {
What I really wanted to do was say that T conformed to HasExpectations:
func doit<T: HasExpectations>(stuffToExpect: [T: HasExpectations]) {
I watched some iOS programming tutorial and have a question on "AnyObject".
The bridging does not work.
I have the following code:
import Foundation
class CalculatorBrain
{
private var accumulator = 0.0
var internalProgram = [AnyObject]()
func setOperand (operand: Double) {
accumulator = operand
internalProgram.append(operand)
}
// ...
}
Same for string...
func performOperation (symbol: String) {
internalProgram.append(symbol)
}
I know about NSString and the reference type vs. struct thing, and that double and string are no reference types.
But anyway in the tutorial the bridging worked fine...
What could be the reason for my failure?
As you correctly say, Double and String are not reference types in Swift – they are structs. Therefore you cannot directly store them in an [AnyObject], you would first need to bridge them to Objective-C in order to do so.
Although bridging to Objective-C in this case is unnecessary – if you want an array of heterogenous types, including value types, then you can use an [Any]:
var internalProgram = [Any]()
However, from what I can tell, you don't want a array of anything (it's very rarely a good idea) – you just want an array that can contain a Double or a String.
You can describe this with an enum:
enum OperationArgument { // feel free to give me a better name
case operand(Double)
case symbol(String)
}
And now you can define an array of it:
var internalProgram = [OperationArgument]()
func setOperand (operand: Double) {
internalProgram.append(.operand(operand))
}
func performOperation (symbol: String) {
internalProgram.append(.symbol(symbol))
}
Now instead of conditional type-casting to get back the type of each element, you can just use a switch or an if case:
for element in internalProgram {
switch element {
case let .operand(operand):
print(operand)
case let .symbol(symbol):
print(symbol)
}
if case let .operand(operand) = element {
print(operand)
}
}
To use a Swift value type as an AnyObject, in Swift 3 you need to explicitly cast it to the old Objective-C object type.
So this
internalProgram.append(operand)
becomes this
internalProgram.append(operand as NSNumber)
And this
internalProgram.append(symbol)
becomes this
internalProgram.append(symbol as NSString)
The tutorial you are talking about has probably been written with Swift 2 where you just needed to import Foundation.
var internalProgram = [AnyObject]()
func setOperand (operand: Double) {
// you have cast operand as AnyObject !!
internalProgram.append(operand as AnyObject)
}
setOperand(operand: 10)
print(internalProgram, type(of: internalProgram)) // [10] Array<AnyObject>
I have a method which does exactly the same thing for two types of data in Swift.
To keep things simple (and without duplicating a method) I pass AnyObject as an argument to my method which can be either of these two types. How to I unwrap it with an || (OR) statement so I can proceed? Or maybe this done otherwise?
func myFunc(data:AnyObject) {
if let data = data as? TypeOne {
// This works fine. But I need it to look something like unwrapping below
}
if let data = data as? TypeOne || let data = data as? TypeTwo { // <-- I need something like this
// Do my stuff here, but this doesn't work
}
}
I'm sure this is trivial in Swift, I just can't figure out how to make it work.
You can't unify two different casts of the same thing. You have to keep them separate because they are two different casts to two different types which the compiler needs to treat in two different ways.
var x = "howdy" as AnyObject
// x = 1 as AnyObject
// so x could have an underlying String or Int
switch x {
case let x as String:
print(x)
case let x as Int:
print(x)
default: break
}
You can call the same method from within those two different cases, if you have a way of passing a String or an Int to it; but that's the best you can do.
func printAnything(what:Any) {
print(what)
}
switch x {
case let x as String:
printAnything(x)
case let x as Int:
printAnything(x)
default: break
}
Of course you can ask
if (x is String || x is Int) {
but the problem is that you are no closer to performing an actual cast. The casts will still have to be performed separately.
Building on Clashsoft's comment, I think a protocol is the way to go here. Rather than pass in AnyObject and unwrap, you can represent the needed functionality in a protocol to which both types conform.
This ought to make the code easier to maintain, since you're coding toward specific behaviors rather then specific classes.
I mocked up some code in a playground that shows how this would work.
Hopefully it will be of some help!
protocol ObjectBehavior {
var nickname: String { get set }
}
class TypeOne: ObjectBehavior {
var nickname = "Type One"
}
class TypeTwo: ObjectBehavior {
var nickname = "Type Two"
}
func myFunc(data: ObjectBehavior) -> String {
return data.nickname
}
let object1 = TypeOne()
let object2 = TypeTwo()
println(myFunc(object1))
println(myFunc(object2))
Find if that shared code is exactly the same for both types. If yes:
protocol TypeOneOrTypeTwo {}
extension TypeOneOrTypeTwo {
func thatSharedCode() {
print("Hello, I am instance of \(self.dynamicType).")
}
}
extension TypeOne: TypeOneOrTypeTwo {}
extension TypeTwo: TypeOneOrTypeTwo {}
If not:
protocol TypeOneOrTypeTwo {
func thatSharedMethod()
}
extension TypeOne: TypeOneOrTypeTwo {
func thatSharedMethod() {
// code here:
}
}
extension TypeTwo: TypeOneOrTypeTwo {
func thatSharedMethod() {
// code here:
}
}
And here you go:
func myFunc(data: AnyObject) {
if let data = data as? TypeOneOrTypeTwo {
data.thatSharedCode() // Or `thatSharedMethod()` if your implementation differs for types.
}
}
You mean like this?
enum IntOrString {
case int(value: Int)
case string(value: String)
}
func parseInt(_ str: String) -> IntOrString {
if let intValue = Int(str) {
return IntOrString.int(value: intValue)
}
return IntOrString.string(value: str)
}
switch parseInt("123") {
case .int(let value):
print("int value \(value)")
case .string(let value):
print("string value \(value)")
}
switch parseInt("abc") {
case .int(let value):
print("int value \(value)")
case .string(let value):
print("string value \(value)")
}
output:
int value 123
string value abc
I'm very new to Swift, but slowly learning by following the Stanford iTunes U course. I have a question about storing and calling functions in an array.
The code I have (below) seems to store the function properly, but when I try to call one of the functions I get this error: '(IntegerLiteralConvertible, IntegerLiteralConvertible) -> $T6' is not identical to (String, Op).
I found this answer that was helpful in getting to where I am, but now I'm stuck.
enum Op {
case binary((Double, Double) -> Double)
}
var ops = [String: Op]()
func addOperation(symbol: String, op: Op) {
ops[symbol] = op
}
addOperation("×", Op.binary(*))
addOperation("÷", Op.binary({ $1 / $0 }))
addOperation("+", Op.binary(+))
addOperation("−", Op.binary({ $1 - $0 }))
var newValue = ops["÷"](6, 3) // Produces the error
My understanding was that ops["÷"] should be the function I stored in the array earlier. Am I not calling it properly?
#Nate Cook answer is corret but why do you have to use enum in this case? Consider using typealias like following :
var ops = [String: Op]()
func addOperation(symbol: String, op:Op) {
ops[symbol] = op
}
addOperation("×", (*))
addOperation("÷", ({ $1 / $0 }))
addOperation("+", (+))
addOperation("−", ({ $1 - $0 }))
var newValue = ops["÷"]?(3,6)
// you have to put this outside of any class
public typealias Op = (Double, Double) -> Double
You have two problems there. First, subscripting a dictionary returns an optional value, so ops["÷"] is an Op? that needs to be unwrapped before you can use it.
Second, the associated value of an enum can't be accessed directly—you can only get the value when pattern matching in a switch statement. I'd suggest adding a computed property to Op that does the work for you:
enum Op {
case binary((Double, Double) -> Double)
var binaryCall: ((Double, Double) -> Double)? {
switch self {
case let .binary(operation):
return operation
}
}
}
Then you would call it like this instead:
var newValue = ops["÷"]?.binaryCall?(6, 3)
// Optional(0.5)
An alternate method of implementation would be to just build an dictionary of binary operations, like so (you still need to unwrap once, though):
typealias BinaryOp = (Double, Double) -> Double
var binaryOps: [String: BinaryOp] = [:]
binaryOps["×"] = { $0 * $1 }
binaryOps["÷"] = { $1 / $0 }
newValue = binaryOps["÷"]?(6, 3)