Dispatch_Group crash on iPhone 4s/iOS 8 - ios

I've written a method using dispatch_group :
class func loadNewEvents(inContext context:NSManagedObjectContext, completion:(() -> Void)?=nil) {
DDLogDebug("Loading New Events")
let context = CoreDataManager.backgroundContext()
let events = CoreDataManager.fetch(Event.self, inContext: context)
var earliestEvent = events.sort({$0.initialTimestamp.integerValue > $1.initialTimestamp.integerValue}).first
let group = dispatch_group_create()
var loadedEvents:[Event]?
var failure = false
while (loadedEvents == nil || loadedEvents!.count != 0) && !failure {
dispatch_group_enter(group);
if let earliestTimeStamp = earliestEvent?.initialTimestamp.longLongValue {
let url = afterUrl(earliestTimeStamp)
getEvents(url: url,
success: { events in
loadedEvents = events
earliestEvent = events.last
dispatch_group_leave(group)
},
failure: {
failure = true
dispatch_group_leave(group)
}
)
} else {
break
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
}
DDLogDebug("Loaded new events")
}
It works great in the iOS9 simulator and on a iPhone5S w/ iOS9.
But on an iPhone 4S with iOS8 there is a crash at the very end of the method :
EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xdefe) in _dispatch_semaphore_dispose
Any idea what would cause this and how could I fix this ?

I finally found out what was the problem : I did not leave the group on the break.
To fix it I called dispatch_group_enter(group) within the if let since it is not needed when I'm not doing an async call.

Related

UITableView scrolling performance problem

I am currently working as a 5 month junior ios developer.
The project I'm working on is an application that shows the prices of 70 cryptocurrencies realtime with websocket connection.
we used websocket connection, UItableview, UITableViewDiffableDataSource, NSDiffableDataSourceSnapshot while developing the application.
But right now there are problems such as slowdown scrolling or not stop scroling and UI locking while scrolling in the tableview because too much data is processed at the same time.
after i check cpu performance with timer profiler I came to the conclusion that updateDataSource and updateUI functions exhausting the main thread.
func updateDataSource(model: [PairModel]) {
var snapshot = DiffableDataSourceSnapshot()
let diff = model.difference(from: snapshot.itemIdentifiers)
let currentIdentifiers = snapshot.itemIdentifiers
guard let newIdentifiers = currentIdentifiers.applying(diff) else {
return
}
snapshot.appendSections([.first])
snapshot.deleteItems(currentIdentifiers)
snapshot.appendItems(newIdentifiers)
dataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
}
func updateUI(data: SocketData) {
guard let newData = data.data else { return }
guard let current = data.data?.price else { return }
guard let closed = data.data?.lastDayClosePrice else { return }
let dailyChange = ((current - closed)/closed)*100
DispatchQueue.main.async { [self] in
if model.filter({ $0.symbol == newData.pairSymbol }).first != nil {
let index = model.enumerated().first(where: { $0.element.symbol == newData.pairSymbol})
guard let location = index?.offset else { return }
model[location].price = current
model[location].dailyPercent = dailyChange
if calculateLastSignalTime(alertDate: model[location].alertDate) > 0 {
//Do Nothing
} else {
model[location].alertDate = ""
model[location].alertType = ""
}
if let text = allSymbolsView.searchTextField.text {
if text != "" {
filteredModel = model.filter({ $0.name.contains(text) || $0.symbol.contains(text) })
updateDataSource(model: filteredModel)
} else {
filteredModel = model
updateDataSource(model: filteredModel)
}
}
}
delegate?.pricesChange(data: self.model)
}
}
Regards.
ALL of your code is running on the main thread. You have to wrap your entire updateUI function inside a DispatchQueue.global(qos:), and then wrap your dataSource.apply(snapshot) line inside a DispatchQueue.main.async. the dataSource.apply(snapshot) line is the only UI work you're doing in all that code you posted.

Parse : JSON text did not start with array or object and option to allow fragments not set

I have a system using Parse that has a web front end and a iOS and Android client.
Everything was working fine until we moved to HTTPS
Still everything works fine on Android and Web (Javascript) however I get the following message when I try to upload a file in iOS
JSON text did not start with array or object and option to allow fragments not set.
The code that is causing it is. It only fails if I include a picture in the upload (Search.sharedInstance.imageURL != nil)
// Send to Parse
if PFUser.currentUser() != nil {
sentReport["user"] = Search.sharedInstance.currentUser
}
sentReport["reportType"] = userSelectedReportType
Search.sharedInstance.reportText = reportNotesTextView.text
sentReport["reportDescription"] = Search.sharedInstance.reportText
if (reportLatCoords != nil && reportLngCoords != nil) {
sentReport["reportPosition"] = PFGeoPoint(latitude: reportLatCoords!, longitude: reportLngCoords!)
}
sentReport["search"] = Search.sharedInstance.selectedPerson
let reportImageFile = PFFile(name: "\(Search.sharedInstance.timeStamp).jpg", data: reportImage!)
if reportPhoto.image != UIImage(named: "selectPhoto") {
if Search.sharedInstance.imageURL != nil {
sentReport["reportPicture"] = reportImageFile
sentReport.setObject(Search.sharedInstance.imageURL!.absoluteString, forKey: "reportImageUri")
}
}
sentReport["loggedAt"] = date
// Handle success & failure
sentReport.saveInBackgroundWithBlock { (success, error) -> Void in
if success == true {
self.dismissViewControllerAnimated(true, completion: { () -> Void in })
} else {
if Reachability.isConnectedToNetwork() == false {
sentReport.pinInBackgroundWithBlock { (success, error) -> Void in
if success {
Search.sharedInstance.syncReportItemsWithServer()
}
}
self.displayAlert("No internet connection available", message: "But don't worry, your report will be sent automatically when you regain an internet connection.")
}
}
}
The bit I am struggling is, that I read /parse fixes this a lot of the time however I have no control over the image upload. Also it works fine in the Android SDK?
My server address is : https://lowlands.lab-cloud.net/parse
Update
ReportImage
var reportImage = UIImageJPEGRepresentation(self.reportPhoto.image!,
1.0)
if reportImage!.length < 10000000 {
reportImage = UIImageJPEGRepresentation(self.reportPhoto.image!, 0.75)
} else if (reportImage!.length >= 10000000) && (reportImage!.length <= 20000000) {
reportImage = UIImageJPEGRepresentation(self.reportPhoto.image!, 0.50)
} else if (reportImage!.length > 20000000) {
reportImage = UIImageJPEGRepresentation(self.reportPhoto.image!, 0.25)
}
The problem will be with your reportImage variable, unfortunately you didnt share it with us... this is how you store UIImages in PFFIle
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name:"image.png", data:imageData)
var userPhoto = PFObject(className:"UserPhoto")
userPhoto["imageName"] = "My trip to Hawaii!"
userPhoto["imageFile"] = imageFile
userPhoto.saveInBackground()

Swift Make For Loop wait for Asynchronous function to end

im trying to work with firebase, i made this function that is saving "Offers" to an Array and then sending this array to another viewcontroller to show them on a tableView.
i found a problem that the for loop is ending before the data is coming from the servers and the callback of firebase is coming, where u can see the line "fir (self.goodOffer...)" is the end of the firebase callback.
maybe someone know how can i make the for loop to wait for the firebase callback to end?
thx.
geocodeAddress(totalFromLocation, callback: { (location) in
if location != nil {
fromLocation = location
self.geocodeAddress(totalToLocation, callback: { (location) in
if location != nil {
toLocation = location
mainDistance = Int(self.distanceCalculate(fromLocation!, locationTwo: toLocation!))
print(mainDistance)
REF_BUSSINES_OFFERS.queryOrderedByKey().queryEqualToValue("north").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
print("in snap")
if let value = snapshot.value {
if self.isAlavator {
if let area = value["north"] {
if let data = area!["withAlavator"]! {
if let bids = data["bids"] as? Int {
for i in 1..<bids+1 {
if let bid = data["\(i)"] {
let bidFromSize = bid!["fromSize"] as! Int
let bidToSize = bid!["toSize"] as! Int
let bidFromDistance = bid!["fromDistance"] as! Int
let bidToDistance = bid!["toDistance"] as! Int
if mainDistance >= bidFromDistance && mainDistance <= bidToDistance {
if Int(totalSize) >= bidFromSize && Int(totalSize) <= bidToSize {
let price = String(bid!["price"])
let name = bid!["name"] as! String
let phoneNumber = bid!["phoneNumber"] as! String
var logoImageData: NSData!
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024, completion: { (data, error) in
if error != nil {
logoImageData = UIImagePNGRepresentation(UIImage(named: "company")!)
} else {
logoImageData = data
}
let offer = Offer(logoImage: logoImageData, name: name, price: price)
self.goodOffers.append(offer)
print("fir \(self.goodOffers[0].price)")
})
}
}
}
print(i)
}//end of for
print("out")
//self.changgeViewToTable()
}
Can't help you much because your code is such a pain to read. Use ObjectMapper to reduce the level (and the effort) to map these fields. Anyhow, you can use dispatch_group to synchronize the various async calls:
let group = dispatch_group_create()
for i in 1..<bids+1 {
// ...
dispatch_group_enter(group)
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024) {
// ...
dispatch_group_leave(group)
}
}
// Wait for all async calls to complete before proceeding
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
Each dispatch_group_enter() should be balanced with a dispatch_group_leave() when the async task is complete.

Wait for other processes

I have to wait for multiple background process before doing other, I am looking for and trying to do something with this, but I can't. Here is the code. I commented the parts to explain what I want to reach. I could do it with completion handler but this function is called for a few times, depending on number of photos.
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
//WAIT FOR THIS
dispatch_async(dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
})
})
}
}
})
})
//BEFORE DOING THIS
dispatch_async(dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
You can do this using dispatch_group methods.
However I would recommend using something like PromiseKit or similar which gives you a much nicer way of controlling dependencies of multiple async blocks.
Here is your code using dispatch_group methods:
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
// create the dispatch group
dispatch_group_t aGroup = dispatch_group_create();
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
// use dispatch_group to coordinate.
// must call before uploadPhoto as we don't know how long it will take
dispatch_group_enter(aGroup);
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
dispatch_async(aGroup, dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
// indicate block has completed (reduced group count)
dispatch_group_leave(aGroup);
})
})
}
}
})
})
// wait for all items in group to finish before running this block
dispatch_group_notify(aGroup, dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
I have to wait for multiple background process before doing other
You should really look at using NSOperationQueue for this requirement as it allows you to configure dependencies between all of the registered operations. In this way you could configure and add a number of downloads and then configure a post download action which is dependent on all of those downloads. The operation queue will deal with starting that operation when all others are complete.
The operations also give you a nice way to cancel the pending uploads / downloads if you don't need them any more.
You could alternatively look at using dispatch_groups to manage this. It's harder to ensure that what you've done is entirely correct.
The main queue is a serial queue, meaning that it executes on block at a time in first-in-first-out order. All of the dispatches in the enumerate should be done before the one at the bottom is run.
EDIT: just noticed that you async in a completion handler.
One simple thing to do is to just count down (because you know how many there are).
Just add var expectedCompletions = attachement.count before the enumerate and then count them down (expectedCompletions--) inside the block. When it gets to 0, run the code at the end.

IBOutlet nil after refreshing data

I'm using XCode 6.3.1 and I'm facing a weird problem that I can't figure it out.
I have a View Controller on my Storyboard opened with a modal segue. When the ViewController is opened it loads some data from my backend (Parse) it looks first on the cache and shows cached data (if exists) while the updated data from server is retrieved on the background. The process is the next:
Get cached data
If cached data exists then update interface
Request server data (In background)
When data arrives update interface
Everything works fine until step 4. When I try to refresh my interface suddenly half of my #IBOutlets are nil and of course app crashes.
what am I missing??
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//eventId is set when the ViewController is instantiated
if eventId != nil {
loadEvent(eventId)
}
}
func loadEvent(id: String) {
var query = Event.query()
query?.cachePolicy = Util.getCachePolicy() //First look in cache, the request network data
query?.getObjectInBackgroundWithId(id, block: { (event: PFObject?, error: NSError?) -> Void in
if error == nil {
var updatedEvent = event as! Event
self.event = updatedEvent
self.updateLayout()
//When self.updateLayout() is called with cached data
//all my IBOutlets are fine but when it's called the second time,
//with data from server half of the IBOutlets are nil
}
})
}
func updateLayout() {
if event != nil {
eventTitle.text = event.name
var paletteColor : UIColor!
var location = event.location
var locationName = location["name"] as! String
eventLocation.text = NSString(format: NSLocalizedString("event_subtitle", comment: ""), event.formattedTimes(), locationName) as String
eventDescription.text = event.abstract
if event.paletteColor != 0 {
paletteColor = Util.colorFromInt(event.paletteColor)
eventHeader.backgroundColor = paletteColor
speakersBlockTitle.textColor = paletteColor
mapButton.tintColor = paletteColor
}
if event.hasPhoto() {
self.eventPhoto.file = event.eventPhoto
self.eventPhoto.loadInBackground({ (image:UIImage?, error:NSError?) -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.eventPhoto.alpha = 1.0
})
})
} else {
self.eventPhoto.removeFromSuperview()
if paletteColor == nil {
paletteColor = eventHeader.backgroundColor
}
actionBar.backgroundColor = paletteColor
}
if event.speaker.isDataAvailable() {
var speaker = event.speaker
speakerName.text = speaker["name"] as? String
speakerInfo.text = speaker["speakerInfo"] as? String
speaker["speakerPhoto"]?.getDataInBackgroundWithBlock({ (imageData:NSData?, error:NSError?) -> Void in
if error == nil {
self.speakerPhoto.image = UIImage(data:imageData!)
self.speakerPhoto.layer.cornerRadius = self.speakerPhoto.frame.size.width/2
self.speakerPhoto.clipsToBounds = true
}
})
} else {
speakerBlock.removeFromSuperview()
}
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.eventHeader.alpha = 1.0
self.eventDescription.alpha = 1.0
self.speakerBlock.alpha = 1.0
self.mapButton.alpha = 1.0
})
}
}
This are all the nil IBOutlets:
Since the code in my comment above doesn't display correctly, here's that comment again:
Something like this:
#IBOutlet weak var myOutlet: UIButton! {
didSet {
if myOutlet == nil {
// put a breakpoint on the next line
println("myOutlet set to nil")
}
}
}

Resources