I've run this code on my mac with good wifi and my phone using wifi / data and I'm getting an extremely slow load time on the code below. It takes about 7-10 seconds for my UI to update which I do in my view controller in the second block of code where the ...'s are.
This isn't the first time I've dealt with web requests and I've never had this kind of slow behavior. It is the first time I've used Alamofire and this kind of completion handler stuff I'm doing in my downloadProfileInformation where DownloadComplete is a typealais for () -> ().
I figure maybe the problem lies there. It's not updating the UI until everything is completely downloaded I guess, but should a picture be taking 10 seconds to download?
https://blzgdapipro-a.akamaihd.net/game/unlocks/0x0250000000000BB0.png
This is an example of a link that could be grabbed from the API.
I also don't know if it's possible the issue is with the API? I've never heard of something like that but I don't know what's possible.
https://api.lootbox.eu/documentation
EDIT:
//Called with IBAction
PROFILE_URL = "\(URL_BASE)\(Info.sharedInstance.platform)/\(Info.sharedInstance.region)/\(Info.sharedInstance.battletag)/profile"
Alamofire.request(PROFILE_URL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String,Any> {
if let error = dict["error"] as? String {
print(error)
} else {
if let data = dict["data"] as? Dictionary<String,Any> {
//Assigning variables to that value
if let username = data["username"] as? String {
DispatchQueue.main.async {
self.battletagNameOutlet.text = username
print("battletag update happening")
}
}
if let level = data["level"] as? Int{
...
}
if let avatarURL = data["avatar"] as? String {
...
}
if let competitive = data["competitive"] as? Dictionary<String,Any> {
....
}
}
}
}
}
}
}
Usually the loading takes more time if the Per_Page is more than 50 for your API link. This can cause a delay when dealing with API and requests that contain images and videos.
Related
I have some code that reads data from Firebase on a custom loading screen that I only want to segue once all of the data in the collection has been read (I know beforehand that there won't be more than 10 or 15 data entries to read, and I'm checking to make sure the user has an internet connection). I have a loading animation I'd like to implement that is started by calling activityIndicatorView.startAnimating() and stopped by calling activityIndicatorView.stopAnimating(). I'm not sure where to place these or the perform segue function in relation to the data retrieval function. Any help is appreciated!
let db = Firestore.firestore()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else{
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
}
You don't need to know the progress of the read as such, just when it starts and when it is complete, so that you can start and stop your activity view.
The read starts when you call getDocuments.
The read is complete after the for loop in the getDocuments completion closure.
So:
let db = Firestore.firestore()
activityIndicatorView.startAnimating()
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else {
for doc in snapshot!.documents{
self.packageIDS.append(doc.documentID)
self.packageNames.append(doc.get("title") as! String)
self.packageIMGIDS.append(doc.get("imgID") as! String)
self.packageRadii.append(doc.get("radius") as! String)
}
}
DispatchQueue.main.async {
activityIndicatorView.stopAnimating()
}
}
As a matter of style, having multiple arrays with associate data is a bit of a code smell. Rather you should create a struct with the relevant properties and create a single array of instances of this struct.
You should also avoid force unwrapping.
struct PackageInfo {
let id: String
let name: String
let imageId: String
let radius: String
}
...
var packages:[PackageInfo] = []
...
db.collection("Packages").getDocuments{(snapshot, error) in
if error != nil{
// DB error
} else if let documents = snapshot?.documents {
self.packages = documents.compactMap { doc in
if let title = doc.get("title") as? String,
let imageId = doc.get("imgID") as? String,
let radius = doc.get("radius") as? String {
return PackageInfo(id: doc.documentID, name: title, imageId: imageId, radius: radius)
} else {
return nil
}
}
}
There is no progress reporting within a single read operation, either it's pending or it's completed.
If you want more granular reporting, you can implement pagination yourself so that you know how many items you've already read. If you want to show progress against the total, this means you will also need to track the total count yourself though.
I am developing a chatting application where I could receive a number of messages at a time which leads to app freezing. Following is my socket receiver:
func receiveNewDirectMessages() {
self.socket?.on(EventListnerKeys.message.rawValue, callback: { (arrAckData, ack) in
print_debug(arrAckData)
guard let dictMsg = arrAckData.first as? JSONDictionary else { return }
guard let data = dictMsg[ApiKey.data] as? JSONDictionary else { return }
guard let chatData = data[ApiKey.data] as? JSONDictionary else { return }
guard let messageId = chatData[ApiKey._id] as? String , let chatId = chatData[ApiKey.chatId] as? String else { return }
if MessageModel.getMessageModel(msgId: messageId) != nil { return }
let isChatScreen = self.isChatScreen
let localMsgId = "\(arc4random())\(Date().timeIntervalSince1970)"
if let senderInfo = data[ApiKey.senderInfo] as? JSONDictionary, let userId = senderInfo[ApiKey.userId] as? String, userId != User.getUserId() {
_ = AppUser.writeAppUserModelWith(userData: senderInfo)
}
let msgModel = MessageModel.saveMessageData(msgData: chatData, localMsgId: localMsgId, msgStatus: 2, seenByMe: false)
let chatModel = ChatModel.saveInboxData(localChatId: msgModel.localChatId, inboxData: chatData)
if isChatScreen {
self.emitMessageStatus(msgId: messageId, chatId: chatId, socketService: .messageStatus, status: .delivered)
self.emitMessageStatus(msgId: messageId, chatId: chatId, socketService: .messageStatus, status: .seen)
} else {
ChatModel.updateUnreadCount(localChatId: chatModel.localChatId, incrementBy: 1)
self.emitMessageStatus(msgId: messageId, chatId: chatId, socketService: .messageStatus, status: .delivered)
}
TabController.shared.updateChatBadgeCount()
})
}
What's happening above:
1. I am receiving all the undelivered messages ONE-By-ONE in this socket listener.
2. Fetching the message data
3. Saving the received sender's info to Realm DB
4. Saving the message model to realm DB
5. SAVING/UPDATING Chat Thread in realm DB
6. Emitting acknowledgement for the received message
7. Update Chat badge count on tab bar
Below is my emitter for acknowledging the message delivery.
func emitMessageStatus(msgId: String, chatId: String, socketService: SocketService, status: MessageStatusAction) {
// Create Message data packet to be sent to socket server
var msgDataPacket = [String: Any]()
msgDataPacket[ApiKey.type] = socketService.type
msgDataPacket[ApiKey.actionType] = socketService.listenerType
msgDataPacket[ApiKey.data] = [
ApiKey.messageId: msgId,
ApiKey.chatId: chatId,
ApiKey.userId: User.getUserId(),
ApiKey.statusAction: status.rawValue
]
// send the messsage data packet to socket server & wait for the acknowledgement
self.emit(with: EventListnerKeys.socketService.rawValue, msgDataPacket) { (arrAckData) in
print_debug(arrAckData)
guard let dictMsg = arrAckData.first as? JSONDictionary else { return }
if let msgData = dictMsg[ApiKey.data] as? [String: Any] {
// Update delivered Seen Status here
if let msgId = msgData[ApiKey.messageId] as? String, let actionType = msgData[ApiKey.statusAction] as? String, let msgStatusAction = MessageStatusAction(rawValue: actionType) {
switch msgStatusAction {
case .delivered:
if let deliveredTo = msgData[ApiKey.deliveredTo] as? [[String: Any]] {
_ = MessageModel.updateMsgDelivery(msgId: msgId, deliveredTo: deliveredTo)
}
case .seen:
if let seenBy = msgData[ApiKey.seenBy] as? [[String: Any]] {
_ = MessageModel.updateMsgSeen(msgId: msgId, seenBy: seenBy)
}
case .pin:
MessageModel.clearPinnedMessages(chatId: chatId)
if let pinTime = msgData[ApiKey.pinTime] as? Double {
MessageModel.updatePinnedStatus(msgId: msgId, isPinned: true, pinTime: pinTime)
}
case .unPin:
if let pinTime = msgData[ApiKey.pinTime] as? Double {
MessageModel.updatePinnedStatus(msgId: msgId, isPinned: false, pinTime: pinTime)
}
case .delete:
MessageModel.deleteMessage(msgId: msgId)
case .ackMsgStatus, .like, .unlike:
break
}
}
}
}
}
What's happening above:
Encapsulating all the related information to acknowledge the event
Update realm DB after acknowledgement delivery
Now, I'm not able to defies a perfect threading policy here. What to write in background thread and what should I write in Main thread. I tried doing it however but that leades to random crashes or packet lossses.
Can anyone please lead me forward on this topic. I will be highly grateful.
Try to use background thread for data processing/ non-UI processing.
Reduce number of updating UI times
Instead of processing 1 by 1 message, using debounce - like. You can store new messages, then update UI with n new messages. So instead of updating UI/saving data to db 100 times for 100 messages, you can do it 1 time for 100 messages. More detail: with every new message, add it to an array. Call debouncer. The Debouncer will delay a function call, and every time it's getting called it will delay the preceding call until the delay time is over. So after e.g 200ms, if there're no new message, the update func will be call (the callback func that debounce's handling). Then update ui/db with n new stored messages.
You can group message by time, like group by 1 hour. And then update with delay between each time group. You can do it when the debouncer is called -> group messages by time -> update db/ui by each group. You can use setTimeout, like update group 1, 100ms later update group 2, so the ui won't be freezed
I'm really confused.
I have function for listen socket.io emit from server side in my ViewController.
after i first time open the view controller and get the message from server all things ok.
but when i open the ViewController(using present) for second time & get the message from server this method run actually two time & as many times as i present the ViewController.
my function is:
func sendMessageStatus() {
var message_body: String!
var send_time: String!
var chat_user_id: UInt!
let socket = SocketConfig.manager.defaultSocket
socket.on("messageReceived") { (dataArray, ack) in
let response = dataArray as NSArray
for resp in response {
let resp_dic = resp as! NSDictionary
for z in resp_dic {
if z.key as! String == "time_now" {
send_time = (z.value as! String)
}
if z.key as! String == "message_body" {
message_body = z.value as? String
}
if z.key as! String == "chat_id" {
chat_user_id = z.value as? UInt
self.chat_id = z.value as? UInt
}
}
}
// 0 user_id is the value for my id(i dont have my id and none of users id is 0 so i use this for know my messages)
let message_object = Messages(user_id: 0, message: message_body, receive_time: send_time)
if var items = messages_dic[chat_user_id] {
items.append(message_object)
messages_dic[chat_user_id] = items
}
else {
messages_dic[chat_user_id] = [message_object]
}
self.chatTableview.reloadData()
self.scrollToBottom(animated: true, delay: 0.3)
}
}
and this append to my messages_dic as many times as present the ViewController.
any one can help to solve this?
thank's.
It is hard to say without seeing the code for your ViewController, but I would guess if is emitting "messageReceived" more frequently than you think. Perhaps is in is a document.ready() function or some other function. Your code here seems alright from a quick glance.
As one commenter suggested, you probably have a memory leak. Your ViewController is probably not getting released properly.
I would suggest using [weak self] in your socket closure:
socket.on("messageReceived") { [weak self] (dataArray, ack) in
Then within your socket closure, replace self with self?. For example:
self?.chatTableview.reloadData()
I found my problem.
the problem is each time that i present the ViewController
socket.on("messageReceived")
set again and surely run as many times as i present the ViewController.
so for solve the problem I use the socket handlers for check if I handle "messageReceived" (set socket.on) don't set again.
so I use this code and the problem solved.
var is_set: Bool = false
let socket = SocketConfig.manager.defaultSocket
for handler in socket.handlers {
if handler.event == "messageReceived" {
is_set = true
}
}
if !is_set { // so set that
socket.on("messageReceived") { (dataArray, ack) in
// othere codes
}
and the another way is when I dismiss the ViewController I remove all handlers by this code:
deinit() {
let socket = SocketConfig.manager.defaultSocket
socket.removeAllHandlers()
}
hope to help someone.
I am currently facing an issue that has been bothering me for days. I have tried several solutions but don't seem to be able to fix the issue. Let me illustrate what is going on.
I have a feed with posts in a UITableView. As soon as the user selects one of the cells, he is shown a detailed view of this post in a new UIViewController. In there, I set up all views and call a function loadPostDetails() in the viewDidLoad() method. As soon as the view shows up, a custom loading indicator is presented which is set to disappear (hide) when the details are loaded and then the UITableView of this UIViewController (let's call it PostController) is shown.
In my loadPostDetails()function, I make a HTTPRequest which acquires the JSON data I need. This returns three kinds of info: the post details themselves, the likes and the comments. I need to handle each of these three elements before I reload the UITableView and show it. Currently, I do it like this:
HTTP.retrievePostDetails(postID: postID, authRequired: true) { (error, postDetails) in
if(error != nil) {
self.navigationController?.popViewController(animated: true)
} else {
if let postDetails = postDetails, let postInfo = postDetails["postInfoRows"] as? [[String: Any]], let postLikesCount = postDetails["postLikesCount"] as? Int, let postLikesRows = postDetails["postLikesRows"] as? [[String: Any]], let postCommentsRows = postDetails["postCommentsRows"] as? [[String: Any]] {
DispatchQueue.main.async {
let tableFooterView = self.postTableView.tableFooterView as! tableFooterView
if let postTitle = postInfo[0]["postTitle"] as? String, let postText = postInfo[0]["postText"] as? String {
self.postTitleLabel.text = postTitle
self.postTextLabel.text = postText
}
for (index, postCommentRow) in postCommentsRows.enumerated() {
tableFooterView.postComments.append(Comment(userID: postCommentRow["userID"] as! Int, userProfilePicURL: postCommentRow["userProfilePicURL"] as! String, userDisplayName: postCommentRow["userDisplayName"] as! String, commentTimeStamp: postCommentRow["commentTimeStamp"] as! TimeInterval, commentText: postCommentRow["commentText"] as! String))
}
var likeDisplayNames = [String]()
for postLikeRow in postLikesRows {
likeDisplayNames.insert(postLikeRow["userDisplayName"] as! String, at: 0)
}
if(postLikesCount > 2) {
tableFooterView.likesLabel.text = "\(likeDisplayNames[0]), \(likeDisplayNames[1]) and \(postLikesCount - 2) others"
} else {
tableFooterView.likesLabel.text = "\(postLikesCount) likes"
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
self.screenDotsLoader.isHidden = true
self.screenDotsLoader.stopAnimating()
self.postTableView.isHidden = false
self.postTableView.reloadData()
})
}
}
}
Note: I add more text to UILabels, like the date and the profile picture of the user, but I have removed a couple of lines to make it more readible and because the extra code is irrelevant for this problem.
Now, as you might already see, I call the reload stuff 1 second later, so in 95% of the cases it works just fine (but still, it is not perfect, as it is a "hack"). In the other 5%, the layout can't figure out the right constraints, resulting in a very bad layout.
I have in the last days tried to play with DispatchGroups(), but I couldn't figure out how to do it. I am in fact trying to know when all tasks have been performed, so when all UILabels have been updated, all UIImageViews have been updated etc. Only then, I want to reload the UITableView.
I was hoping someone could point me in the right direction so I can enhance my user experience a bit more. Thank you!
DispatchGroups is used when you do a bunch of asynchronous tasks together , and need to be notified upon finish of all , but currently you don't do this as when you receive the response , all are inside the main thread which is synchronous ( serial ) which means all the stuff before reloading the table will happen before it's reload , so you're free to use dispatch after if this will make sense to your UX
This question already has an answer here:
JSON parsing swift, array has no value outside NSURLSession
(1 answer)
Closed 6 years ago.
I collected JSON data from an API and am able to initialize my array (class instance variable), but after the block, data is destroyed from array. How can I return data from the task block that can be used to initialize my array?
override func viewDidLoad() {
// var movies=[Movie]()
let requestURL: NSURL = NSURL(string: "https://yts.ag/api/v2/list_movies.json")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("File downloaded successfully.")
do {
let jsonYts = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
let jsonDataTag = jsonYts["data"]
let jsonMovie = jsonDataTag!!["movies"]!
let movies = [Movie].fromJSONArray(jsonMovie as! [JSON])
// self.moviesList = movies as! [Movie] // this is my array. I want to add data to it
for data in self.moviesList {
// print(data.title)
}
} catch {
}
}
}
task.resume()
}
Asynchronous methods don't return to the invoking function because they are not called directly in the first place. They are placed on a task queue to be called later (on a different thread!). It's the hardest thing for those new to asynchronous programming to master. Your async method must mutate a class variable. So you'll have:
class MyContainer: UIViewController {
var myInstanceData: [SomeObject] // In your case moviesList
func startAsyncRequest() { // In your case viewDidLoad()
// In your case NSMutableURLRequest
doSomethingAsynchronous(callback: { [weak self] (result) in
// in your case JSON parsing
self?.myInstanceData = parseResult(result) // share it back to the containing class
// This method returns nothing; it is not called until well
// after startAsyncRequest() has returned
})
}
}
Once the callback finishes, myInstanceData is available to all the other methods in the class. Does that make sense as a general pattern?
After that you have to learn this material:
Swift performSegueWithIdentifier not working
To get the result back into the UI from the background thread on which your callback runs.
Addendum
Taking a second look after editing your question for clarity, it seems you may already know this, but you've commented out the correct line, and if you put it back in, things just work, that is, the movies won't be destroyed but will be recorded in the class property moviesList.
// self.moviesList = movies as! [Movie] // this is my array. I want to add data to it
Why not just re-enable it? Perhaps the real problem is: Do you mean to append instead of overwrite, perhaps? Then the answer may be as simple as:
self.moviesList += movies as! [Movie]
You can just use "SwiftyJson" to handle the response from the API if it returns JSON data; if it uses XML then use "SWXMLHash". They are both pods (libraries) designed to handle the response from their respective formats.
Installation help for SwiftyJson. If you still have problems comment below.
At this point you can just import SwiftyJson and use it.
Let response be any object, and suppose your JSON data is in this format:
{"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center"
}
}
Then you can just write code like this
func response(data){
let dt = JSON(data : data) // the second data is the response from api
response = dt.object // for accessing the values as object from json format
let style = response["text"]["data"]!! as? String // style = "bold"
let n = response["text"]["name"]!! as? String // n = "text1"
let size = response["text"]["hOffset"]!! as? Int // size = 250
}
// Done !! Easy.. if you guys need help or snapshots for the same do comment..