How to call async methods in an Array map or forEach? - swift

I'm making my first attempts with Swift's async/await features and Task.
My first goal is working with NSItemProvider. I have a custom class that extends NSObject and conforms to NSSecureCoding. I have working code that converts my class to and from NSItemProvider.
What I'm now trying to do is convert an array of NSItemProvider objects back into an array of MyClass objects. Each conversion requires a call to an async function.
I'm able to do this using a plain for in loop. The trouble begins when I try to use compactMap or forEach to create the new array from the array of NSItemProvider.
Here is some code that can be copied into a Swift Playground or project that demonstrates the issue.
import Foundation
class MyClass: NSObject, NSSecureCoding {
var name: String
override var description: String {
return name
}
init(name: String) {
self.name = name
}
static var supportsSecureCoding = true
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
}
required init?(coder: NSCoder) {
if let name = coder.decodeObject(of: NSString.self, forKey: "name") as? String {
self.name = name
} else {
return nil
}
}
}
extension MyClass {
static let identifier = "mySpecialId"
func asItemProvider() -> NSItemProvider {
return NSItemProvider(item: self, typeIdentifier: Self.identifier)
}
static func create(withItemProvider itemProvider: NSItemProvider) async throws -> Self? {
let res = try await itemProvider.loadItem(forTypeIdentifier: identifier)
return res as? Self
}
}
func example() {
Task {
let objs = [ MyClass(name: "A"), MyClass(name: "B"), MyClass(name: "C"), MyClass(name: "D") ]
let providers: [NSItemProvider] = objs.map { $0.asItemProvider() }
do {
// This basic `for in` loop works
var newObjs = [MyClass]()
for provider in providers {
if let obj = try await MyClass.create(withItemProvider: provider) {
newObjs.append(obj)
}
}
print("newObjs: \(newObjs)")
// Attempt to use `forEach`
// Error: Cannot pass function of type '(NSItemProvider) async -> MyClass?' to parameter expecting synchronous function type
/*
providers.forEach { provider in
if let obj = try await MyClass.create(withItemProvider: provider) {
newObjs.append(obj)
}
}
*/
// Attempt to use `compactMap`
// Error: Cannot pass function of type '(NSItemProvider) async -> MyClass?' to parameter expecting synchronous function type
//let newObjs2 = providers.compactMap { await MyClass.create(withItemProvider: $0) }
} catch {
}
}
}
I was looking at another question but the solutions are far more complicated than just using a simple for in loop.
Is there a simpler way to use compactMap, for example, to directly convert the array of NSItemProvider into an array of MyClass?

Related

Swift - Return type of struct parameters without default values

I'm very new in Swift so i might be missing some basics.
I have struct:
struct MyStruct {
var a: Int
var b: String
var c: Bool
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
}
and function, that should iterate through given struct properties and return their types in json:
func structProps(){
let elm = MyStruct()
let mirror = Mirror(reflecting: elm)
var exampleDict: [String: String] = [:]
for child in mirror.children {
exampleDict[child.label!] = String(describing:type(of: child.value)) as String
}
if let theJSONData = try? JSONSerialization.data(
withJSONObject: exampleDict,
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
}
}
it kinda return what i need:
JSON string = {"a":"Int","b":"String","c":"Bool"}
Because i'm having a lot of structs and i want to export json from all of them, i'm wondering if there is a way to have generic initializer. Without passing default values.
It means without
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
If I understand correctly , you can create a base protocol and add as an extension to your structures like
protocol BaseFunction {
func getElements() -> [String : String]
func getDict() -> String
}
extension BaseFunction {
func getElements() -> [String : String] {
let mirror = Mirror(reflecting: self)
let propertiesRemoveNil = mirror.children.filter({!(($0.value as AnyObject) is NSNull)})
let properties = propertiesRemoveNil.compactMap({$0.label})
var types = [String]()
_ = mirror.children.forEach({
types.append(String(describing:type(of: $0.value)))
})
return Dictionary(uniqueKeysWithValues: zip(properties, types))
}
func getDict() -> String{
if let theJSONData = try? JSONSerialization.data(
withJSONObject: getElements(),
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
return theJSONText ?? ""
}
return ""
}
}
And usage :
func structProps(){
let elm = MyStruct()
print(elm.getDict())
}
OUTPUT :
{"a":"Int","b":"String","c":"Bool"}
I was going to write a comment about using a protocol but I thought it would be easier to understand as an answer with some code.
To make the usage more generic so you don't need specific code for each type of struct you should use a protocol. Instead of having an init that might clash with already existing init in the struct I prefer a static method that returns a new object, also known as a factory method
protocol PropertyExtract {
static func createEmpty() -> PropertyExtract
}
Then we can make use of the default init for the struct or any supplied to create an object with some initial values by letting the struct conform to the protocol in an extension
extension MyStruct: PropertyExtract {
static func createEmpty() -> PropertyExtract {
MyStruct(a: 0, b: "", c: false)
}
}
And instead of hardcoding or passing a specific type of object to the encoding function we pass the type of it
func structProps(for structType: PropertyExtract.Type)
and use the protocol method to get an instance of the type
let object = structType.createEmpty()
The whole function (with some additional changes)
func structProps(for structType: PropertyExtract.Type) -> String? {
let object = structType.createEmpty()
let mirror = Mirror(reflecting: object)
let exampleDict = mirror.children.reduce(into: [String:String]()) {
guard let label = $1.label else { return }
$0[label] = String(describing:type(of: $1.value))
}
if let data = try? JSONEncoder().encode(exampleDict) {
return String(data: data, encoding: .utf8)
}
return nil
}
And this is then called with the type of the struct
let jsonString = structProps(for: MyStruct.self)
According to Creating a Swift Runtime Library, there is a way to access meta data types without initializer.
Solution for what i was asking for is possible with Runtime library.
Mirror(reflecting:) expects an instance of a type and not a type itself, ref.
One idea is to have a generic function that works with all types conforming to a protocol that provides an empty init. Something like:
protocol EmptyInitializable {
init()
}
struct StructOne {
let a: Bool
let b: String
}
struct StructTwo {
let c: Int
let d: Float
}
extension StructOne: EmptyInitializable {
init() {
a = false
b = ""
}
}
extension StructTwo: EmptyInitializable {
init() {
c = 1
d = 1.0
}
}
func print(subject: EmptyInitializable) -> String? {
let dictionary = Dictionary(uniqueKeysWithValues:
Mirror(reflecting: subject).children.map {
($0.label!, String(describing: type(of: $0.value)))
}
)
return (try? JSONSerialization.data(withJSONObject: dictionary)).flatMap {
String(data: $0, encoding: .utf8)
}
}
print(subject: StructOne()) // "{"a":"Bool","b":"String"}"
print(subject: StructTwo()) // "{"d":"Float","c":"Int"}"
You still have to implement the empty init and assign some values and I'm afraid there's no way currently to avoid that.

Save state in Userdefaults

I have a class that saves the state of something, in my case some variable of the ViewController, but sometimes it loads wrong or old data, but I can't figure out why.
Maybe somebody can have a look of my code and see if it makes sense.
class TopFlopState: Codable, PersistenceState {
var group: Groups = .large {
didSet {
save()
}
}
var base: Bases = .usd {
didSet {
save()
}
}
var valueOne: StatIntervalBaseModel = StatIntervalBaseModel(stat: "ppc", interval: "24h", base: "usd") {
didSet {
save()
}
}
init(){
let savedValues = load()
if savedValues != nil {
self.group = savedValues!.group
self.base = savedValues!.base
self.valueOne = savedValues!.valueOne
}
}
}
This is the PersistenceState protocol:
/**
Saves and Loads the class, enum etc. with UserDefaults.
Has to conform to Codable.
Uses as Key, the name of the class, enum etc.
*/
protocol PersistenceState {
}
extension PersistenceState where Self: Codable {
private var keyUserDefaults: String {
return String(describing: self)
}
func save() {
saveUserDefaults(withKey: keyUserDefaults, myType: self)
}
func load() -> Self? {
return loadUserDefaults(withKey: keyUserDefaults)
}
private func saveUserDefaults<T: Codable>(withKey key: String, myType: T){
do {
let data = try PropertyListEncoder().encode(myType)
UserDefaults.standard.set(data, forKey: key)
print("Saved for Key:", key)
} catch {
print("Save Failed")
}
}
private func loadUserDefaults<T: Codable>(withKey key: String) -> T? {
guard let data = UserDefaults.standard.object(forKey: key) as? Data else { return nil }
do {
let decoded = try PropertyListDecoder().decode(T.self, from: data)
return decoded
} catch {
print("Decoding failed for key", key)
return nil
}
}
}
If a value gets set to the value it should automatically save, but like I set sometimes it saves the right values but loads the wrong ones...
In my opinion, It return the cache. Because in Apple official documentation, it state
UserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value
Maybe you can change the flow, when to save the data. In your code show that you call save() 3 times in init().

Enum associated value confusing

When I try use func obj func, I get the error:
Cannot invoke 'obj' with an argument list of type '(message: (QueueAddable))'
I'm confused with Swift types.
Obj func used to get concrete type for decode.
protocol QueueAddable: Encodable {
var playlistsCollection:String? { get }
var playlists: [String]? { get }
}
struct Playlist: QueueAddable {
var playlistsCollection:String? {
return "id"
}
var playlists: [String]? {
return ["id", "id2"]
}
private enum CodingKeys:String,CodingKey {
case playlistsCollection
case playlists
}
public func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: CodingKeys.self)
try values.encode(playlistsCollection, forKey: Playlist.CodingKeys.playlistsCollection)
try values.encode(playlists, forKey: .playlists)
}
}
func obj<Q>(message: Q) where Q: QueueAddable {
let encoder = JSONEncoder()
let data = try! encoder.encode(message)
}
enum SomeEnum {
case playlist(QueueAddable)
func doSome() throws -> Data {
switch self {
case .playlist(let queueAddable):
let encoder = JSONEncoder()
// Error on the following line:
obj(message: queueAddable)
return Data()
}
}
}
let playlist = Playlist()
let data = try SomeEnum.playlist(playlist).doSome()
I think the problem is that the function expects a type and not a protocol. If you make the enum generic using a type that implements the protocol it will work.
Change the enum's first two lines like this:
enum SomeEnum<Q : QueueAddable> {
case playlist(Q)
I tested in the following playground:
import Foundation
protocol QueueAddable: Encodable {
var playlistsCollection:String? { get }
var playlists: [String]? { get }
}
struct Playlist: QueueAddable {
var playlistsCollection:String? {
return "id"
}
var playlists: [String]? {
return ["id", "id2"]
}
private enum CodingKeys:String,CodingKey {
case playlistsCollection
case playlists
}
public func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: CodingKeys.self)
try values.encode(playlistsCollection, forKey: Playlist.CodingKeys.playlistsCollection)
try values.encode(playlists, forKey: .playlists)
}
}
func obj<Q>(message: Q) where Q: QueueAddable {
let encoder = JSONEncoder()
let data = try! encoder.encode(message)
}
enum SomeEnum<Q : QueueAddable> {
case playlist(Q)
func doSome() throws -> Data {
switch self {
case .playlist(let queueAddable):
let encoder = JSONEncoder()
// No longer error on the following line:
obj(message: queueAddable)
return Data()
}
}
}
let playlist = Playlist()
let data = try SomeEnum.playlist(playlist).doSome()
Hope it helps!

How to overcome the error of "Generic parameter 'T' is not used in function signature"?

I'm trying to convert the following to be generic.
extension RLMOrganization: DataProvider {
func getLastSyncToken() -> String {
let lastUpdated: RLMOrganization? = self.findAll(sortedBy: "syncToken").last
if let syncToken = lastUpdated?.syncToken {
return syncToken
} else {
return "00000000000000000000000000000000"
}
}
}
And have tried this:
protocol DataProvider: DatabaseLayer {
associatedtype T: Object
func findAll<T: Object>(sortedBy key: String) -> [T]
}
extension DataProvider {
func findAll<T: Object>(sortedBy key: String) -> [T] {
let database = self.getDatabase()
if let allObjects = database?.objects(T.self) {
let results = allObjects.sorted(byKeyPath: key, ascending: true)
return Array(results)
}
return []
}
func getLastSyncToken<T: Object>() -> String {
let lastUpdated = self.findAll(sortedBy: "syncToken").last as? T
if let value = lastUpdated?.value(forKey: "syncToken") { // get value from object by string name
let syncToken = value as! String
return syncToken
} else {
return "00000000000000000000000000000000"
}
}
...
But can't seem to overcome the error of:
Generic parameter 'T' is not used in function signature
I would think the compiler has everything it needs to determine type usage.
Below works for me, I don't know how findAll is defined but the problem is the reference to self as I see it so you need to define T there using associatedtype.
protocol DataProvider: DatabaseLayer {
associatedtype T: Object
func findAll(sortedBy: String) -> T?
}

How can I insert this entry into my dictionary?

I have a swift class called ArticleArchive as follows
import Foundation
class ArticleArchive: NSObject, NSCoding {
var entries: Dictionary<String, Dictionary<String, String>>?
var userDefaultsKey = "savedArticles"
override init() {
self.entries = Dictionary<String, Dictionary<String, String>>()
}
init(articles: Dictionary<String, Dictionary<String, String>>) {
self.entries = articles
}
required init(coder aDecoder: NSCoder) {
self.entries = aDecoder.decodeObjectForKey(userDefaultsKey) as? Dictionary
}
func encodeWithCoder(aCoder: NSCoder) {
if let articles = self.entries {
aCoder.encodeObject(articles, forKey: userDefaultsKey)
}
}
func populateArticles(articles: Dictionary<String, Dictionary<String, String>>) {
self.entries = articles
}
func save() {
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: userDefaultsKey)
}
func clear() {
NSUserDefaults.standardUserDefaults().removeObjectForKey(userDefaultsKey)
}
class func loadSaved() -> ArticleArchive? {
if let data = NSUserDefaults.standardUserDefaults().objectForKey("savedArticles") as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? ArticleArchive
}
return nil
}
}
In a viewController, I have the following var of type ArticleArchive at the top of the class
var savedArticles : ArticleArchive? = nil
In viewDidLoad I do the following to initialise the variable
if let savedArticles:ArticleArchive = ArticleArchive.loadSaved() {
self.savedArticles = savedArticles
} else {
self.savedArticles = ArticleArchive()
}
I am trying to insert a Dictionary<String, String> into the savedArticles variable like so but it doesn't work.
let currentDictionary = filtered[indexPath.row] as Dictionary<String, String>
self.savedArticles?.entries[currentDictionary["link"]] = currentDictionary
I get an error that states "Could not find an overload for 'subscript' that accepts the supplied arguments"
I've gone around in circles trying different things but no luck, maybe someone can help me out. How can I insert currentDictionary (a Dictionary<String, String>) into savedArticles?
The insertion works, if the dictionary entries is non-optional
There is a good reason to use non-optional types.
Let's assume that when the object ArticleArchive exists, there exists also the entries dictionary which is empty by default.
The encodeWithCoder and init(coder) methods are also safe, because it's ensured that the non-optional entries dictionary is saved anyway even if it's empty, so it's never nil.
Here's the code
Edit : I added a type alias ArticleDictionary for better readability
class ArticleArchive: NSObject, NSCoding {
typealias ArticleDictionary = Dictionary<String, Dictionary<String, String>>
var entries: ArticleDictionary
var userDefaultsKey = "savedArticles"
override init() {
self.entries = ArticleDictionary()
}
init(articles: ArticleDictionary) {
self.entries = articles
}
required init(coder aDecoder: NSCoder) {
self.entries = aDecoder.decodeObjectForKey(userDefaultsKey) as! ArticleDictionary
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.entries, forKey: userDefaultsKey)
}
func populateArticles(articles: ArticleDictionary) {
self.entries = articles
}
func save() {
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: userDefaultsKey)
}
func clear() {
NSUserDefaults.standardUserDefaults().removeObjectForKey(userDefaultsKey)
}
class func loadSaved() -> ArticleArchive? {
if let data = NSUserDefaults.standardUserDefaults().objectForKey("savedArticles") as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? ArticleArchive
}
return nil
}
}
as the link key might not exist, the value must be unwrapped.
savedArticles.entries[currentDictionary["link"]!] = currentDictionary
I modified your class to have this method:
func addEntry(key:String, dict:Dictionary<String,String>){
self.entries![key] = dict;
}
Then you can do this:
self.savedArticles?.addEntry(currentDictionary["link"]!, dict: currentDictionary)
Seems to work.
EDIT : If for some reason entries has to remain optional, then this can prevent a runtime crash (bit hacky but works)
func addEntry(key:String, dict:Dictionary<String,String>){
if let e = self.entries{
self.entries![key] = dict;
}else{
print("avoided runtime crash!")
}
}