Most of the time it I get all the results, but sometimes I don't, and that's obviously a problem. I have a function like:
func addProduct(thisProduct:Product) {
let realm = try! Realm()
try! realm.write {
realm.add(thisProduct)
}
countProductsAddedToday()
// sometimes doesn't include the just added product to the count
}
The problem is my function countProductsAddedtoday() is not always returning the correct number of products when called immediately after a write transaction like in this case. If it gets called later (by tapping a button in my app), it returns the right count.
I'm hoping this is a common problem and that I'm doing something I'm not supposed to be doing.
The function countProductsAddedToday() is like this:
func countProductsAddedToday() -> Int {
let predicate = NSPredicate(format: "timeAdded >= %d", unixTimestampForToday)
let realm = try! Realm()
let results = realm.objects(Product).filter(predicate)
return results.count
}
Additional information: I can't be sure but I think the problem happens more often when adding products quickly, but then stops happening. Is there some delayed writing or caching of results or something like that?
The error I'm getting when adding products on a physical device:
0 Realm 0x000000010042d7e0 _ZN5realm4util18terminate_internalERNSt3__118basic_stringstreamIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE + 44
1 Realm 0x000000010042da0c _ZN5realm4util9terminateEPKcS2_l + 340
2 Realm 0x00000001002a70ac _ZN5realm5_impl16RealmCoordinator19run_async_notifiersEv + 2124
3 Realm 0x00000001002a6668 _ZN5realm5_impl16RealmCoordinator9on_changeEv + 40
4 Realm 0x000000010026adf4 _ZN5realm5_impl20ExternalCommitHelper6listenEv + 672
5 Realm 0x000000010026b89c _ZZN5realm5_impl20ExternalCommitHelperC1ERNS0_16RealmCoordinatorEENK3$_0clEv + 24
6 Realm 0x000000010026b878 _ZNSt3__112__async_funcIZN5realm5_impl20ExternalCommitHelperC1ERNS2_16RealmCoordinatorEE3$_0JEE9__executeIJEEEvNS_15__tuple_indicesIJXspT_EEEE + 64
7 Realm 0x000000010026b82c _ZNSt3__112__async_funcIZN5realm5_impl20ExternalCommitHelperC1ERNS2_16RealmCoordinatorEE3$_0JEEclEv + 24
8 Realm 0x000000010026b73c _ZNSt3__119__async_assoc_stateIvNS_12__async_funcIZN5realm5_impl20ExternalCommitHelperC1ERNS3_16RealmCoordinatorEE3$_0JEEEE9__executeEv + 32
9 Realm 0x000000010026bfc0 _ZNSt3__114__thread_proxyINS_5tupleIJMNS_19__async_assoc_stateIvNS_12__async_funcIZN5realm5_impl20ExternalCommitHelperC1ERNS5_16RealmCoordinatorEE3$_0JEEEEEFvvEPSB_EEEEEPvSG_ + 388
10 libsystem_pthread.dylib 0x0000000180cebb28 <redacted> + 156
11 libsystem_pthread.dylib 0x0000000180ceba8c <redacted> + 0
12 libsystem_pthread.dylib 0x0000000180ce9028 thread_start + 4
Change your function to:
func countProductsAddedToday() -> Int {
let predicate = NSPredicate(format: "timeAdded >= %d", unixTimestampForToday)
let realm = try! Realm()
realm.refresh()
let results = realm.objects(Product).filter(predicate)
return results.count
}
Related
after a successful build through xcode, the app runs in the simulator and my iPhone - when I distribute it for testing, it crashes when test users try to preform a search.
If i hardcode the URL elements - it works in both simulation mode and functions perfectly in the users test trials.
i have CrashLytics operating and it says the crash is a result of this line of code
let allContactsData = try Data(contentsOf: urlResults!)
i check in the previous VC that all the fields contain a value - and a "print" confirms the same
Company : Accountant
Suburb : Southport
State : QLD
Getting results from: http://www.myawsmurl.com.au/api/get_details.php?no=Accountant&state=QLD&suburb=Southport
var's are set at the top of the class via the following:
var toSearchFor: String = ""
var toSearchForSuburb: String = ""
var toSearchForState: String = ""
and this is the func that causes the issue:
func getResults() {
toSearchFor = searchingFor.trimmingCharacters(in: .whitespacesAndNewlines)
toSearchForSuburb = searchingSuburb.trimmingCharacters(in: .whitespacesAndNewlines)
toSearchForState = searchingState.trimmingCharacters(in: .whitespacesAndNewlines)
print("Company : \(toSearchFor)")
print("Suburb : \(toSearchForSuburb)")
print("State : \(toSearchForState)")
//toSearchFor = "Accountant"
//toSearchForSuburb = "Southport"
//toSearchForState = "QLD"
//print("Company : \(toSearchFor)")
//print("Suburb : \(toSearchForSuburb)")
//print("State : \(toSearchForState)")
let searchURL = ("http://www.myawsmurl.com.au/api/get_details.php?no=" + toSearchFor + "&state=" + toSearchForState + "&suburb=" + toSearchForSuburb)
let urlResults = URL(string:searchURL)
print("Getting results from: \(searchURL)")
do {
let allContactsData = try Data(contentsOf: urlResults!)
let allContacts = try JSONSerialization.jsonObject(with: allContactsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : NSArray]
etc etc
}
catch {
}
tableView.reloadData()
}
as mentioned before, if i uncomment the hardcoded vars, it runs as excepted without issues.
any help would be appreciated as i still cant figure out why it runs in simulation mode without any issues but fails on live testing even though all fields have a value.
EDIT: (crash data)
#0. Crashed: com.apple.main-thread
0 ThisAWSMAPP 0x1000752e4 ViewController.getResults() -> () (ViewController.swift:158)
1 ThisAWSMAPP 0x100076180 specialized ViewController.viewDidAppear(Bool) -> () (ViewController.swift)
2 ThisAWSMAPP 0x100071e3c #objc ViewController.viewDidAppear(Bool) -> () (ViewController.swift)
3 UIKit 0x18cb0ddb0 -[UIViewController _setViewAppearState:isAnimating:] + 856
4 UIKit 0x18cb0e31c -[UIViewController _endAppearanceTransition:] + 228
5 UIKit 0x18cbc4d64 -[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:] + 1224
6 UIKit 0x18cc93c5c __49-[UINavigationController _startCustomTransition:]_block_invoke + 232
7 UIKit 0x18cc1aa1c -[_UIViewControllerTransitionContext completeTransition:] + 116
8 UIKit 0x18cd68fac __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke.99 + 724
9 UIKit 0x18cb2e9d0 -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 492
10 UIKit 0x18cb2e4f8 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
11 UIKit 0x18cb2e314 -[UIViewAnimationState animationDidStop:finished:] + 160
12 QuartzCore 0x189cdf0d4 CA::Layer::run_animation_callbacks(void*) + 260
13 libdispatch.dylib 0x18587e9a0 _dispatch_client_callout + 16
14 libdispatch.dylib 0x1858835e8 _dispatch_main_queue_callback_4CF + 996
15 CoreFoundation 0x1869750c0 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
16 CoreFoundation 0x186972cdc __CFRunLoopRun + 1572
17 CoreFoundation 0x1868a2d94 CFRunLoopRunSpecific + 424
18 GraphicsServices 0x18830c074 GSEventRunModal + 100
19 UIKit 0x18cb5b130 UIApplicationMain + 208
20 ThisAWSMAPP 0x1000646d8 main (AppDelegate.swift:16)
21 libdyld.dylib 0x1858b159c start + 4
URL(string:) may return nil if the string is not a correctly formatted URL. It is not safe to force unwrap it as urlResults!, especially if you are constructing it from user input.
It safer like this:
if let url = urlResults {
let allContactsData = try Data(contentsOf:url)
// etc
} else {
// log an error for debugging
}
You should make sure the strings are URL encoded
String.trimmingCharacters only removes characters from beginning and end of the string. But your input variables may contain embedded spaces, or other illegal characters for a URL string.
Translate the input strings like this:
toSearchFor = searchingFor.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
The carsh at the given line indicates your urlResults is getting nil somehow.
Try encoding the url string before making URL object.
let toSearchFor = ""
let toSearchForSuburb = ""
let toSearchForState = ""
let searchURL = ("http://www.myawsmurl.com.au/api/get_details.php?no=" + toSearchFor + "&state=" + toSearchForState + "&suburb=" + toSearchForSuburb).addingPercentEncoding(withAllowedCharacters: . urlQueryAllowed)
if let url = searchURL, let urlResults = URL(string:url) {
print("Getting results from: \(url)")
do {
let allContactsData = try Data(contentsOf: urlResults!)
let allContacts = try JSONSerialization.jsonObject(with: allContactsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : NSArray]
}
catch {
}
}
Seems like thread issue. Can you try executing your code on main thread.
DispatchQueue.main.async({
let searchURL = ("http://www.myawsmurl.com.au/api/get_details.php?no=" + toSearchFor + "&state=" + toSearchForState + "&suburb=" + toSearchForSuburb)
let urlResults = URL(string:searchURL)
print("Getting results from: \(searchURL)")
do {
let allContactsData = try Data(contentsOf: urlResults!)
let allContacts = try JSONSerialization.jsonObject(with: allContactsData, options: JSONSerialization.ReadingOptions.allowFragments) as! [String : NSArray]
etc etc
}
catch {}
self.tableView.reloadData()
})
NSData is extended to determine the file type:
extension NSData {
var dataType: String? {
// Ensure data length is at least 1 byte
guard self.length > 0 else { return nil }
// Get first byte
var c = [UInt8](count: 1, repeatedValue: 0)
self.getBytes(&c, length: 1)
// Identify data type
switch (c[0]) {
case 0xFF:
return "jpg"
case 0x89:
return "png"
case 0x47:
return "gif"
case 0x49, 0x4D:
return "tiff"
default:
return nil //unknown
}
}
}
The method above is called on a NSData object from image data that is fetched from a server.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
do {
// Fetch image synchronously from server
let query = PFQuery(className: <...>)
let result = try query.getFirstObject()
guard
let imageObject = result.objectForKey(<...>) as? PFFile,
let imageData = try? imageObject.getData(),
let image = imageData.dataType == "gif" ? UIImage.animatedImageWithAnimatedGIFData(imageData) : UIImage(data: imageData)
else {
return
}
<...>
} catch (let error as NSError) {
<...>
}
}
However the app very rarely crashes at line self.getBytes:
What is the reason for this?
The buffer of getBytes is &c, an UnsafeMutablePointer - do I have to take any special memory considerations because of that?
Update
The crashes still occur with the following variation of the code:
// Get first byte
var c: UInt8 = 0;
self.getBytes(&c, length: 1)
Update
The crashes still occur with the following variation of the code:
// Get first byte
var c = [UInt8](count: 1, repeatedValue: 0)
c.withUnsafeMutableBufferPointer {
buffer in
getBytes(buffer.baseAddress, length: 1)
}
guard c.indices.contains(0) else { return nil }
I got the following crash and included the whole thread, maybe someone can spot a hint:
Thread 18 Crashed:
0 libsystem_platform.dylib 0x21a8e198 _platform_memmove$VARIANT$CortexA9 + 92
1 Foundation 0x22512923 __34-[_NSDispatchData getBytes:range:]_block_invoke + 176
2 libdispatch.dylib 0x218d238d _dispatch_data_apply + 82
3 libdispatch.dylib 0x218d4a51 dispatch_data_apply + 26
4 Foundation 0x22512865 -[_NSDispatchData getBytes:range:] + 86
5 Foundation 0x2267730b -[_NSDispatchData getBytes:length:] + 24
6 MyAppName 0x00079ba0 partial apply forwarder for (extension in MyAppName):__ObjC.NSData.(dataType.getter : Swift.String?).(closure #1) (NSData+Extension.swift:54)
7 MyAppName 0x00079c14 partial apply forwarder for reabstraction thunk helper from #callee_owned (#inout Swift.UnsafeMutableBufferPointer<Swift.UInt8>) -> (#unowned (), #error #owned Swift.ErrorType) to #callee_owned (#inout Swift.UnsafeMutableBufferPointer<Swift.UInt8>) -> (#out (), #error #owned Swift.ErrorType) (NSData+Extension.swift:0)
8 MyAppName 0x00079cb8 generic specialization <Swift.UInt8, ()> of Swift.Array.withUnsafeMutableBufferPointer <A> ((inout Swift.UnsafeMutableBufferPointer<A>) throws -> A1) throws -> A1 (NSData+Extension.swift:0)
9 MyAppName 0x00079a70 (extension in MyAppName):__ObjC.NSData.dataType.getter : Swift.String? (NSData+Extension.swift:55)
10 MyAppName 0x00079948 #objc (extension in MyAppName):__ObjC.NSData.dataType.getter : Swift.String? (NSData+Extension.swift:0)
11 MyAppName 0x000d2264 MyAppName.DataManager.(fetchImagesFromServer (MyAppName.ImageSet) -> ()).(closure #1) (DataManager.swift:1214)
12 libdispatch.dylib 0x218cd823 _dispatch_call_block_and_release + 8
13 libdispatch.dylib 0x218dc5e9 _dispatch_root_queue_drain + 1558
14 libdispatch.dylib 0x218dbfcd _dispatch_worker_thread3 + 94
15 libsystem_pthread.dylib 0x21a91b29 _pthread_wqthread + 1022
16 libsystem_pthread.dylib 0x21a91718 start_wqthread + 6
Update
The crashes still occur with the following variation of the code:
// Get first byte
var c = UnsafeMutablePointer<UInt8>.alloc(1)
defer { c.dealloc(1) }
self.getBytes(c, length: 1)
switch (c[0]) { ...
With the help of an Apple engineer (via a TSI ticket) the issue was finally identified.
All code permutations above for reading the first byte are valid and working.
The issue was that the NSData object was created when a file was fetched from a server using the Parse iOS SDK which stores the data in a temporary file with file protection key NSFileProtectionCompleteUntilFirstUserAuthentication.
The file protection key allows reading data of the NSData object only after the user unlocks the device once after reboot. Although the data is not readable before unlocking, the NSData object can be created and even the NSData.length property is accessible. However, attempting to read the data would throw an exception.
I changed the code and added a check if the protected data is available before attempting to read it with UIApplication.sharedApplication().protectedDataAvailable.
You may wonder why a file was fetched by the app before the device was even unlocked. The app was started by a remote user notification. That explains why the crash happened so rarely.
Learned 2 things:
Always check your file protection key
Apple technical support gave a super in-depth explanation and is worth the money
A Swift array is more like a C++ std::vector than a C array: it has other contents besides the array elements. You can't get a pointer to the first element using &c. You need to ask the array for a pointer to its elements, like this:
var c = [UInt8](count: 1, repeatedValue: 0)
c.withUnsafeMutableBufferPointer { buffer in
getBytes(buffer.baseAddress, length: 1)
}
For your particular case, it seems overkill to use an array, though. Why not just do:
var c: UInt8 = 0;
self.getBytes(&c, length: 1)
When my code tries to download the series from the API, it randomly crashes with this error message:
(entity: Series; id:
0x7b181002016-04-04 14:01:33.868 Postzegel Catalogus[1816:39059]
CoreData: error: Serious application error. Exception was caught
during Core Data change processing. This is usually a bug within an
observer of NSManagedObjectContextObjectsDidChangeNotification.
-[NSCFSet addObject:]: attempt to insert nil with userInfo (null) 0 (entity: Series; id:
0x7b00c450
;
2016-04-04 14:01:33.871 Postzegel Catalogus[1816:39059] *** Terminating app due
to uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSCFSet addObject:]: attempt to insert nil'
*** First throw call stack: ( 0 CoreFoundation 0x0083d494 __exceptionPreprocess + 180 1 libobjc.A.dylib
0x02551e02 objc_exception_throw + 50 2 CoreFoundation
0x0083d3bd +[NSException raise:format:] + 141 3 CoreFoundation
0x0070c959 -[__NSCFSet addObject:] + 185 4 CoreData
0x0038a010 -[NSManagedObjectContext(_NSInternalChangeProcessing)
_processPendingInsertions:withDeletions:withUpdates:] + 560 5 CoreData 0x003846da
-[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 2410 6 CoreData 0x00383d56 -[NSManagedObjectContext processPendingChanges] + 54 7
CoreData 0x003ae5e4
-[NSManagedObjectContext(_NestedContextSupport) _parentProcessSaveRequest:inContext:error:] + 116 8 CoreData 0x00433bec __82-[NSManagedObjectContext(_NestedContextSupport)
executeRequest:withContext:error:]_block_invoke + 412 9 CoreData
0x003a924c internalBlockToNSManagedObjectContextPerform + 76 10
libdispatch.dylib 0x03c8f9cd
_dispatch_client_callout + 14 11 libdispatch.dylib 0x03c76d90 _dispatch_barrier_sync_f_slow_invoke + 133 12
libdispatch.dylib 0x03c8f9cd
_dispatch_client_callout + 14 13 libdispatch.dylib 0x03c74f7c _dispatch_main_queue_callback_4CF + 910 14 CoreFoundation
0x007871be __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE + 14 15
CoreFoundation 0x00745434 __CFRunLoopRun + 2356
16 CoreFoundation 0x00744846
CFRunLoopRunSpecific + 470 17 CoreFoundation
0x0074465b CFRunLoopRunInMode + 123 18 GraphicsServices
0x07a8d664 GSEventRunModal + 192 19 GraphicsServices
0x07a8d4a1 GSEventRun + 104 20 UIKit
0x0102beb9 UIApplicationMain + 160 21 Postzegel Catalogus
0x000f63b1 main + 145 22 libdyld.dylib
0x03cb9a25 start + 1 ) (entity: Series; id: 0x7b25bc40
;
And I just don't know why? I used a private Managed Context Option so that using a . I even set the fields in my .xcdatamodeld to optional. So it shouldn't be a problem it it is nil? And it just keeps crashing randomly, not even at the same object. How can I fix this?
I included my code in the hope that might help you. I have removed my API key, so you won't be able to try it out. If you have any other comment on my code please tell me, I'm new to Core Data and Alamofire so I have the tenancy to make 'spaghetti code'.
Thanks in advance
import Foundation
import CoreData
import Alamofire
import SwiftyJSON
//CoreData Init
let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext: NSManagedObjectContext = appDelegate.managedObjectContext
let queue = dispatch_queue_create("com.GJ-Computers.Postzegel-Catalogus.responseJSON-Manager", DISPATCH_QUEUE_CONCURRENT)
//Colnect API
let LANG: String = NSLocale.preferredLanguages()[0].substringToIndex(NSLocale.preferredLanguages()[0].startIndex.advancedBy(2))
let DATE = NSCalendar.currentCalendar().component([.Day, .Month, .Year], fromDate: NSDate())
let API_KEY: String = "----" //Private API KEY
let CAT_STAMPS: String = ("cat/stamps/")
var BASE_URL: String{
return ("http://api.colnect.net/" + LANG + "/api/" + API_KEY + "/")
}
//Ghetto Delegate
var didGetCountires: Bool = false
var didGetYears: Bool = false
var didGetSeries: Bool = false
//MARK: - First Time setup Database
func setupDatabase(){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){
getYears() //Download Years per country from database
}
}
//get Series
func getSeries(){
//Retrieve Countries from Coredata
let countryFetchRequest = NSFetchRequest(entityName: "Countries")
var results: [Countries]?
do {
results = try managedContext.executeFetchRequest(countryFetchRequest) as? [Countries]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
let resultCount = results!.count
var completedRequestCount: Int = 0
var requestedRequests = 0
for result in results!{
let countryID = result.countryID
Alamofire.request(.GET, (BASE_URL + "series/"+CAT_STAMPS+"producer/\(countryID)")).responseJSON(queue: queue, completionHandler:{ response in
if let json = response.result.value{
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = managedContext
let rawData = JSON(json)
for data in rawData {
//Setup let
let seriesID = Int(data.1.array![0].string!)
let seriesName = data.1.array![1].string
let itemCount = Int(data.1.array![2].string!)
if seriesID != 0 && itemCount != 0 && seriesName != nil{
privateMOC.performBlock{
let series = NSEntityDescription.insertNewObjectForEntityForName("Series", inManagedObjectContext: managedContext) as! Series
series.countryID = countryID
series.seriesID = seriesID
series.seriesName = seriesName
series.itemCount = itemCount
print(completedRequestCount)
do {
try privateMOC.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
}else{
print("ERROR")
}
}
}
completedRequestCount += 1
print(completedRequestCount)
})
requestedRequests += 1
if(requestedRequests == resultCount){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){
while(true){
if(completedRequestCount == resultCount){
didGetSeries = true
sleep(3)
print("DEBUG - Series Done")
break
}
}
}
}
}
}
Actually, the reason, using private MOC solved your problem is because you are performing the operation in the background thread, and when you do coredata operation in the background thread you have to ensure that
Managed object contexts are bound to the thread (queue) that they are
associated with upon initialization
Managed objects retrieved from a context are bound to the same queue
that the context is bound to
FYI not because of ("I made a private MOC, but when you make one you have to consistently add it in the code otherwise it won't work. I forgot to replace managedContext with privateMOC")
Solved it Myself. I made a private MOC, but when you make one you have to consistently add it in the code otherwise it won't work. I forgot to replace managedContext with privateMOC. My bad
let series = NSEntityDescription.insertNewObjectForEntityForName("Series", inManagedObjectContext: managedContext) as! Series
should be
let series = NSEntityDescription.insertNewObjectForEntityForName("Series", inManagedObjectContext: privateMOC) as! Series
I have two operations fetching a value. I only care about the sum both values. And I don't care about the value at all if it takes too long.
So I thought it would be an easy task for GCD using groups. Unfortunately, the below code only works fine on iOS 9. Each time I have no matching calls of dispatch_group_enter()/dispatch_group_leave() I get a crash.
The documentation states clearly that I have to match both call. But when I use a timeout on dispatch_group_wait(), it is impossible to have the same amount of leave calls as enter calls; that is the whole point of specifying a timeout.
So is this a known bug in iOS 8? Am I doing something wrong? Is there another solution to my initial problem that works on iOS 8 as well?
EDIT: Actually we can boil it down to this:
var sync_group: dispatch_group_t = dispatch_group_create();
dispatch_group_enter(sync_group);
let maxWait = dispatch_time(DISPATCH_TIME_NOW, Int64(60 * NSEC_PER_SEC))
let result = dispatch_group_wait(sync_group, maxWait)
sync_group = dispatch_group_create();
Works as expected on iOS 9 but does crash on iOS 8 on the last line because the old dispatch_group_t instance can not be release. Any easy workarounds?
EDIT 2: Turns out it is broken on iOS 9.0 too. It only works as as it should in iOS 9.1+.
Original Code:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
/* We want this method to block until the network code succeeded. */
let sync_group: dispatch_group_t = dispatch_group_create();
/* Start async operation 1. */
dispatch_group_enter(sync_group);
self.someAsyncOperation1({(value: Int, finalValue: Bool) in
if (finalValue) {
valOp1 = value
dispatch_group_leave(sync_group);
}
})
/* Start async operation 2. */
dispatch_group_enter(sync_group);
self.someAsyncOperation2({(value: Int, finalValue: Bool) in
if (finalValue) {
valOp2 = value
dispatch_group_leave(sync_group)
}
})
/* Block current thread until all leaves were called. If it takes more then 60 sec we don't care and let go. */
let maxWait = dispatch_time(DISPATCH_TIME_NOW, Int64(60 * NSEC_PER_SEC))
let result = dispatch_group_wait(sync_group, maxWait)
if (result > 0) {
/* This will result in a crash when we leave the scope: SIGTRAP in dispatch_semaphore_dispose */
return
}
dispatch_async(dispatch_get_main_queue(), {
let newValue = valOp1 + valOp2
self.lastKnownNotificationCombinedCounter = newValue
success(newValue)
})
})
The actual crash loops like that:
Exception Type: SIGTRAP
Exception Codes: #0 at 0x3958a2a4
Thread 2 Crashed:
0 libdispatch.dylib 0x3958a2a4 _dispatch_semaphore_dispose$VARIANT$mp + 48
1 libdispatch.dylib 0x3958b491 _dispatch_dispose$VARIANT$mp + 30
2 libdispatch.dylib 0x3957ea8f -[OS_dispatch_object _xref_dispose] + 44
3 myApp 0x00176a24 block_destroy_helper67 + 354
4 myApp 0x00176ab8 0x2e000 + 1346232
5 myApp 0x00178334 0x2e000 + 1352500
6 libsystem_blocks.dylib 0x395d3adb _Block_release + 216
7 Foundation 0x2c4143b9 -[NSBlockOperation dealloc] + 58
8 libobjc.A.dylib 0x39036d57 objc_object::sidetable_release(bool) + 164
9 libobjc.A.dylib 0x390371a9 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 402
10 libdispatch.dylib 0x39589423 _dispatch_root_queue_drain + 1176
11 libdispatch.dylib 0x3958a1fb _dispatch_worker_thread3 + 104
12 libsystem_pthread.dylib 0x396fae25 _pthread_wqthread + 666
13 libsystem_pthread.dylib 0x396fab78 start_wqthread + 6
I came up with this workaround:
private let MAX_TRIES = 20;
func dispatch_group_wait_ios8Safe(group: dispatch_group_t, _ timeout: dispatch_time_t) -> Int {
if #available(iOS 9, *) {
/* Just forward the call. */
return dispatch_group_wait(group, timeout)
} else {
/* Forward the call to original function and store result. */
let firstResult = dispatch_group_wait(group, timeout)
var result = firstResult, tries = 0
while(result > 0 && tries < MAX_TRIES) {
dispatch_group_leave(group)
result = dispatch_group_wait(group, DISPATCH_TIME_NOW)
tries += 1
}
/* Return original result. */
return firstResult
}
}
So until someone comes up with a better solution I stick with this.
When I start my app, I perform an API call to see whether there's new data available. The data is stored in my local Realm database, and some of it is displayed in the initial table view controller.
Once the API call is finished, I check if some conditions are met that require me to delete a bunch of the previous data from the database and then create new objects. However, when I delete the old data, my app crashes with the following exception:
2015-08-06 11:56:32.057 MSUapp[19754:172864] *** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
*** First throw call stack:
(
0 CoreFoundation 0x000000010660cc65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001083bdbb7 objc_exception_throw + 45
2 Realm 0x0000000105b78e95 _ZL17RLMVerifyAttachedP13RLMObjectBase + 85
3 Realm 0x0000000105b7878d _ZL10RLMGetLinkP13RLMObjectBasemP8NSString + 29
4 Realm 0x0000000105b7c23e ___ZL17RLMAccessorGetterP11RLMProperty15RLMAccessorCodeP8NSString_block_invoke_12 + 46
5 MSUapp 0x0000000105764867 _TFFC6MSUapp29FavoriteLeaguesViewController18generateLeagueListFS0_FT_T_U_FTCS_6LeagueS1__Sb + 39
6 MSUapp 0x00000001057648eb _TTRXFo_oC6MSUapp6LeagueoS0__dSb_XFo_iS0_iS0__dSb_ + 27
7 libswiftCore.dylib 0x0000000108674ae2 _TFSs14_insertionSortUSs21MutableCollectionType_USs13GeneratorType__Ss22BidirectionalIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible____FTRQ_GVSs5RangeQQ_5Index_RFTQQQ_9Generator7ElementS7__Sb_T_ + 1570
8 libswiftCore.dylib 0x0000000108676682 _TFSs14_introSortImplUSs21MutableCollectionType_USs13GeneratorType__Ss21RandomAccessIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible_Ss16SignedNumberType_S3_____FTRQ_GVSs5RangeQQ_5Index_RFTQQQ_9Generator7ElementS8__SbSi_T_ + 1250
9 libswiftCore.dylib 0x0000000108676172 _TFSs10_introSortUSs21MutableCollectionType_USs13GeneratorType__Ss21RandomAccessIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible_Ss16SignedNumberType_S3_____FTRQ_GVSs5RangeQQ_5Index_FTQQQ_9Generator7ElementS8__Sb_T_ + 1058
10 libswiftCore.dylib 0x00000001085ec947 _TFSs4sortUSs21MutableCollectionType_USs13GeneratorType__Ss21RandomAccessIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible_Ss16SignedNumberType_S3_____FTRQ_FTQQQ_9Generator7ElementS6__Sb_T_ + 471
11 libswiftCore.dylib 0x00000001086a8d9e _TPA__TFFSa4sortU__FRGSaQ__FFTQ_Q__SbT_U_FRGVSs26UnsafeMutableBufferPointerQ__T_ + 222
12 libswiftCore.dylib 0x00000001086a8e18 _TPA__TTRG0_R_XFo_lGVSs26UnsafeMutableBufferPointerq___dT__XFo_lGS_q___iT__42 + 56
13 libswiftCore.dylib 0x00000001085f7fda _TFSa30withUnsafeMutableBufferPointerU__fRGSaQ__U__FFRGVSs26UnsafeMutableBufferPointerQd___Q_Q_ + 522
14 libswiftCore.dylib 0x00000001085f7db4 _TFSa4sortU__fRGSaQ__FFTQ_Q__SbT_ + 132
15 MSUapp 0x0000000105761709 _TFC6MSUapp29FavoriteLeaguesViewController18generateLeagueListfS0_FT_T_ + 1097
16 MSUapp 0x000000010576354b _TFC6MSUapp29FavoriteLeaguesViewController27numberOfSectionsInTableViewfS0_FCSo11UITableViewSi + 59
17 MSUapp 0x00000001057635fa _TToFC6MSUapp29FavoriteLeaguesViewController27numberOfSectionsInTableViewfS0_FCSo11UITableViewSi + 58
18 UIKit 0x000000010737cac3 -[UITableViewRowData _updateNumSections] + 84
19 UIKit 0x000000010737d4b4 -[UITableViewRowData invalidateAllSections] + 69
20 UIKit 0x00000001071c873b -[UITableView _updateRowData] + 217
21 UIKit 0x00000001071de2b7 -[UITableView noteNumberOfRowsChanged] + 112
22 UIKit 0x00000001071dd9f5 -[UITableView reloadData] + 1355
23 MSUapp 0x00000001057647c6 _TFFC6MSUapp29FavoriteLeaguesViewController11viewDidLoadFS0_FT_T_U_FTO10RealmSwift12NotificationCS1_5Realm_T_ + 166
24 RealmSwift 0x0000000105f37210 _TFF10RealmSwift41rlmNotificationBlockFromNotificationBlockFFT12notificationOS_12Notification5realmCS_5Realm_T_bTSSCSo8RLMRealm_T_U_FTSSS2__T_ + 224
25 RealmSwift 0x0000000105f372af _TTRXFo_oSSoCSo8RLMRealm_dT__XFdCb_dCSo8NSStringdS__dT__ + 111
26 Realm 0x0000000105c0645a -[RLMRealm sendNotifications:] + 986
27 Realm 0x0000000105c068e6 -[RLMRealm commitWriteTransaction] + 262
28 Realm 0x0000000105c06a48 -[RLMRealm transactionWithBlock:] + 120
29 RealmSwift 0x0000000105f34250 _TFC10RealmSwift5Realm5writefS0_FFT_T_T_ + 176
30 MSUapp 0x00000001056d46db _TZFC6MSUapp14DatabaseHelper23removeForSportAndSeasonfMS0_FTCS_5Sport6seasonSS_T_ + 603
31 MSUapp 0x0000000105710d22 _TFFFC6MSUapp11AppDelegate14loadRemoteDataFS0_FT_T_U_FGSaCS_5Sport_T_U_FGSaCS_6League_T_ + 866
32 MSUapp 0x0000000105710dc7 _TTRXFo_oGSaC6MSUapp6League__dT__XFo_iGSaS0___iT__ + 23
33 MSUapp 0x00000001057103d1 _TPA__TTRXFo_oGSaC6MSUapp6League__dT__XFo_iGSaS0___iT__ + 81
34 MSUapp 0x000000010575de90 _TTRXFo_iGSaC6MSUapp6League__iT__XFo_oGSaS0___dT__ + 32
35 MSUapp 0x000000010575ddeb _TFZFC6MSUapp9APIHelper11loadLeaguesFMS0_FTSi18shouldWriteToRealmSb10completionGSqFGSaCS_6League_T___T_U_FCSo6NSDataT_ + 2763
36 MSUapp 0x00000001056f4a0e _TTSf2n_n_n_n_n_d_i_n_n_n___TFFC6MSUapp14JSONDataSource18loadRemoteJsonDataFS0_FTSSCS_19GETParameterBuilderFCSo6NSDataT__T_U_FTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqS2__GSqCSo7NSError__T_ + 2302
37 MSUapp 0x00000001056f2d59 _TPA__TTSf2n_n_n_n_n_d_i_n_n_n___TFFC6MSUapp14JSONDataSource18loadRemoteJsonDataFS0_FTSSCS_19GETParameterBuilderFCSo6NSDataT__T_U_FTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqS2__GSqCSo7NSError__T_ + 249
38 Alamofire 0x00000001059e7599 _TTRXFo_oCSo12NSURLRequestoGSqCSo17NSHTTPURLResponse_oGSqCSo6NSData_oGSqCSo7NSError__dT__XFo_oS_oGSqS0__iGSqS1__oGSqS2___dT__ + 25
39 Alamofire 0x00000001059e7461 _TFFFC9Alamofire7Request8responseFDS0_US_18ResponseSerializer___FT5queueGSqCSo8NSObject_18responseSerializerQ_17completionHandlerFTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqQ0__GSqCSo7NSError__T__DS0_U_FT_T_U_FT_T_ + 737
40 Alamofire 0x00000001059e690e _TPA__TFFFC9Alamofire7Request8responseFDS0_US_18ResponseSerializer___FT5queueGSqCSo8NSObject_18responseSerializerQ_17completionHandlerFTCSo12NSURLRequestGSqCSo17NSHTTPURLResponse_GSqQ0__GSqCSo7NSError__T__DS0_U_FT_T_U_FT_T_ + 206
41 Alamofire 0x00000001059a89d7 _TTRXFo__dT__XFdCb__dT__ + 39
42 libdispatch.dylib 0x000000010938b186 _dispatch_call_block_and_release + 12
43 libdispatch.dylib 0x00000001093aa614 _dispatch_client_callout + 8
44 libdispatch.dylib 0x0000000109392a1c _dispatch_main_queue_callback_4CF + 1664
45 CoreFoundation 0x00000001065741f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
46 CoreFoundation 0x0000000106535dcb __CFRunLoopRun + 2043
47 CoreFoundation 0x0000000106535366 CFRunLoopRunSpecific + 470
48 GraphicsServices 0x000000010cc17a3e GSEventRunModal + 161
49 UIKit 0x00000001070f08c0 UIApplicationMain + 1282
50 MSUapp 0x000000010570f857 main + 135
51 libdyld.dylib 0x00000001093df145 start + 1
52 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
This call stack lets me assume, that it's because of my write access in FavoriteLeaguesViewController's generateLeagueList method. The following is its body:
var favorites = FavoritesHelper.sharedInstance.favoriteLeagues
favorites.sort { $0.sport < $1.sport }
for favorite in favorites {
// Add to array, which we can later use for cellForRowAtIndexPath
}
favorites is of the type [League], where League is a Realm Object. I'd assume the exception occurs because I'm accessing properties of the League objects, which have been deleted from the Realm database in the mean time (because the API call that has been started in the AppDelegate is now finished).
My question then is: How can I prevent this from happening? How can I make sure that there's no more writing/reading-access to any of the League objects prior to deleting them?
You can check if an object has been deleted from the Realm by calling object.invalidated -- if it returns true, then it has been deleted or the Realm has manually invalidated.
I got a really nice way to catch a RLMException within Swift.
Currently Swift doesn't show where a RLMException happened.
In Realm/RLMUtil.mm:266, there is the definition for RLMException.
If you change the code to generate a swift error,
Xcode now can show you where the exception occurred.
Now it is a part of Swift.
// Realm/RLMUtil.mm:266
static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
// add some code to generate a swift error. E.g. division-by-zero.
int a = 0;
if (reason == nil) {
a = 1;
}
NSLog(#"calculating 1 / %d = %f", a, 1 / a);
... remainder of the original code...
}
I've just place breakpoint inside method:
// Realm/RLMUtil.mm:193
static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
// ...
}
And on left panel you can check stack trace, here you can find where error throws.
The issue was in my FavoritesHelper class. It had both a favoriteLeagueIDs and favoriteLeagues property. I always set both of them and used the IDs for internal usage and the other property for whenever I want some data from these leagues.
This meant, that all favorite leagues were constantly referenced by the favoriteLeagues property (of the type [League]), thus crashing the app when I wanted to fetch them after they're invalidated.
What I've done to fix this, was to change the property favoriteLeagues to a computed property as follows:
var favoriteLeagues: [League] {
get {
var leagues = [League]()
for id in favoriteLeagueIDs {
if let league = realm.objectForPrimaryKey(League.self, key: id) {
leagues.append(league)
}
}
return leagues
}
}
Now the leagues are no longer referenced and just get loaded from the database when I need to read them. Invalidated or deleted objects don't get loaded because of the if let statement (the Realm.objectForPrimaryKey(:key:) method returns nil in such a case).
I was unable to place breakpoints within the Realm framework itself as others suggested, but instead placed an exception breakpoint on my entire project:
This allowed me to catch a proper stack trace when the exception was thrown, and find my bug.
you can calling isInvalidated or invalidated to judge the object is invalid (YES) or not (NO).
you can also write a a custom method to define what is real invalidate
look for the document ,we will see a property:
/**
Indicates if the object can no longer be accessed because it is now invalid.
An object can no longer be accessed if the object has been deleted from the Realm that manages it, or
if `invalidate` is called on that Realm.
*/
#property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
the invalidated is readonly but what does isInvalidated mean?
it is equals to - (BOOL)invalidated { return invalidated; }
it means that you can write a a custom method to define what is real invalidate you want.
Try to create the element on realm instead of add
So:
try! realm.write {
realm.add(...)
}
Replace with:
try! realm.write {
realm.create(...)
}
And then after the delete operation realm should work as expected!
In my experience, if you trying to use target object (which you wanna delete) after delete, application will crash.
If you wanna trigger some code blocks after removing realm object, just trying to trigger that block right before the object removing in the memory. Trying to usage that object after removed from memory, will make some problem and will crash the app.
For example:
try! realm.write {
print("deleted word: \(targetObject.word)")
realm.delete(targetObject)
// targetObject was removed, so don't try to access it otherwise you gonna got the 'nil' value instead of object.
}
after spent a day, i figure out with the remove DispatchQueue.main.async in my realm.delete() function and finally it worked.
DispatchQueue.main.async {
realm.delete()
}
to
realm.delete()
In my case I was deleting data from 2 tables at once.. one with a foreign to the other.
let itemToDelete = counters[indexPath.row]
let realm = try! Realm()
try! realm.write {
realm.delete(itemToDelete)
let predicate = NSPredicate(format: "counterid = \(c.id)")
let children = realm.objects(Details.self).filter(predicate)
realm.delete(children)
}
But the problem was that I was trying to delete the children of the item that does not exist anymore. Switching the order, solved it!
let itemToDelete = counters[indexPath.row]
let realm = try! Realm()
try! realm.write {
let predicate = NSPredicate(format: "counterid = \(c.id)")
let children = realm.objects(Details.self).filter(predicate)
realm.delete(children)
realm.delete(itemToDelete) //this should be deleted after
}
Hope this helps someone else!
Here are some of the reasons based on my past experiences.
Overriding NSObject's isEqual(_ object: Any?), then returning identifier which was just deleted by Realm.
How did I fix it?
Check if object is invalidated if so return false otherwise proceed with using your identifier
Example:
extension YourModel {
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? YourModel,
!self.isInvalidated else {
return false
}
return object.id == self.id
}
}
Using Diffing tools
For those using diffing tools like RxDataSources, IGListKit etc.. you have to perform the same check as mentioned above before trying to access the indentifier.
Here is a work round if you are using RxDataSources that prevent the crash caused by adopting to IdentifiableType protocol.
Check if the Realm object was invalidated. If so, just return a random unique value, like so:
var identity: String {
return isInvalidated ? "deleted-object-\(UUID().uuidString)" : objectId
}
Source: - https://github.com/RxSwiftCommunity/RxDataSources/issues/172#issuecomment-427675816
What you can do is observe the Results<> object returned from your initial Realm query (the one used to populate your list/table view) and update that list in the observer callback method whenever there is a change in the database.
Just make sure that you use the same Realm instance to delete/modify the object as the one used for the initial query.
EDIT:
Some code examples:
let realm = Realm()
let results = realm.objects(Favourite.self)
let notificationToken = results.observe { [weak self] (changes) in
guard let tableView = self?.tableView else { return }
tableView.reloadData()
}
// Somewhere else
try? realm.write {
if let favourite = results.first {
realm.delete(favourite)
}
}
// OR
if let objectRealm = results.first?.realm {
try? objectRealm.write {
objectRealm.delete(results.first!)
}
}
// Don't forget to stop observing in deinit{}
// notificationToken.invalidate()
You can also use the #ObservedRealmObject or #ObservedResults property wrapper, you implicitly open a realm and retrieve the specified objects or results. You can then pass those objects to a view further down the hierarchy.
And use the .onDelete(perform: ) method. In example: .onDelete(perform: $dogs.remove) see below:
(SwiftUI)
struct DogsView: View {
#ObservedResults(Dog.self) var dogs
/// The button to be displayed on the top left.
var leadingBarButton: AnyView?
var body: some View {
NavigationView {
VStack {
// The list shows the dogs in the realm.
// The ``#ObservedResults`` above implicitly opens a realm and retrieves
// all the Dog objects. We can then pass those objects to views further down the
// hierarchy.
List {
ForEach(dogs) { dog in
DogRow(dog: dog)
}.onDelete(perform: $dogs.remove) **//<- Deletion of the Object**
}.listStyle(GroupedListStyle())
.navigationBarTitle("Dogs", displayMode: .large)
.navigationBarBackButtonHidden(true)
.navigationBarItems(
leading: self.leadingBarButton,
// Edit button on the right to enable rearranging items
trailing: EditButton())
}.padding()
}
}
}
Souce: MongoDB - CRUD Swift SDK