Storing items of generic type - swift

So I'm writing an app that uses the Argo library to parse our JSON. Works pretty well. However, I'm trying to find a way to cache the parsed types we get back. They're all implemented as structs. I tried something like this:
struct CachedItem <T: Decodable where T == T.DecodedType> {
let value: T
let cachedTime: NSTimeInterval
init(value: T) {
self.value = value
cachedTime = NSDate().timeIntervalSince1970
}
func isExpired() -> Bool {
let currentTime = NSDate().timeIntervalSince1970
return ((currentTime - cachedTime) > 20.minutes)
}
}
However, attempting to create a cache like this:
var cache: [NSURL : CachedItem]
fails with the error: "reference to generic type 'CachedItem' requires arguments in <...>", which I understand to mean I need to do something like this:
var cache: [NSURL : CachedItem<Item>]
Is there any way I can get what I want here? Or any other suggestions for caching generic types that aren't Objective-C based classes.
Edit:
For posterity, here's the Cache and CacheItem types I came up with after Rob's answer.
struct Cache {
private var cache: [String : CachedValue] = [:]
private let queue = dispatch_queue_create("Cache Queue", DISPATCH_QUEUE_SERIAL)
mutating func setValue(value: Any?, forType type: String) {
dispatch_sync(queue) {
guard let value = value else {
return
}
self.cache[type] = CachedValue(value: value)
}
}
func valueForType<T>(type: String) -> T? {
var result: T?
dispatch_sync(queue) {
guard let cachedValue = self.cache[type] where !cachedValue.isExpired() else {
result = .None
return
}
result = cachedValue.value as? T
}
return result
}
}
struct CachedValue {
let value: Any
private let cachedTime: NSTimeInterval
init(value: Any) {
self.value = value
cachedTime = NSDate().timeIntervalSince1970
}
func isExpired() -> Bool {
let currentTime = NSDate().timeIntervalSince1970
return ((currentTime - cachedTime) > 1.minutes)
}
}

Basically, all you can say about the types in this cache are that they're Decodable, and that really isn't telling you anything useful since they're already decoded. That leaves AnyObject. If this really is a "cache of anything", then [NSURL: AnyObject] or even [NSURL: Any] is appropriate. This puts a lot of ugliness on the consuming side to figure out its type, but that's basically the identical ugliness that you had to have used to parse the JSON (which fundamentally works with AnyObject).
Seldom do I suggest AnyObject, but in this case, it's probably appropriate if you really want one big cache (rather than a cache for each type, which would be preferable if possible).

Related

A way to compare generics in Swift only if they conform to Equatable

Wondering if there's a good way to do this or not:
I have a #propertyWrapper named "Enhanced" that I use. I use the wrappedValue.set to do some actions, and I would also like to do some further actions if the property is Equatable.
Currently, the code looks like this:
#propertyWrapper
class Enhanced<T: Equatable>: Codable
{
private var value: T
var projectedValue: Enhanced<T> { self }
var wrappedValue: T
{
get { value }
set { set(newValue, notify: nil) }
}
func set(_ proposedValue: T, notify: Bool?)
{
let oldValue = value
let newValue = proposedValue
let changed = newValue != oldValue
if changed { /* more magic here */ }
value = newValue
}
}
Now I would like to remove the Equatable conformance over the generic T, but still be able to compare the old and new values IF the generic T conforms to Equatable.
I've tried a handful of techniques, all of which dead end somewhere. My latest was this:
let changed: Bool
switch T.self
{
case let equatableType as any Equatable.Type:
if
let oldEquatableValue = oldValue as? any Equatable,
let newEquatableValue = newValue as? any Equatable
{
changed = newEquatableValue != oldEquatableValue
}
default:
changed = true
}
...but the error is an understandable Binary operator '!=' cannot be applied to two 'any Equatable' operands.
I tried different patterns to cast the generic type T into an Equatable and silently fail if the generic does not conform, but even if they do, the resulting "cast" types I get back aren't equatable themselves.
Any revelations to the proper pattern would be great!
After some deep web-sleuthing, I came across a snippet of code that does the magic I need:
private extension Equatable
{
func isEqualTo(_ rhs: Any) -> Bool
{
if let castRHS = rhs as? Self
{
return self == castRHS
}
else
{
return false
}
}
}
(HT to neonichu/equalizer.swift on GitHub)
With this bit of pseudo type-erasure, I can make this work:
let changed: Bool
if let oldEquatableValue = oldValue as? any Equatable,
let newEquatableValue = newValue as? any Equatable
{
changed = oldEquatableValue.isEqualTo(newEquatableValue) == false
}
else
{
changed = true
}
By using an extension on Equatable that does further casting of the values, this allows for these two values to be compared (and fail if they are not the same).
Hope this helps someone else!

Blank constant when trying to get list of classes that have adopted a Protocol

I am trying to get a list of classes that have adopted a certain Protocol Migration: Preparation, and then to append those classes into an array. Here is the function in question:
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
var migrationsList = [Preparation.Type]()
var count = UInt32(0)
let classList = objc_copyClassList(&count)!
for i in 0..<Int(count) {
let classInfo = ClassInfo(classList[i])!
if let cls = classInfo.classObject as? Migration.Type {
migrationsList.append(cls)
print(cls.description)
}
}
return migrationsList
}
}
In principle all that should work, but when debugging I note that the classInfo variable is referring to each class in the iteration, but when assigning and casting in the if let as line, the constant cls is always blank - neither a value/class nor nil, just completely blank.
Any idea what I got wrong with that code?
I am also open to suggestions for any better way to get a list of all classes that have adopted a particular protocol...
EDIT: I forgot to provide the code for ClassInfo
import Foundation
struct ClassInfo: CustomStringConvertible, Equatable {
let classObject: AnyClass
let className: String
init?(_ classObject: AnyClass?) {
guard classObject != nil else { return nil }
self.classObject = classObject!
let cName = class_getName(classObject)!
self.className = String(cString: cName)
}
var superclassInfo: ClassInfo? {
let superclassObject: AnyClass? = class_getSuperclass(self.classObject)
return ClassInfo(superclassObject)
}
var description: String {
return self.className
}
static func ==(lhs: ClassInfo, rhs: ClassInfo) -> Bool {
return lhs.className == rhs.className
}
}
I can't explain why cls is always blank, like I said in my comment it's something I run into every time I'm dealing with meta types. As for making the code work as intended, I found this q&a and updated it with Swift 3 to get this code which should cover your situation. It's important to stress that this will only work if you correctly expose Swift to the Objective-C runtime.
Drop this code anywhere and call print(Migrations.getMigrations()) from a convenient entry point.
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
return getClassesImplementingProtocol(p: Preparation.self) as! [Preparation.Type]
}
static func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
let classes = objc_getClassList()
var ret = [AnyClass]()
for cls in classes {
if class_conformsToProtocol(cls, p) {
ret.append(cls)
}
}
return ret
}
static func objc_getClassList() -> [AnyClass] {
let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyClass]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)] {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
class Migration: Preparation {
}
#objc
protocol Preparation {
}

Different process between Struct and Class in mutating asynchronously in Swift3

In struct type, mutating self in async process makes error as below.
closure cannot implicitly captured a mutating self
If I change the struct to class type, the error disappear.
What is difference between struct and class when mutate self in asynchronously?
struct Media {
static let loadedDataNoti = "loadedDataNotification"
let imagePath: String
let originalPath: String
let description: String
var imageData: Data?
let tag: String
var likeCount: Int?
var commentCount: Int?
var username: String?
var delegate: MediaDelegate?
public init(imagePath: String, originalPath: String, description: String, tag: String, imageData: Data? = nil) {
self.imagePath = imagePath
self.originalPath = originalPath
self.description = description
self.tag = tag
if imageData != nil {
self.imageData = imageData
} else {
loadImageData()
}
}
mutating func loadImageData() {
if let url = URL(string: imagePath) {
Data.getDataFromUrl(url: url, completion: { (data, response, error) in
if (error != nil) {
print(error.debugDescription)
return
}
if data != nil {
self.imageData = data! // Error: closure cannot implicitly captured a mutating self
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Media.loadedDataNoti), object: data)
}
})
}
}
A struct is a value type. How does struct mutating work? It works by making a completely new struct and substituting it for the original. Even in a simple case like this:
struct S {
var name = "matt"
}
var s = S()
s.name = "me"
... you are actually replacing one S instance by another — that is exactly why s must be declared as var in order to do this.
Thus, when you capture a struct's self into an asynchronously executed closure and ask to mutate it, you are threatening to appear at some future time and suddenly rip away the existing struct and replace it by another one in the middle of executing this very code. That is an incoherent concept and the compiler rightly stops you. It is incoherent especially because how do you even know that this same self will even exist at that time? An intervening mutation may have destroyed and replaced it.
Thus, this is legal:
struct S {
var name = "matt"
mutating func change() {self.name = "me"}
}
But this is not:
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
struct S {
var name = "matt"
mutating func change() {delay(1) {self.name = "me"}} // error
}
When you mutate an instance of a value type -- such as a struct -- you're conceptually replacing it with a new instance of the same type, i.e. doing this:
myMedia.mutatingFuncToLoadImageData()
...can be thought of as doing something like this:
myMedia = Media(withLoadedData: theDownloadedData)
...except you don't see the assignment in code.
You're effectively replacing the instance that you call a mutating function on. In this case myMedia. As you may realize, the mutation has to have finished at the end of the mutating function for this to work, or your instance would keep changing after calling the mutating function.
You're handing off a reference to self to an asynchronous function that will try to mutate your instance after your mutating function has ended.
You could compile your code by doing something like
var myself = self // making a copy of self
let closure = {
myself.myThing = "thing"
}
but that would only change the value of the variable myself, and not affect anything outside of your function.

Swift cast dictionary value as type

I am looking for best way how can I make this code to one line:
if (dictionary["Amoumt"] is Double) {
amount = dictionary["Amount"] as Double
} else {
amount = NSString(string: dictionary["Amount"] as String).doubleValue
}
I have Dictionary<String, AnyObject> and I would like to parse values from it. I am using code like above but it's too many lines. I would like to make it to one line. Something like:
dictionary["Amount"].parseDouble()
There is no problem to create extension with this method:
func parseDouble() -> Double {
if (self is Double) {
return self as Double
} else {
return NSString(string:(self as String)).doubleValue
}
}
But which type should I extend? Next could you help me with generic method? So I could call something like this:
dictionary["Amount"].parse(Double)
And is this good way how to do this or should I do it another way?
You can use AnyObject as is. Try:
var dictionary:[String:AnyObject] = [
"foo": 4.21, // NSNumber
"bar": "42.5", // NSString
"baz": [1,2,3], // NSArray
]
let foo = dictionary["foo"]?.doubleValue ?? 0 // -> 4.21
let bar = dictionary["bar"]?.doubleValue ?? 0 // -> 42.5
let baz = dictionary["baz"]?.doubleValue ?? 0 // -> 0.0
This works because both NSNumber and NSString have .doubleValue property.
On the other hand, NSArray does not have that property, in this case it returns nil.
As described in the document:
You can also call any Objective-C method and access any property without casting to a more specific class type. This includes Objective-C compatible methods marked with the #objc attribute.
Maybe you are looking for this kind of extenison:
extension Dictionary{
func parseDouble(key:Key) -> Double {
let result = self[key]
if (result is Double) {
return result as Double
} else {
return NSString(string:(result as String)).doubleValue
}
}
}
And getting value for key by using this:
var doubleValue = dictionary.parseDouble("amount")
And a generic function
extension Dictionary{
func parse<T>(key:Key) -> T? {
let result = self[key]
if (result is T) {
return result as? T
}
return nil
}
}
var doubleValue:Double? = myDistionary.parse("someKeyForDouble")
var stringValue:String? = myDistionary.parse("someKeyForString")
The easiest way to condense this is to use the nil coalescing operator and an optional double. This attempts to cast the value as a double. If it succeeds, it unwraps it, if it fails, it defaults to the second value, which comes from converting `dict[amount]' to a string and taking its double value.
var amount: Double? = nil
let dict: [String: AnyObject] = ["amount" : "1.2"]
amount = (dict["amount"] as? Double) ?? NSString(string: dict["amount"] as! String).doubleValue
And to make your function:
func getDouble(obj: AnyObject) -> Double {
return (obj as? Double) ?? NSString(string: obj as! String).doubleValue
}
and you call it using getDouble(dict["amount"]). An extension for something like this is probably overkill in my opinion.
If you wanted an extension, the best place to put it in theory would be AnyObject – because it is an AnyObject that you want to convert.
But AnyObject is actually a protocol, so you can't extend it.
I wouldn't recommend putting it as an extension to Dictionary – the coercing of a type into a double is not really anything to do with dictionaries.
So the best approach is to do it as a free function not as an extension:
func getDouble(obj: AnyObject?) -> Double {
return (obj as? Double) ?? (obj as? NSString)?.doubleValue ?? 0
}
asDouble(dictionary["Amount"])
Note, this function is safe if you pass it a function that is neither a double nor a string. Other solutions using as or ! instead of as? will crash at runtime if you ever pass something else in.
You could argue it should return an optional, with nil if the value was not convertible to a double. This is what String.toInt() does. Unfortunately, the limitation of NSString.doubleValue is that it doesn't do this – it defaults to zero instead – so you can't combine these two approaches.

Casting AnyObject to T

Code:
class User
{
class var BoolProperty: Bool
{
get {
var anyObject: AnyObject? = getValue("BoolProperty")
if let value = anyObject as? Bool {
return value
}
else {
return false
}
}
set(value) {
setValue("BoolProperty", value: value)
}
}
private class func getValue(key: String) -> AnyObject?
{
var store = NSUserDefaults.standardUserDefaults();
return store.objectForKey(key) as AnyObject?
}
}
passes the test:
class UserTests: XCTestCase
{
func testFields()
{
User.BoolProperty = true
var result = User.BoolProperty
XCTAssertEqual(true, result)
}
}
but the following code doesn't pass the same test, which uses T instead of Bool for casting:
class User
{
class var BoolProperty: Bool
{
get {
return get("BoolProperty", defaultValue: false)
}
set(value) {
setValue("BoolProperty", value: value)
}
}
private class func getValue(key: String) -> AnyObject?
{
var store = NSUserDefaults.standardUserDefaults();
return store.objectForKey(key) as AnyObject?
}
private class func get<T>(key: String, defaultValue: T) -> T
{
var anyObject: AnyObject? = getValue(key)
if let value = anyObject as? T {
return value
}
else {
return defaultValue
}
}
}
it seems, that for some reason if let value = anyObject as? T always returns false when casting to T.
In C# this is a classic example of working with untyped collections and I was wondering what's the right approach to achieve the same in Swift.
The problem is that an NSNumber is not a Bool. It looks like a Bool, and it can be converted to a Bool, but it's really a different type. Your anyObject is really an NSNumber, and asking for it to be as? T where T is Bool is too far. That's still likely a compiler bug, because it should be the same as a generic as without, but it's where the problem is happening.
You need to specialize the generic function to NSNumber:
get {
return Bool(get("BoolProperty", defaultValue: NSNumber(bool: false)))
}
It's still probably worth opening a radar. It shouldn't behave differently with and without the generic.
That said, part of the trouble is the use of AnyObject. A Bool is not an AnyObject. It's an Any. When you try to assign it to an AnyObject, it gets converted into an NSNumber. I don't know of any documentation of this; I've worked it out empirically in playgrounds. Compare the results of let a:AnyObject = false and let a:Any = false.
In theory, this should fix it all:
var anyObject: Any? = getValue(key)
But it currently crashes the compiler.