UITableView scrolling performance problem - ios

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.

Related

How to fix memory issues given by Instrument tools in Swift?

I have memory issues, especially for the error
XPC connection interrupted
The screen is freezing for a few seconds..
So, I've been learning how to use the Instruments tools and try to fix this error. However, I've been trying to find the error in my code and it's apparently not the fault of my code but maybe the libraries?
As a result of this test, I've got some warnings (the purple ones):
Memory Issues (3 leaked types):
- 1 instance of _DateStorage leaked (0x10b1eb060)
- 1 instance of OS_dispatch_data leaked (0x10b0b1ac0)
- 1 32-byte malloc block leaked (x10b1eb040)
Could you tell me how to fix these warnings, knowing there is no backtrace available? Or how could I find somewhere that could tell me to fix those?
EDIT:
Thanks to Instrument tools, I found the function that caused the problem! So, I don't know if it is really about memory or Idk but here's the function!
The accurate and useful error I get is : "Closure #1 in closure #1 in MessagesTableViewController.getLastMessages"
I found here What is a closure #1 error in swift?, the error is probably caused by forced optional types. So, I am going to try to remove those.
func getLastMessages(cell: ContactMessageTableViewCell, index: IndexPath) {
// first, we get the total number of messages in chatRoom
var numberOfMessagesInChatRoom = 0
let previousCellArray = self.tableView.visibleCells as! [ContactMessageTableViewCell]
var index1 = 0
var messages = [JSQMessage]()
var sortedMessages = [JSQMessage]()
var messagesSortedByChatRooms = [String: [JSQMessage]]()
var doesHaveMessagesCount = false
var doesHaveSortedMessagesCount = false
let firstQuery = Constants.refs.databaseChats.queryOrderedByKey()
_ = firstQuery.observe(.childAdded, with: { [weak self] snapshot in
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let text = data["text"],
let chatRoom = data["chatRoom"],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
messages.append(message)
var arrayVariable = [JSQMessage]()
// we wanna get all messages and put it in the array corresponding to the chat room key
if messagesSortedByChatRooms[chatRoom] != nil { // if there is already the chatRoom key in dictionary
if let message1 = messagesSortedByChatRooms[chatRoom] {
arrayVariable = message1
}
arrayVariable.append(message)
messagesSortedByChatRooms[chatRoom] = arrayVariable
} else { // if there isn't the chatRoom key
arrayVariable.append(message)
messagesSortedByChatRooms[chatRoom] = arrayVariable
}
}
}
DispatchQueue.main.async {
// we have to sort messages by date
for (chatRoom, messagesArray) in messagesSortedByChatRooms {
var loopIndex = 0
var lastMessage: JSQMessage?
var array = [JSQMessage]()
for message in messagesArray { // we run through the messages array
array.removeAll()
loopIndex += 1
if loopIndex != 1 {
if message.date > lastMessage!.date {
array.append(message)
messagesSortedByChatRooms[chatRoom] = array
} else {
array.append(lastMessage!)
messagesSortedByChatRooms[chatRoom] = array
}
} else {
lastMessage = message
if messagesArray.count == 1 {
array.append(message)
messagesSortedByChatRooms[chatRoom] = array
}
}
}
}
if !doesHaveMessagesCount {
//doesHaveMessagesCount = true
// we have the number of chats in database
let secondQuery = Constants.refs.databaseChats.queryOrderedByPriority()
_ = secondQuery.observe(.childAdded, with: { [ weak self] snapshot in
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let text = data["text"],
let chatRoom = data["chatRoom"],
!text.isEmpty
{
if let message = JSQMessage(senderId: id, displayName: name, text: text)
{
index1 += 1
if chatRoom != nil {
if let unwrappedSelf = self {
if unwrappedSelf.sortedChatRoomsArray.contains(chatRoom) {
sortedMessages.append(message)
for (chatRoomKey, messageArray) in messagesSortedByChatRooms {
unwrappedSelf.lastMessages[chatRoomKey] = messageArray[0]
}
}
}
}
if let unwrappedSelf = self {
if index1 == messages.count && chatRoom != unwrappedSelf.roomName {
sortedMessages.append(JSQMessage(senderId: id, displayName: name, text: "no message"))
}
}
}
}
DispatchQueue.main.async {
if let unwrappedSelf = self {
if !doesHaveSortedMessagesCount {
//doesHaveSortedMessagesCount = true
if unwrappedSelf.sortedChatRoomsArray.indices.contains(index.row) {
if unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]] != nil {
if unwrappedSelf.lastMessagesArray.count != 0 {
let currentChatRoom = unwrappedSelf.sortedChatRoomsArray[index.row]
if unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]?.text != "no message" {
if UUID(uuidString: unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]!.text) == nil {
cell.contactLastMessageLabel.text = unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]?.text
} else {
cell.contactLastMessageLabel.text = "New image"
}
} else {
cell.contactLastMessageLabel.text = ""
cell.contactLastMessageLabel.font = UIFont(name:"HelveticaNeue-Light", size: 16.0)
}
if unwrappedSelf.lastMessagesArray.indices.contains(index.row) {
if unwrappedSelf.lastMessagesArray[index.row] != unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]?.text {
if unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]?.senderId != PFUser.current()?.objectId {
if unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]?.text != "no message" {
cell.contactLastMessageLabel.font = UIFont(name:"HelveticaNeue-Bold", size: 16.0)
}
var numberOfDuplicates = 0
for cell in previousCellArray {
if cell.contactLastMessageLabel.text == unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]?.text {
numberOfDuplicates += 1
}
}
if numberOfDuplicates == 0 {
if unwrappedSelf.selectedUserObjectId != "" {
unwrappedSelf.changeCellOrder(index: index.row, selectedUserObjectId: unwrappedSelf.selectedUserObjectId, lastMessage: unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]!.text)
} else {
unwrappedSelf.changeCellOrder(index: index.row, selectedUserObjectId: "none", lastMessage: unwrappedSelf.lastMessages[unwrappedSelf.sortedChatRoomsArray[index.row]]!.text)
}
} else {
unwrappedSelf.tableView.reloadData()
}
cell.activityIndicatorView.stopAnimating()
}
} else {
cell.contactLastMessageLabel.font = UIFont(name:"HelveticaNeue-Light", size: 16.0)
}
}
}
} else {
}
}
}
}
}
})
}
}
})
}
FINL EDIT: I put a closure inside of another closure so it created an infine loop ;)

How to Handle Keyboard After Reload Table View?

After reload tableview, Inside Textfield keyboard is not showing properly .How can i fix That keyboard properly.
This is My code,
Here, I am Getting Firebase Images
let queue = OperationQueue()
queue.addOperation {
if let urlString = nammaApartmentVisitor.getprofilePhoto() {
NAFirebase().downloadImageFromServerURL(urlString: urlString,imageView: cell.image_View)
}
}
queue.waitUntilAllOperationsAreFinished()
if isActivityIndicatorRunning == false {
cell.activityIndicator.startAnimating()
} else if (isActivityIndicatorRunning == true) {
cell.activityIndicator.stopAnimating()
cell.activityIndicator.isHidden = true
}

Function in Swift will run, but will not execute "completion" in Swift

I am trying to call a function in Swift. The first time I call it, it runs as intended, but the second time the completion handler if and else statements don't run. Despite this, I still know the function runs because I had it print "RAN" every time it ran. I have no idea what I am doing wrong. Any help?
My Code:
[other code]
self.movieNum = Int(arc4random_uniform(50) + 1)
// Int movieNum is turned into String
let movieNumNSNumber = self.movieNum as NSNumber
let movieNumString: String = movieNumNSNumber.stringValue
self.checkIfMovieHasAlreadyBeenShown(movieID: movieNumString, completion: { seen in
if seen {
var i = 1
var haveAllBeenSeen = 1
while i <= 50 {
let iNSNumber = i as NSNumber
let iString: String = iNSNumber.stringValue
self.checkIfMovieHasAlreadyBeenShown(movieID: movieNumString, completion: { seen in
if seen {
print("SEEN")
}
else {
print("NOT SEEN")
haveAllBeenSeen = 0
}
})
i += 1
}
if haveAllBeenSeen == 0 {
self.swipingView()
}
else {
[other code]
}
else {
return
}
})

Swift display annotations in mapView in another thread

This code does not add annotations to mapView. I saw in one answer that mapView function is called every time addAnotation is called so where's the problem? But when I move map they show up.
func addPlacesMarkers(location:CLLocation) {
self.communication.getJsonData(self.getPubsNearByCreator(location)) { (finalData, error) -> Void in
if error != nil {
print(error)
} else {
if let row: NSArray = finalData {
for var i = 0; i < row.count; i++ {
let lat = row[i]["lat"] as! String
let lng = row[i]["lng"] as! String
let title = row[i]["name"] as! String
let id = row[i]["id"] as! String
let point = CustomizedAnotation(id: Int(id)!, name: title)
point.title = title
point.coordinate.latitude = Double(lat)!
point.coordinate.longitude = Double(lng)!
let keyExists = self.places[Int(id)!] != nil
if keyExists == false {
self.places.updateValue(point, forKey: Int(id)!)
}
}
var finalPlaces :[MKPointAnnotation] = []
for place in self.places.values {
finalPlaces.append(place)
}
self.mView.addAnnotations(finalPlaces)
self.mView.showsPointsOfInterest = false
}
}
}
}
You can't modify the UI in a thread different from the main.
You should put your UI modification code inside a dispatch_async block like this:
dispatch_async(dispatch_get_main_queue()) {
//Your code that modify the UI
self.mView.addAnnotations(finalPlaces)
}

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