Swift generic functions issues. Decrease duplicating code - ios

I want to understand how can decrease amount of duplicated code. I have two almost the same functions. The differences are next:
firs function returns array of [ExerciseEntity] and second function returns array of [WorkoutEntity]
func replaceExercisesIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [ExerciseEntity] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<ExerciseEntity>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<ExerciseEntity>(),
sourceArray: jsonObjects)
return managedObjects
}
func replaceWorkoutsIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [WorkoutEntity] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<WorkoutEntity>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<WorkoutEntity>(),
sourceArray: jsonObjects)
return managedObjects
}
This is a similar question related to how to use generic function I asked before.
I implemented this in my code but:
func importArray<T: ImportableUniqueObject>(from exercisesDict: [[String: Any]], transaction: BaseDataTransaction) -> [T] where T.ImportSource == [String: Any] {
let managedObjects = try? transaction.importUniqueObjects(Into<T>(), sourceArray: jsonObjects)
}
But here is few things, with T type
First - I can't add this code: editObject?.id = identifier
as there is no id in T type.
Second when I debug these generic functions debugger every time crashes:
Message from debugger: The LLDB RPC server has crashed. The crash log is located in ~/Library/Logs/DiagnosticReports and has a prefix 'lldb-rpc-server'. Please file a bug and attach the most recent crash log.
If interesting here is a file with log. I have not submitted it yet.
For sure I can add a lot of prints to track behavior, though it's a but annoying) But main task is to get rid of duplication.

Try this (I have not tested):
protocol MyProtocol {
var id: Int { get set }
}
struct ExerciseEntity {
var id: Int
}
struct WorkoutEntity {
var id: Int
}
func replaceWorkoutsIdentifiers<T: MyProtocol>(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [T] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<T>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<T>(),
sourceArray: jsonObjects)
return managedObjects as! T
}
Using:
let array: [ExerciseEntity] = replaceWorkoutsIdentifiers(from jsonObjects: ..., transaction: ...)

Related

Type of expression "[" is ambiguous without more context

I have searched "Type of expression is ambiguous without more context" but none of the instances are helping me figure out my issue.
struct Ouid {
let uid: String
}
public var completion: (Ouid) -> (Void)?
var results = [Ouid]()
func noteIsHere() {
let safetyEmail = DatabaseManager.safeEmail(emailAddress: otherUserEmail)
let recentUIDObserve = ref.child("\(safetyEmail)/uid")
recentUIDObserve.observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value as? String else {
return
}
print("this is value: \(String(describing: value))")
let results: [Ouid] = value.compactMap({ dictionary in
guard let uid = dictionary**[**"uid"] else { // for some reason, the "Type of expression is ambiguous without more context" highlights at the bracket.
return nil
}
})
}
I don't understand what is wrong because I tried to call data from firebase using the compactMap in another view controller and it worked.
This is the code that worked and has no errors:
struct Users {
let name: String
let email: String
}
public var completion: ((Users) -> (Void))?
func usersAreHere() {
let recentUserQuery = (ref?.child("user").queryLimited(toFirst: 11))!
print("recentuserquery: \(recentUserQuery)")
recentUserQuery.observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value as? [[String: String]] else {
return
}
print("\(value)")
let results: [Users] = value.compactMap({ dictionary in
guard let name = dictionary["name"]?.lowercased(),
let safeEmail = dictionary["email"] else {
return nil
}
return Users(name: name, email: safeEmail)
})
self.results = results
self.results.shuffle()
self.tableView.reloadData()
})
self.results.append(contentsOf: results)
}
First, compactMap will query all elements of an Array.
guard let value = snapshot.value as? String else {
return
}
As you can see, value is a String, so when you use value.compactMap({ dictionary in }, it will query all the elements of "value". "dictionary" is Character type(String is array of Character), not Dictionary. You can not use [].
In the 2nd example:
guard let value = snapshot.value as? [[String: String]] else {
return
}
value now is an Array of Dictionary ([[String: String]] == Array<[String: String]>), so when you use value.compactMap({ dictionary in }, "dictionary" is Dictionary type. Now you can use value["key"] to get the value

How to code Nested Loop with synchronous in swift?

In my project, I call getMain() and that has nested loop. That loop call setUp(). My problem is setUp() before finish, upper loop is quit.
Firstly call getMian():
func getMain(){
let entityDescription = NSFetchRequest<NSFetchRequestResult>(entityName: "MainThemeHome")
do{
let results = try context.fetch(entityDescription)
if(results.count) > 0 {
sections.removeAll()
debugPrint(results.count)
outer_count = results.count
for i in 0 ..< (results.count){
let match = results[i] as! NSManagedObject
let associated_url = match.value(forKey: "main_associated_url") as! String
let name = match.value(forKey: "main_name") as! String
//call function
self.setUpViews(associated_url: associated_url, main_name: name, i: i )
self.myGroup.leave()
}
}else{
}
}catch{}
}
Loop call setUp() is below,
private func setUp(associated_url : String , main_name: String,i : Int) {
if Reachability().isInternetAvailable() == true {
self.rest.auth(auth: self.prefs.value(forKey: "access_token") as! String!)
self.rest.get(url: StringResource().mainURL + associated_url , parma: [ "show_min": "true" ], finished: {(result : NSDictionary, status : Int) -> Void in
self.assetsTable.removeAll()
if(status == 200){
let data = result["data"] as! NSArray
for item in 0…data.count - 1 {
let themes : AnyObject = data[item] as AnyObject
let created = themes["created"] as! String
let assets_id = themes["id"] as! Int
let name = themes["name"] as! String
var poster_img_url = themes["poster_image_url"] as! String
let provider_id = themes["provider_id"] as! Int
poster_img_url = StringResource().posterURL + poster_img_url
self.assetsTable.append(AssetsTableItem(created: created, assets_id: assets_id, name: name, poster_image_url: poster_img_url , provider_id: provider_id))
}
}
self.sections.append(SectionsHome(package_name: main_name, package_url: associated_url,i: i,packageTable: self.assetsTable))
self.inner_count = self.sections.count
}else{
}
})
}
}
How to control setUp() is completely finished, loop will be increase count and quit. How to solve this problem. Please help.
You need to use closures in setUp function so that when the setUp if finished you will get the callback
private func setUp(associated_url : String , main_name: String,i : Int, ,sucess:((Void) -> Void)?,failure:((Void?) -> Void)?) {
if Reachability().isInternetAvailable() == true {
self.rest.auth(auth: self.prefs.value(forKey: "access_token") as! String!)
self.rest.get(url: StringResource().mainURL + associated_url , parma: [ "show_min": "true" ], finished: {(result : NSDictionary, status : Int) -> Void in
self.assetsTable.removeAll()
if(status == 200){
let data = result["data"] as! NSArray
for item in 0…data.count - 1 {
let themes : AnyObject = data[item] as AnyObject
let created = themes["created"] as! String
let assets_id = themes["id"] as! Int
let name = themes["name"] as! String
var poster_img_url = themes["poster_image_url"] as! String
let provider_id = themes["provider_id"] as! Int
poster_img_url = StringResource().posterURL + poster_img_url
self.assetsTable.append(AssetsTableItem(created: created, assets_id: assets_id, name: name, poster_image_url: poster_img_url , provider_id: provider_id))
}
}
self.sections.append(SectionsHome(package_name: main_name, package_url: associated_url,i: i,packageTable: self.assetsTable))
self.inner_count = self.sections.count
sucess!()
}else{
failure!()
}
})
}
}
And change the way you call the function as
self.setUp(associated_url: associated_url, main_name: name, i: i, sucess: { () in
debugPrint("SUCCESS")
}) { () in
debugPrint("FAILURE")
}
What I can understand is that you want to make loop wait until the data is not fetched in that particular interation number. If this is the case then you should look for synchronous operation. Like dont let the control passed to next iteration until the operation is finished. But this is a bad practice and makes your app behaviour non responsive.
I would say to work on asynchronous line and create a block or closure and put your code what is expected to perform when you receive your data and put the code outside that block that are not dependent on the data. Blocks anyway capture the environment in that case you should not be worried about the control getting passed.

Object Mapper - parsing array of [AnyObject]

I have response JSON of multitype objects from API.
It has type property inside. Now I'm trying to apply some kind of automated mapping basing on type property, but I can't make it work in any means.
private let modelClassMap = [
"first_type": First.self
]
func createModelWithDictionary(json: [String: AnyObject]) -> [AnyObject] {
var items: [AnyObject]
if let items = json["items"] as? [[String: AnyObject]] {
for item in items {
if let typeString = item["type"] as? String {
var Type = self.modelClassMap[typeString]
items.append(Mapper<Type>().map(item))
}
}
}
return items
}
error I am getting is that Type is not a type
What you're trying to do is not really possible, because template's associated types are not runtime. Compiler needs to know a type at compile time.
We can do it a bit differently, using enums:
enum ModelClassMap: String {
case FirstType = "first_type"
func map(item: [String: AnyObject]) -> AnyObject? {
switch self {
case FirstType:
return Mapper<First>().map(item)
}
}
}
And in your for-loop you can try convert string to enum:
func createModelWithDictionary(json: [String: AnyObject]) -> [AnyObject] {
var mappedItems: [AnyObject] = []
if let items = json["items"] as? [[String: AnyObject]] {
items.forEach() {
if let typeString = $0["type"] as? String,
let mappedType = ModelClassMap(rawValue: typeString),
let mappedObject = mappedType.map($0) {
// mappedObject represents an instance of required object, represented by "type"
mappedItems.append(mappedObject)
}
}
}
return mappedItems
}

Value of type 'Self.ManageableType' has no member 'uid'

This is my code, which builds in XCode 7.2.1.
When I try to build the project in XCode 7.3 beta 2, I got the error "Value of type 'Self.ManageableType' has no member 'uid'"
protocol Manageable {
typealias ManageableType : NSManagedObject
var uid: String { get set }
}
extension Manageable {
static func className() -> String {
return String(self)
}
static func fetchObjects(predicate: NSPredicate?,
completion:(fetchedObjects: [String: ManageableType]) -> ()) {
let entityDescription = NSEntityDescription.entityForName(className(),
inManagedObjectContext: CoreDataStack.sharedInstance.context)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
if let p = predicate {
fetchRequest.predicate = p
}
var fetchedObjectsDict: [String: ManageableType] = [:]
do {
let result = try CoreDataStack.sharedInstance.context.executeFetchRequest(fetchRequest) as! [ManageableType]
if result.count > 0 {
for object in result {
fetchedObjectsDict[object.uid] = object
}
}
} catch {
print("ERROR FETCH MANAGEABLE OBJECTS: \(error)")
}
completion(fetchedObjects: fetchedObjectsDict)
}
}
When I try to change loop code block into:
for object in result {
let uid = object.valueForKey("uid") as! String
fetchedObjectsDict[uid] = object
}
I got the error "Ambiguous use of 'valueForKey'"
Why these errors happen here in new XCode version, help please?
Your protocol extension needs a type constraint
extension Manageable where Self : NSManagedObject, ManageableType == Self { ... }

Swift parse json to struct

I have a struct like
struct Channel {
var id : Int = 0
var name = ""
}
and I am getting json from URL as
{"channel_list":[{"channel_id":0,"channel_name":"test1"},{"channel_id":0,"channel_name":"test2"}]}
However I am not able to get data as
func parseJson(anyObj:AnyObject) -> Array<Channel>{
var list:Array<Channel> = []
if anyObj is Array<AnyObject> {
var b:Channel = Channel()
for json in anyObj as! Array<AnyObject>{
b.id = (json["channel_id"] as AnyObject? as? Int) ?? 0
b.name = (json["channel_name"] as AnyObject? as? String) ?? ""
list.append(b)
}
}
return list
}
//read code
let anyObj: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0),error: nil) as AnyObject?
println(anyObj)
if let myobj=anyObj["channel_list"] as AnyObject {
self.Channellist=self.parseJson(anyObj!)
}
Whats wrong with this?
First, instead of using AnyObject, you should cast the JSON response as a Dictionary: [NSObject:AnyObject] then safe cast the result of anyObj["channel_list"] to an Array of Dictionaries [[NSObject:AnyObject]], because this is your JSON response format.
Then you need to use this type in your parseJSON function. We're also simplifying it while we're at it, because there's no need to do weird castings anymore.
Also, you were passing the wrong argument to your function (you used anyObj instead of myObj).
struct Channel {
var id : Int = 0
var name = ""
}
func parseJson(anyObj: [[NSObject:AnyObject]]) -> Array<Channel>{
var list: Array<Channel> = []
var b: Channel = Channel()
for json in anyObj {
b.id = (json["channel_id"] as? Int) ?? 0
b.name = (json["channel_name"] as? String) ?? ""
list.append(b)
}
return list
}
if let anyObj = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(0),error: nil) as? [NSObject:AnyObject] {
if let myobj = anyObj["channel_list"] as? [[NSObject:AnyObject]] {
self.Channellist=self.parseJson(myobj)
}
}
There's still room for improvement: you could create an initializer for your Struct, for example, and also create a typealias for the response types, use map to create the list, etc.
Here's how I would do it with Swift 2:
struct Channel {
var id : Int
var name: String
init?(JSON: [NSObject: AnyObject]?) {
guard let channelID = json["channel_id"] as? Int, let channelName = json["channel_name"] as? String
else { name = ""; id = 0; return nil }
name = channelName
id = channelID
}
}
func parseJSON(array: [[NSObject:AnyObject]]) -> [Channel?] {
return array.map { Channel(JSON: $0) }
// If you don't want to return optionals to channel you can do this instead:
// return array.map { Channel(JSON: $0) }.filter { $0 != nil }.map { $0! }
}
// And in the caller
do {
guard let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [NSObject : AnyObject]
else { throw NSError(domain ... // Setup error saying JSON wasn't parsed. }
guard let arrayContents = dict["channel_list"] as? [[NSObject:AnyObject]]
else { throw NSError(domain ... // Setup error saying array wasn't found. }
let channels = parseJSON(arrayContents)
}
catch {
print(error)
}

Resources