I'm extensively using KVC to build unified interface for the needs of an app. For instance, one of my functions gets an object, which undergoes several checks based solely on dictionary of string keys.
Thus I need a way to check if an object by the key is of collection type.
I expected to have been able to make some protocol check (like IEnumerable in C# to check if it can be enumerated), but it didn't work out:
if let refCollection = kvcEntity.value(forKey: refListLocalKey) as? AnySequence<CKEntity> { ... }
I tried AnyCollection, too.
I know I could iterate all main collection types, by simply typing:
if let a = b as? Set { ...} // (or: if a is Set {...})
if let a = b as? Array { ...}
if let a = b as? Dictionary { ...}
But this doesn't seem proper from inheritance/polymorphism point of view.
Collection
can no longer be used for type-checking, hence Ahmad F's solution would no longer compile.
I did some investigation. Some people advice to bridge to obj-c collections and use isKindOfClass
, others try to employ reflection (by using Mirror
). Neither is satisfactory.
There's a pretty straight-forward, a bit rough yet efficient way to accomplish the task via splitting object type if our concern is Array
, Dictionary
or Set
(list can be updated):
func isCollection<T>(_ object: T) -> Bool {
let collectionsTypes = ["Set", "Array", "Dictionary"]
let typeString = String(describing: type(of: object))
for type in collectionsTypes {
if typeString.contains(type) { return true }
}
return false
}
Usage:
var set : Set! = Set<String>()
var dictionary : [String:String]! = ["key" : "value"]
var array = ["a", "b"]
var int = 3
isCollection(int) // false
isCollection(set) // true
isCollection(array) // true
isCollection(dictionary) // true
Hard-code is the drawback but it does the job.
Try to use this function:
func isCollection<T>(object: T) -> Bool {
switch object {
case _ as Collection:
return true
default:
return false
}
}
Calling:
// COLLECTION TESTING //
let arrayOfInts = [1, 2, 3, 4, 5]
isCollection(object: arrayOfInts) // true
let setOfStrings:Set<String> = ["a", "b", "c"]
isCollection(object: setOfStrings) // true
// [String : String]
let dictionaryOfStrings = ["1": "one", "2": "two", "3": "three"]
isCollection(object: dictionaryOfStrings) // true
// NON-COLLECTION TESTING //
let int = 101
isCollection(object: int) // false
let string = "string" // false
let date = Date()
isCollection(object: date) // false
Hope this helped.
You could create another protocol
protocol CountableCollection {
var count: Int { get }
}
extension Array: CountableCollection where Element: Any {
}
extension Dictionary: CountableCollection where Key == String, Value == Any {
}
Add all the methods that you need from Collection
to the new protocol that has been created (I have added just count
getter for demonstration).
After this you could simply do
if someVar is CountableCollection {
print(someVar.count)
}
someVar would be true if it is an Array
or Dictionary
. You can also make it conform to Set
if required.