extra entities with nil attributes in Core Data - ios

Here is my code. I have extra entities with nil attributes in Core Data. when I delete and run application firstly, I get one saved object with nil attributes fetched from core data.
class RepositoryEntity {
private var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func fetchRepositories() -> [RepositoryEntity] {
do {
return try context.fetch(RepositoryEntity.fetchRequest())
} catch(let error) {
print("errr: ", error.localizedDescription)
}
return []
}
func saveObject(repo: Repository, onSuccess: () -> Void, onFailure: (_ error: String) -> Void) {
let repoEntity = RepositoryEntity(context: self.context)
repoEntity.fullName = repo.fullName
repoEntity.dateCreated = repo.dateCreated
repoEntity.url = repo.url
repoEntity.language = repo.language
repoEntity.repoDescription = repo.repoDescription
repoEntity.id = repo.id
let ownerEntity = OwnerEntity(context: self.context)
ownerEntity.ownerName = repo.owner.ownerName
ownerEntity.avatarUrl = repo.owner.avatarUrl
repoEntity.addToOwner(ownerEntity)
// Save the data
do {
try context.save()
onSuccess()
} catch(let error) {
onFailure("Something Happend. Try again later.")
print(error.localizedDescription)
}
}
func deleteRepository(repo: Repository, onSuccess: () -> Void, onFailure: (_ error: String) -> Void) {
let repositories = fetchRepositories()
guard let deletableRepo = repositories.first(where: {$0.id == repo.id}) else { return }
self.context.delete(deletableRepo)
do {
try context.save()
onSuccess()
} catch(let error) {
onFailure("Something Happens. try again later.")
print(error.localizedDescription)
}
}
}
when I delete and run application firstly, I get one saved object with nil attributes fetched from core data.

When you write "I get one saved object...": what object? RepositoryEntity? How do you know you have a saved object, by calling fetchRepositories()? I can only assume it's like that (as opposed to having an empty OwnerEntity).
In that case, the problem is that, to call func fetchRepositories() you need to create an instance. So, when you start with zero objects, as soon as you call fetchRepositories() you already have at least one.
Change:
func fetchRepositories() -> [RepositoryEntity]
with:
static func fetchRepositories() -> [RepositoryEntity]
and call it from the type:
RepositoryEntity.fetchRepositories()
The same also for deleteRepository.

Related

nested stringByEvaluatingJavaScript in UIWebview migrate to WKWebview

In UIWebview implementation I had something like:-
if let pageBody = webView?.stringByEvaluatingJavaScript(from: "document.body.innerHTML") {
if pageBody.contains("xyz") {
return webView?.stringByEvaluatingJavaScript(from:
"document.getElementById('xyz').innerHTML")
}
}
I am trying to migrate this to WKWebview:-
I did something like this but the return value gets lost in the nested completion handlers:-
wkWebView?.evaluateJavaScript("document.body.innerHTML", completionHandler: { (pageBody, nil) in
if let pBody = (pageBody as? String)?.contains("xyz"), pBody {
wkWebView?.evaluateJavaScript("document.getElementById('xyz').innerHTML", completionHandler: { (result, error) in
resultString = result as? String
})
}
})
return resultString
evaluateJavaScript is run asynchronously (unlike stringByEvaluatingJavaScript which will wait until the javascript has been evaluated and return the result), so resultString hasn't been set by the time you return it. You will need to organize your code so that the result of javascript is used after the completion handler has been run. Something like this:
func getElementXYZ(_ completionHandler: #escaping (String?) -> Void) {
wkWebView?.evaluateJavaScript("document.body.innerHTML") { (pageBody, nil) in
if let pBody = (pageBody as? String)?.contains("xyz"), pBody {
wkWebView?.evaluateJavaScript("document.getElementById('xyz').innerHTML") { (result, error) in
completionHandler(result as? String)
}
}
}
}
And to call the function:
self.getElementXYZ { result in
//Do something with the result here
}

Completion Handler not working Properly in swift

im trying to populate two arrays with the data i get from the firestore database. im getting the data successfully however it was late and when i printed them in viewDidLoad it printed empty arrays. so i decided to implement a completion handler however it still shows and empty array. can anyone tell me why my print statement runs before the functions even though im using escaping
func yourFunctionName(finished: #escaping () -> Void) {
db.collection("countries")
.whereField("capital", isEqualTo: "washington")
.getDocuments { (snapshot, error) in
if error == nil{
for document in snapshot!.documents {
let documentData = document.data()
//print(document.documentID)
//print(documentData)
self.countries.append(document.documentID)
}
}
}
db.collection("countries")
.whereField("climate", isEqualTo: "pleasant")
.getDocuments { (snapshot, error) in
if error == nil {
for document in snapshot!.documents{
let documentData = document.data()
//print(document.documentID)
//print(documentData)
self.countries2.append(document.documentID)
}
}
}
finished()
}
viewDidLoad(){
yourFunctionName {
print(self.countries)
print(self.countries2)
}
}
i get the empty arrays in the output although the arrays should have been filled before i called print though im using #escaping. please someone help me here
You are actually not escaping the closure.
For what I know the "#escaping" is a tag that the developper of a function use to signify the person using the function that the closure he/she is passing will be stored and call later (after the function ends) for asynchronicity and memory management. In your case you call the closure passed immediately in the function itself. Hence the closure is not escaping.
Also the firebase database is asynchronous. Meaning that you don't receive the result immediately
This part :
{ (snapshot, error) in
if error == nil{
for document in snapshot!.documents {
let documentData = document.data()
//print(document.documentID)
//print(documentData)
self.countries.append(document.documentID)
}
}
}
is itself a closure, that will be executed later when the result of the query is produced. As you can see in the doc, the function is escaping the closure : https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/Query.html#getdocumentssource:completion:
func getDocuments(source: FirestoreSource, completion: #escaping FIRQuerySnapshotBlock)
So to summarise :
The code for the firebase query will be call later (but you don't know when), and your closure "finished" is called immediately after having define the firebase callback, thus before it has been called.
You should call your finished closure inside the firebase callback to have it when the arrays are populated.
I think your main problem here is not about to populate your arrays, your problem is how to get it better.
I did an example of how you could do that in a better way.
First, break your big function in two, and populate it out of your function.
Look at this code and observe the viewDidLoad implementation.
func countries(withCapital capital: String, completionHandler: (Result<Int, Error>) -> Void) {
db.collection("countries")
.whereField("capital", isEqualTo: capital)
.getDocuments { (snapshot, error) in
guard error == nil else {
completionHandler(.failure(error!))
return
}
let documents = snapshot!.documents
let ids = documents.map { $0.documentID }
completionHandler(.success(ids))
}
}
func countries(withClimate climate: String, completionHandler: (Result<Int, Error>) -> Void) {
db.collection("countries")
.whereField("climate", isEqualTo: climate)
.getDocuments { (snapshot, error) in
guard error == nil else {
completionHandler(.failure(error!))
return
}
let documents = snapshot!.documents
let ids = documents.map { $0.documentID }
completionHandler(.success(ids))
}
}
func viewDidLoad(){
countries(withClimate: "pleasant") { (result) in
switch result {
case .success(let countries):
print(countries)
self.countries2 = countries
default:
break
}
}
countries(withCapital: "washington") { (result) in
switch result {
case .success(let countries):
print(countries)
self.countries = countries
default:
break
}
}
}
If you have to call on main thread call using it
DispathQueue.main.async {
// code here
}
I hope it helped you.
They are returning empty arrays because Firebase's function is actually asynchronous (meaning it can run after the function "yourFunctionName" has done its work)
in order for it to work as intended (print the filled arrays)
All you need to do is call it inside Firebase's closure itself, like so:
func yourFunctionName(finished: #escaping () -> Void) {
db.collection("countries")
.whereField("capital", isEqualTo: "washington")
.getDocuments { (snapshot, error) in
if error == nil{
for document in snapshot!.documents {
let documentData = document.data()
self.countries.append(document.documentID)
finished() //<<< here
}
}
}
db.collection("countries")
.whereField("climate", isEqualTo: "pleasant")
.getDocuments { (snapshot, error) in
if error == nil {
for document in snapshot!.documents{
let documentData = document.data()
self.countries2.append(document.documentID)
finished() //<<< and here
}
}
}
}
I has been sometime since I encountered that problem but I beleve the issue is that you are calling the completion handler to late. What I mean is that you can try to call it directly after you have lopped throught the documents. One idea could be to return it in the compltion or just do as you do. Try this instead:
func yourFunctionName(finished: #escaping ([YourDataType]?) -> Void) {
var countires: [Your Data Type] = []
db.collection("countries")
.whereField("capital", isEqualTo: "washington")
.getDocuments { (snapshot, error) in
if error == nil{
for document in snapshot!.documents {
let documentData = document.data()
//print(document.documentID)
//print(documentData)
countries.append(document.documentID)
}
finished(countries)
return
}
}
}
func yourSecondName(finished: #escaping([YouDataType]?) -> Void) {
var countries: [Your data type] = []
db.collection("countries")
.whereField("climate", isEqualTo: "pleasant")
.getDocuments { (snapshot, error) in
if error == nil {
for document in snapshot!.documents{
let documentData = document.data()
//print(document.documentID)
//print(documentData)
countires.append(document.documentID)
}
finished(countires)
return
}
}
func load() {
yourFunctionName() { countries in
print(countires)
}
yourSecondName() { countries in
print(countries)
}
}
viewDidLoad(){
load()
}
What this will do is that when you call the completion block that is of type #escaping as well as returning after it you won't respond to that completely block any longer and therefore will just use the data received and therefore not care about that function anymore.
I good practice according to me, is to return the object in the completion block and use separate functions to be easier to debug and more efficient as well does it let you return using #escaping and after that return.
You can use a separate method as I showed to combine both methods and to update the UI. If you are going to update the UI remember to fetch the main queue using:
DispathQueue.main.async {
// Update the UI here
}
That should work. Greate question and hope it helps!

Closure returning data before async work is done

UPDATED WITH PROPOSED SOLUTION AND ADDITIONAL QUESTION
I'm officially stuck and also in callback hell. I have a call to Firebase retrieving all articles in the FireStore. Inside each article object is a an Image filename that translates into a storage reference location that needs to be passed to a function to get the absolute URL back. I'd store the URL in the data, but it could change. The problem is the ArticleListener function is prematurely returning the closure (returnArray) without all the data and I can't figure out what I'm missing. This was working fine before I added the self.getURL code, but now it's returning the array back empty and then doing all the work.
If anyone has some bonus tips here on chaining the methods together without resorting to PromiseKit or GCD that would be great, but open to all suggestions to get this to work as is
and/or refactoring for more efficiency / readability!
Proposed Solution with GCD and updated example
This is calling the Author init after the Article is being created. I am trying to transform the dataDict dictionary so it get's used during the Author init for key ["author"]. I think I'm close, but not 100% sure if my GCD enter/leave calls are happening in the right order
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
print("Error in setting up snapshot listener - \(error)")
} else {
let fireStoreDispatchGrp = DispatchGroup() /// 1
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
//NEW EXAMPLE WITH ADDITIONAL TASK HERE
if let author = $0.data()["author"] as? DocumentReference {
author.getDocument() {(authorSnapshot, error) in
fireStoreDispatchGrp.enter() //1
if let error = error {
print("Error getting Author from snapshot inside Article getDocumentFunction - leaving dispatch group and returning early")
fireStoreDispatchGrp.leave()
return
}
if let newAuthor = authorSnapshot.flatMap(Author.init) {
print("Able to build new author \(newAuthor)")
dataDict["author"] = newAuthor
dataDict["authorId"] = authorSnapshot?.documentID
print("Data Dict successfully mutated \(dataDict)")
}
fireStoreDispatchGrp.leave() //2
}
}
///END OF NEW EXAMPLE
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
fireStoreDispatchGrp.enter() /// 2
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
dataDict["image"] = url.absoluteString
case .failure(let error):
print("Error getting URL for author: \n Error: \(error) \n forReference: \(reference) \n forArticleID: \(id)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
returnArray.append(newArticle)
}
fireStoreDispatchGrp.leave() ///3
}
}
}
//Completion block
print("Exiting dispatchGroup all data should be setup correctly")
fireStoreDispatchGrp.notify(queue: .main) { ///4
completion(returnArray)
}
}
}
updateListeners(for: listener)
}
Original Code
Calling Setup Code
self.manager.SetupArticleListener() { [weak self] articles in
print("🌈🌈🌈🌈🌈🌈🌈In closure function to update articles🌈🌈🌈🌈🌈🌈🌈")
self?.articles = articles
}
Article Listener
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
printLog("Error retrieving documents while adding snapshotlistener, Error: \(error.localizedDescription)")
} else {
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
print("Success in getting url from reference \(url)")
dataDict["image"] = url.absoluteString
print("Dictionary XFORM")
case .failure(let error):
print("Error retrieving URL from reference \(error)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
printLog("Success in creating Article with xformed url")
returnArray.append(newArticle)
}
}
}
}
print("🌈🌈🌈🌈🌈🌈🌈 sending back completion array \(returnArray)🌈🌈🌈🌈🌈🌈🌈")
completion(returnArray)
}
}
updateListeners(for: listener)
}
GetURL
private func getURL(reference: StorageReference, _ result: #escaping (Result<URL, Error>) -> Void) {
reference.downloadURL() { (url, error) in
if let url = url {
result(.success(url))
} else {
if let error = error {
print("error")
result(.failure(error))
}
}
}
}
You need dispatch group as the for loop contains multiple asynchronous calls
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
printLog("Error retrieving documents while adding snapshotlistener, Error: \(error.localizedDescription)")
} else {
let g = DispatchGroup() /// 1
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
g.enter() /// 2
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
print("Success in getting url from reference \(url)")
dataDict["image"] = url.absoluteString
print("Dictionary XFORM")
case .failure(let error):
print("Error retrieving URL from reference \(error)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
printLog("Success in creating Article with xformed url")
returnArray.append(newArticle)
}
g.leave() /// 3
}
}
}
g.notify(queue:.main) { /// 4
print("🌈🌈🌈🌈🌈🌈🌈 sending back completion array \(returnArray)🌈🌈🌈🌈🌈🌈🌈")
completion(returnArray)
}
}
}
updateListeners(for: listener)
}

Fetch request required after decoding into a NSManagedObject object

I have the following generic function that works: it correctly creates the objects and I know is saved into core data because if do a fetch request right after, I get the object I just created. However, the object itself isn't a valid core data object (x-core data fault). Is there any way around so I don't have to do a fetch request right after a decoding an object? Many thanks.
func decode<T: Decodable>(data: Data?, objectType: T.Type, save: Bool = true, completionHandler: #escaping (T) -> ())
{
guard let d = data else { return }
do
{
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = SingletonDelegate.shared.context
let root = try JSONDecoder(context: privateContext).decode(objectType, from: d)
if save
{
try privateContext.save()
privateContext.parent?.performAndWait
{
do
{
if let p = privateContext.parent
{
try p.save()
}
}catch
{
print(error)
}
}
}
DispatchQueue.main.async
{
completionHandler(root)
}
}catch
{
print(error)
}
}
extension CodingUserInfoKey
{
static let context = CodingUserInfoKey(rawValue: "context")!
}
extension JSONDecoder
{
convenience init(context: NSManagedObjectContext)
{
self.init()
self.userInfo[.context] = context
}
}
A core data fault is a valid Core Data object; it just hasn't been retrieved from the backing store into memory yet.
To reduce memory use, Core Data only fetches the full object when you access one of its properties. This fetch is automatic and effectively transparent to your code.
This means you don't need to do anything special; you can just use the managed object.

Saving in background causes response time to be delayed (iOS)

I have a table, which uses a NSFetchedResultsController to populate it's data. When I refresh my table, I need to update all 50+ items, so I do the following: I make a call to the server which returns JSON data, store the "media" object into an array, loop through this array and individually store each object to core data (in background thread), then reload the table. This works fine. However there is a major issue.
Sometimes the step of saving to the database takes 7+ seconds, due to looping through large arrays and individually storing each object to core data. And while this step is executing, when I fetch other data from the server, the response time is delayed tremendously. I wont be able to fetch new data until the save process is complete. I'm quite confused because this is supposed to be done in the background thread and not block other server calls.
Why does saving data to core data in bg causing my response time to be delayed? Is there a better approach to storing large arrays to core data without disrupting any responses?
//Refreshing User Table method
class func refreshUserProfileTable(callback: (error: NSError?) -> Void) {
//getProfile fetches data from server
ProfileWSFacade.getProfile(RequestManager.userID()!) {
(profile, isLastPage, error) -> () in
DataBaseManager.sharedInstance.saveInBackground({ (backgroundContext) in
let mediaList = profile?["media"] as? Array<JSONDictionary>
if let mediaList = mediaList {
//Response time is delayed when this loop is executing
for media in mediaList {
DataBaseManager.sharedInstance.storeObjectOfClass(Media.self, dict: media, context: backgroundContext)
}
}
}, completion: {
callback(error: error)
})
}
}
//MARK: Core data methods:
//Save in background method in Database manager
func saveInBackground(
block: (backgroundContext: NSManagedObjectContext) -> Void,
completion: (Void->Void)? = nil)
{
let mainThreadCompletion = {
if let completion = completion {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion()
})
}
}
backgroundContext.performBlock { () -> Void in
guard RequestManager.userID() != nil else {
mainThreadCompletion()
return
}
block(backgroundContext: self.backgroundContext)
if RequestManager.userID() != nil {
_ = try? self.backgroundContext.save()
DataBaseManager.sharedInstance.save()
}
mainThreadCompletion()
}
}
//Stores class object
func storeObjectOfClass<T: NSManagedObject where T: Mappable>(
entityClass:T.Type,
dict: JSONDictionary,
context: NSManagedObjectContext? = nil) -> T
{
let context = context ?? mainManagedObjectContext
let predicate = NSPredicate(format: "%K LIKE %#", entityClass.primaryKey(), entityClass.primaryKeyFromDict(dict))
let requestedObject = DataBaseManager.createOrUpdateFirstEntity(
entityType: T.self,
predicate: predicate,
context: context) { (entity) -> () in
entity.populateFromDictionary(dict)
}
return requestedObject
}
//Creates or updates core data entity
class func createOrUpdateFirstEntity<T: NSManagedObject>(
entityType entityType: T.Type,
predicate: NSPredicate,
context: NSManagedObjectContext,
entityUpdateBlock:(entity: T) -> ()) -> T
{
guard DataBaseManager.sharedInstance.doPersistentStoreAvailible() else { return T() }
let desc = NSEntityDescription.entityForName(String(entityType), inManagedObjectContext: context)!
let existingEntityRequest = NSFetchRequest()
existingEntityRequest.entity = desc
existingEntityRequest.predicate = predicate
let requestedObject = try? context.executeFetchRequest(existingEntityRequest).first
if let requestedObject = requestedObject as? T {
entityUpdateBlock(entity: requestedObject)
return requestedObject
} else {
let newObject = T(entity: desc, insertIntoManagedObjectContext: context)
entityUpdateBlock(entity: newObject)
return newObject
}
}
I found out that .performBlock follows the FIFO rule, first in, first out. Meaning the blocks will be executed in the order in which they were put into the internal queue: SO Link. Because of that, the next rest call would wait until the first block has completed before it saved, and did its callback. The actual response time wasnt slow, it was just the saving time because of FIFO.
The solution was to use a different NSManagedContext for profile loading, rather than using the one that was being used for all background calls.
let profileContext: NSManagedObjectContext
//Instead of calling saveInBackground, we save to saveInProfileContext, which wont block other rest calls.
func saveInProfileContext(
block: (profileContext: NSManagedObjectContext) -> Void,
completion: (Void->Void)? = nil)
{
let mainThreadCompletion = {
if let completion = completion {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion()
})
}
}
profileContext.performBlock { () -> Void in
guard RequestManager.userID() != nil else {
mainThreadCompletion()
return
}
block(profileContext: self.profileContext)
if RequestManager.userID() != nil {
_ = try? self.profileContext.save()
DataBaseManager.sharedInstance.save()
}
mainThreadCompletion()
}
}

Resources