Swift closure as values in Dictionary - ios

I'm trying to use an Objective-C library which expects a NSDictionary as its return type. Within the NSDictionary, I can return values of any type, including blocks.
I cannot figure out if there is a way to write an analogous swift method that returns a Dictionary with a closure or a string as a possible value type.
I can't use AnyObject as the value type for the dictionary so this doesn't work:
Dictionary<String,AnyObject> = ["Key":{(value:AnyObject) -> String in return value.description]
I get a Does not conform to protocol error from the compiler regarding the closure and AnyObject.
Is there a higher level type or protocol that both closures and basic types adhere to that I can use as the value type in a Dictionary?

Your basic problem is that in Objective-C closures (aka blocks) are represented as NSObject (or more precisely are transparently converted to NSObjects) while in Swift there is no such mapping. This means that closures can not be directly stored in a Dictionary (short of using objective-c glue)
The closest I can come up with is something along the lines of wrapping the value in an enum:
enum DataType {
case AsString(String)
case AsClosure((AnyObject)->String)
}
var dict:Dictionary<String,DataType> = [
"string":DataType.AsString("value"),
"closure":DataType.AsClosure({(argument:AnyObject) -> String in
return "value"
}
)
]
Which is probably a better solution anyway, because this way you have an explicit typing associated with individual arguments instead of it being implicit using some sort of inflection.
Alternatively, you could only wrap the closure and use a dictionary of type Dictionary<String,Any>.

If you still need a workaround, here is one; usage looks like this:
var d : [String : AnyObject] = [:]
d["a"] = Blocks.voidBlockToId({ println("Doing something") })
d["b"] = "Some string"
d["c"] = Blocks.intBlockToId({ i in println("Called with integer: \(i)") })
Blocks.toIntBlock(d["c"])(1)
Blocks.toVoidBlock(d["a"])()
println(d["b"])
Output is:
Called with integer: 1
Doing something
Some string
The Blocks class is defined like this in Objective-C (with corresponding header and bridging header, I won't put those here):
typedef void(^VoidBlock)(void);
typedef void(^IntBlock)(int);
#implementation Blocks
+ (id) voidBlockToId: (VoidBlock) block { return block; }
+ (VoidBlock) toVoidBlock: (id) block { return (VoidBlock)block; }
+ (id) intBlockToId: (IntBlock) block { return block; }
+ (IntBlock) toIntBlock:(id)block { return (IntBlock)block; }
#end
You also need to add a new xyzBlockToId and toXyzBlock method for every new closure-type you want to use. It's pretty ugly, but it works.

There is another type, Any, that object, structs and primitives all conform to but functions do not. There is no general function type, but you can describe a function type as its arguments and return value like this:
Dictionary<String, (AnyObject) -> String>
Function Types

Could you use an NSMutableDictionary?
Alternatively, this seemed to work for me using your example:
1> import Foundation
2> var myDict: [String: (NSObject) -> String] = ["Key":{(value:NSObject) -> String in return value.description}]
myDict: [String : (NSObject) -> String] = {
[0] = {
key = "Key"
value =
}
}
3> myDict["Key"]!("Testing")
$R2: String = "Testing"
Hmm, maybe this Swift-Code doesn't really help, because you want to have heterogenous dictionaries.
It's also not possible to put closures into an NSDictionary, it seems (as a closure does not conform to AnyObject).

You could also roll your own higher type using an enum. You need the dictionary values to be either strings or functions which return strings, so define a type to represent that:
enum MyDictVal {
case ValString(String)
case ValFunc(AnyObject -> String)
}
Then, you can put it in a dictionary:
let d: Dictionary<String, MyDictVal> = [
"a": .ValString("a")
, "b": .ValFunc({ (value) in value.description })
]
Then you'll need to process the dictionary values using pattern matching:
switch d["b"] {
case .ValString(let s):
...
case .ValFunc(let f):
...
}

A more "generic" solution which should work with Any object, but shown with closures and function references. Drop it into a playground and try it out!
// Wrapper for sticking non-objects in NSDictionary instances
class ObjectWrapper {
let value: T
init(_ value: T) {
self.value = value
}
}
// convenience to downcast `as! ObjectWrapper` and return its value
func getValueFromObjectWrapper(a: AnyObject) -> T {
return (a as! ObjectWrapper).value
}
func wrappedObjectsInDictionary() -> NSDictionary {
var dict = NSMutableDictionary()
let appendToFoo: (String) -> String = NSString.stringByAppendingString("foo")
let firstChar: (String) -> Character = { $0[$0.startIndex] }
dict.setObject(ObjectWrapper(firstChar), forKey: "stringToChar")
dict.setObject(ObjectWrapper(appendToFoo), forKey: "stringTransformer")
return dict.copy() as! NSDictionary
}
let dict = wrappedObjectsInDictionary()
let appendToFoo: (String) -> String = getValueFromObjectWrapper(dict["stringTransformer"]!)
let strToChar: (String) -> Character = getValueFromObjectWrapper(dict["stringToChar"]!)
appendToFoo("bar") // "foobar"
strToChar("bar") // "b"

Related

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.

Array.map() produces '[T]', not the expected contextual result type '[String: Any?]'

I'm writing an extension to bridge the dictionary values between FirebaseDatabase and Eureka.
private extension Dictionary {
func firebaseFriendlyDictionary() -> [String: Any?] {
return self.map({ (key: String, value: Any?) -> (String, Any?) in
if value is NSDate {
return (key, (value as! NSDate).timeIntervalSince1970)
}
return (key, value)
})
}
}
But I get thrown this error when I try to build:
map produces '[T]', not the expected contextual result type '[String: Any?]'
Your problem lies with the fact, that map always returns an Array, even when applied on a Dictionary. Your error message basically means, that you declared your method as returning a Dicitonary, but the statement inside returns an Array ([T] - means an Array with objects of some type T). In your case, the array returned by map will contain tuples (more about them here). In this case it looks like a key value pair, but its not equivalent to a key-value pair inside a Dictionary. Basically, tuples enable you to return more than one value/object from method. You can think of them as of anonymous structures.
In my opinion, there is no need to use a map to accomplish what you need - the solution provided by Mr. Xcoder is the way to go.
If you really want to use something functional like map, the method you actually want is reduce.
I'll demonstrate. To make things as clear as possible, I think it will help if we separate out the transformation to which your values are being subjected into a function of its own:
func dateToSeconds(_ thing:Any?) -> Any? {
guard let date = thing as? Date else {return thing}
return date.timeIntervalSince1970
}
Okay, so here's our test dictionary:
let d1 : [String:Any?] = ["1":Date(), "2":"two", "3":15, "4":true]
Now we're ready to apply reduce. It passes two parameters into its function. The first is the "accumulator" where we keep building up the ultimate result, which in this case is another dictionary. The second is an element of the original dictionary, represented as a tuple of key-value pairs called key and value:
let d2 = d1.reduce([String:Any?]()) { (dict, tuple) in
var dict = dict
dict[tuple.key] = dateToSeconds(tuple.value)
return dict
}
And when we examine d2, we see that we have got the right answer:
d2 // ["3": {some 15}, "2": {some "two"}, "1": {some 1486228695.557882}, "4": {some true}]
I couldn't figure out how to fix that error, nor have I succeeded achieving the desired result with an extension or a map(), but I have an alternative solution to the problem, using a function:
Declaring Dictionary:
var date = NSDate()
var swiftDict:[String : Any?] = ["1": date, "2": "two", "3": 15, "4": true]
Function:
func firebaseFriendlyDictionary(_ dict: [String : Any?]) -> [String : Any?]{
var Dict = dict
for (key, value) in dict
{
if (value is NSDate){
Dict[key] = (value as! NSDate).timeIntervalSince1970
}
}
return Dict
}
Usage:
swiftDict = firebaseFriendlyDictionary(swiftDict)
Testing:
Assuming that we have the date 2017-02-04 16:42:46 +0000 the output is 1486226566.2349629, which is correct.
Why not mapping the Dictionary? As Losiowaty pointed in his excellent answer, map always returns an Array, in this case an Array of Tuples([T]). In my opinion a map function is not needed in this context, plus it requires more code to accomplish.
Hope this helps!

Putting two generic Arrays into one Swift Dictionary with Generics

I'm trying to put two different generic types into a collection. In this example there are two arrays, one containing Ints and the other Strings.
let intArray = Array<Int>()
let stringArray = Array<String>()
let dict = [1:intArray, "one": stringArray]
The error reads Type of expression is ambiguous without more context.
So I tried specifying the Dictionary's type
let dict: [Hashable: Any] = [1:intArray, "one": stringArray]
This leads to two errors.
Using 'Hashable' as a concrete type conforming to protocol 'Hashable' is not supported.
Protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
Adding import Foundation and using NSDictionary as the type works fine.
let dict: NSDictionary = [1:intArray, "one": stringArray]
But this should be possible in pure Swift too without using Foundation. What is the type the Dictionary has to have?
edit: This apparently has more to do with the type of the keys. They have to be of the same type, not just conform to Hashable.
let dict: [Int:Any] = [1:intArray, 2: stringArray]
This works. But is it possible to make the type of the value more precise? [Int:Array<Any>] does not work.
Elaborating on the answer from #RobNapier, here is a similar approach that uses enum for both, keys and values of the dictionary:
enum Key: Equatable, Hashable {
case IntKey(Int)
case StringKey(String)
var hashValue: Int {
switch self {
case .IntKey(let value) : return 0.hashValue ^ value.hashValue
case .StringKey(let value) : return 1.hashValue ^ value.hashValue
}
}
init(_ value: Int) { self = .IntKey(value) }
init(_ value: String) { self = .StringKey(value) }
}
func == (lhs: Key, rhs: Key) -> Bool {
switch (lhs, rhs) {
case (.IntKey(let lhsValue), .IntKey(let rhsValue)) : return lhsValue == rhsValue
case (.StringKey(let lhsValue), .StringKey(let rhsValue)) : return lhsValue == rhsValue
default: return false
}
}
enum Value {
case IntValue(Int)
case StringValue(String)
init(_ value: Int) { self = .IntValue(value) }
init(_ value: String) { self = .StringValue(value) }
}
var dict = [Key: Value]()
dict[Key(1)] = Value("One")
dict[Key(2)] = Value(2)
dict[Key("Three")] = Value("Three")
dict[Key("Four")] = Value(4)
What is the type the Dictionary has to have?
You may try:
let dict: [NSObject: Any] = [1: intArray, "one": stringArray]
The statement let dict: [Hashable: Any] = ... does not compile, because the type of the key of a Dictionary must be a concrete type conforming to Hashable, e.g. Int, String, etc. Hashable is not a concrete type.
The above suggestion works, because 1. NSObject is a concrete type (where you can instantiate objects from), 2. NSObject is a Hashable, and 3. because instances of subclasses of NSObjects will work here as well, and 4. the compiler can initialise NSObject subclasses from string and number literals.
If you don't like NSObject as the type of the key, you may create your own class or struct.
Note that your first attempt let dict = [1:intArray, "one": stringArray] works if you include Foundation; yielding an NSDictionary (so no need to explicitly state this type).
The reason why we can have these kinds of, apparently, generic dictionaries when using Foundation is the implicit type conversion performed (behind the hood) by the compiler when bridging Swift native types to Foundation.
let intArray : [Int] = [10, 20, 30]
let stringArray : [String] = ["foo", "baz", "bar"]
let dict = [1:intArray, "onx": stringArray]
print(dict.dynamicType)
for e in dict {
print(e.dynamicType, e.key.dynamicType, e.value.dynamicType)
}
/* __NSDictionaryI
(AnyObject, AnyObject) __NSCFString _SwiftDeferredNSArray
(AnyObject, AnyObject) __NSCFNumber _SwiftDeferredNSArray */
The keys as well as values in dict above are wrapped in type AnyObject; which can hold only reference (class) type objects; the compiler implicitly performs conversion of value types Int/String to Foundation reference types __NSCFNumber/__NSCFString. This is still an NSDictionary though; e.g. AnyObject itself does not conform to Hashable, so it can't be used as a key in a native Swift dictionary.
If you wish to create Swift-native "generic-key" dictionary, I'd suggest you create a wrapper (say a structure) that conforms to Hashable and that wraps over the underlying (various) key type(s). See e.g. (the somewhat outdated) thread
How to create Dictionary that can hold anything in Key? or all the possible type it capable to hold

Swift Array - use "Contains" of type AnyObject

I want to use function contains on Array of type AnyObject
import UIKit
var resultArray: Array<AnyObject> = Array()
resultArray.append(50)
resultArray.append(false)
resultArray.append("Test string")
let found = contains(resultArray, 50)
I get the error:
Type 'AnyObject -> L' does not conform to protocol 'IntegerLiteralConvertible'
I agree with the comments and other answer; AnyObject is not good practice, but if you really want to use AnyObject, you can treat your array of AnyObjects as an NSArray object and then use the function containsObject():
if (resultArray as NSArray).containsObject(AnyObjectOfAnyType) {
// Do something
}
You should probably be using Any in this example since you’re holding non-class types – otherwise you’ll be doing implicit conversion to NSThings.
But here’s a non-ObjC-interop way to do it:
let found = contains(resultArray) { ($0 as? Int) == 50 }
You can use the is keyword to differentiate between types, then use the piece of code by Airspeed Velocity above to do the search... Here's an example that you can modify and add extra types on if need be:
if resultArray[0] is String {
found = contains(resultArray) {
($0 as? String) == "hello"
}
} else if resultArray[0] is Int {
found = contains(resultArray) {
($0 as? Int) == 50
}
}
NOTE: You said your array will contain only 1 type at a time - this is important.

Syntax explanation: square brackets in Swift

I'm studying Swift and got confusing with following syntax:
var treasures: [Treasure] = []
Treasure is custom class, declared as follow:
class Treasure: NSObject { }
In Objective-C square brackets mean method, but what do they mean in Swift?
Ok, this is the meaning of
var treasures: [Treasure] = []
var: you are declaring a variable
treasures: the name of your variable
[Treasure]: the type of your variable, in this case the type is Array of Treasure, the compiler will allow you to insert only object of type Treasure in your Array
[]: the actual object (Array) referenced by your variable, in this case an empty Array.
E.g. if you want the Array to hold 2 elements you can write
var treasures: [Treasure] = [Treasure(), Treasure()]
Hope this helps.
Update:
My example can also be written this way
var treasures = [Treasure(), Treasure()]
Infact thanks to the Type Inference the compiler can deduce the type of the variable treasures looking at the type of the assigned value.
[Treasure] is just a syntax sugar for Array<Treasure>.
The same way [String:Treasure] is just a syntax sugar for Dictionary<String,Treasure>.
[] is just an empty array of the type you defined. The same way [:] is an empty dictionary.
When it comes to Swift and square brackets, the rules are simple. They are used only in two situations:
1) working with Array and Dictionary types:
let vectors : [[Int]] = [[1,2,3],[4,5,6]]
let birthBook : [Int:[String]] = [1987:["John","William"], 1990: ["Mary"]]
2) for subscripting objects that support subscripting:
class RouteMapper {
private var routeMap : [String:String] = [:]
subscript(endpoint: String) -> String {
get {
if let route = routeMap[endpoint] {
return route
}
return "/"
}
set(newValue) {
routeMap[endpoint] = newValue
}
}
}
let routeMapper = RouteMapper()
routeMapper["users"] = "/v1/confirmed/users"
let url = routeMapper["admins"]
Since [ and ] are not allowed in custom operators, these are the only usages for now.

Resources