Mahrukh Salman
Mahrukh Salman

Reputation: 45

App Crashes and i am unable to find the reason

i have been working on developing an application. I have implemented Crashlytics in the app as well. While testing on a device it generated a strange crash that i am not able to resolve. Here i am posting the crash log

Crashed: com.apple.main-thread
0  Business Card                  0x104c951ec specialized closure #1 in PostVC.tableView(_:cellForRowAt:) (<compiler-generated>)
1  Business Card                  0x104b22738 thunk for @escaping @callee_guaranteed (@guaranteed FIRDataSnapshot) -> () (<compiler-generated>)
2  Business Card                  0x104d39c0c __71-[FIRDatabaseQuery observeSingleEventOfType:withBlock:withCancelBlock:]_block_invoke + 479 (FIRDatabaseQuery.m:479)
3  Business Card                  0x104d39e74 __92-[FIRDatabaseQuery observeSingleEventOfType:andPreviousSiblingKeyWithBlock:withCancelBlock:]_block_invoke + 502 (FIRDatabaseQuery.m:502)
4  Business Card                  0x104d26724 __43-[FChildEventRegistration fireEvent:queue:]_block_invoke.53 + 74 (FChildEventRegistration.m:74)
5  libdispatch.dylib              0x1a752fbb0 _dispatch_call_block_and_release + 32
6  libdispatch.dylib              0x1a753100c _dispatch_client_callout + 20
7  libdispatch.dylib              0x1a753ccd8 _dispatch_main_queue_callback_4CF + 968
8  CoreFoundation                 0x1a7804e20 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
9  CoreFoundation                 0x1a77ffb7c __CFRunLoopRun + 1980
10 CoreFoundation                 0x1a77ff098 CFRunLoopRunSpecific + 480
11 GraphicsServices               0x1b1969534 GSEventRunModal + 108
12 UIKitCore                      0x1ab91f7ac UIApplicationMain + 1940
13 Business Card                  0x104afa3fc main + 18 (AppDelegate.swift:18)
14 libdyld.dylib                  0x1a767ef30 start + 4

And here is the code where it says it crashed for the PostVc.swift file.

//
//  PostVC.swift
//  Business Card
//
//  Created by Elev 8 Valley on 1/10/18.
//  Copyright © 2018 elev8valley. All rights reserved.
//

import Foundation
import UIKit
import Firebase

class PostVC: UIViewController{
    @IBOutlet weak var userImage: UIImageView!
    @IBOutlet weak var nameField: UILabel!
    @IBOutlet weak var postField: UITextView!
    @IBOutlet weak var notificationsTable: UITableView!

    var window: UIWindow?
    var postId: String?
    var notifications  = [MyNotification](){
        didSet{

        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        FirebaseConnection.Connection().notificaionsListerner = self
       // FirebaseConnection.Connection().getNotificationList()
        UINavigationBar.appearance().tintColor = UIColor.white
        //self.navigationController?.navigationBar.barTintColor = darkGrayBackgroundColor
        //self.navigationController?.navigationBar.tintColor = UIColor.white
        // Do any additional setup after loading the view.
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        userImage.layer.masksToBounds = true
        userImage.layer.cornerRadius = userImage.frame.width / 2
        userImage.layer.borderColor = UIColor.white.cgColor
        userImage.layer.borderWidth = 2


        retrieveUserProfile()
    }

    func retrieveUserProfile(){
        if let pid = self.postId{
            var path = "Posts/\(pid)"
            self.window = Popup.show(vc: self)
            let ref = Database.database().reference(withPath: path)
            ref.observeSingleEvent(of: .value, with: { (snapshot) in
                if let w = self.window{
                    DispatchQueue.main.async {
                        Popup.hide(alertWindow: w)
                    }
                }
                if snapshot.exists(){
                    if let dic = snapshot.value as? NSDictionary{
                        DispatchQueue.main.async {
                            self.postField.text = dic["postContent"] as? String
                            //self.nameField.text = dic["postCreatorName"] as? String
                            if let postCreatorId = dic["postCreatorId"] as? String{
                                var path = "Profile"
                                path = "\(path)/\(postCreatorId).png"
                                let ref = Storage.storage().reference(withPath: path)
                                downloadImage(ref: ref, imageView: self.userImage)
                                self.downloadUserDetails(id: postCreatorId)
                            }

                            var not = [MyNotification]()
                            for p in self.notifications{
                                if let pid = p.postId, let id = self.postId{
                                    if pid == id{
                                        not.append(p)
                                    }
                                }
                            }
                            self.notifications = not
                            self.notificationsTable.reloadData()
                        }
                    }
                }
                else{
                    MessageBox.showSnackbar(message: "Post deleted")
                    self.navigationController?.popToRootViewController(animated: true)
                    print("No snapshot")
                }
            })
        }
    }
    private func downloadUserDetails(id: String){
        let path = "users/\(id)"
        Database.database().reference(withPath: path).observeSingleEvent(of: .value, with: {
            (snapshot) in
            if let dic = snapshot.value as? NSDictionary{
                DispatchQueue.main.async {
                    if let name = dic["name"] as? String{
                        self.nameField.text = name
                    }
                }
            }
        })
    }


}



extension PostVC : UITableViewDelegate, UITableViewDataSource{

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = UIView()
        header.backgroundColor = UIColor.clear
        return header
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 5
    }


    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if notifications[indexPath.section].notificationType == NotificationType.Request{
            return 120
        }
        return 100
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return notifications.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let not = notifications[indexPath.section]


        var gencell = tableView.dequeueReusableCell(withIdentifier: "notificationCell", for: indexPath)

        if not.notificationType == NotificationType.Comment || not.notificationType == NotificationType.Like{
            var cell = gencell as! RequestNotificationCell

            if let name = not.fromName, !name.isEmpty{
                cell.customView?.nameLabel.text = not.fromName
                cell.customView?.descriptionLabel.text = not.message
                cell.customView?.timeLabel.text = not.time?.timeAgoDisplay()
            }
            else{
                if let id = not.from.id{
                    Database.database().reference(withPath: "users/\(id)").observeSingleEvent(of: .value, with: { (snapshot) in
                        if let dic = snapshot.value as? NSDictionary{
                            let user = CircleUser(dic: dic)
                            if let name = dic.value(forKey: "name") as? String{
                                self.notifications[indexPath.section].fromName = name
                                cell.customView?.nameLabel.text = not.fromName
                                cell.customView?.descriptionLabel.text = not.message
                                cell.customView?.timeLabel.text = not.time?.timeAgoDisplay()
                            }
                        }
                    })
                }
            }

            var path = "Profile"
            if let uid = not.from.id{
                path = "\(path)/\(uid).png"

            }
            let ref = Storage.storage().reference(withPath: path)
            let placeHolder = #imageLiteral(resourceName: "boy")
            //let task = cell.customView?.image.sd_setImage(with: ref, placeholderImage: nil)
            cell.customView?.image.sd_setImage(with: ref, placeholderImage: #imageLiteral(resourceName: "client"), completion: { (image, error, cache, ref) in
                if let image = image{

                }
            })


            cell.customView?.addBtn.isHidden = true
            cell.customView?.rejectBtn.isHidden = true
            return cell

        }

        return gencell

    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let not = self.notifications[indexPath.section]

    }
}





extension PostVC : NotificationListener{
    func notificationsRetrieved(notifications: [MyNotification]) {
        self.notifications = notifications
        if notifications.count > 0{
            //reload data
            notificationsTable.reloadData()
        }
        else{
            MessageBox.Show(message: "No Notifications", title: "Empty", view: self)
        }
    }
}

I have searched on a thread on stackoverflow that it is a memory leak but i don't know where exactly this memory leak is happening.

Here is the video on how it crashed. Crash!

Upvotes: 0

Views: 546

Answers (2)

ELanz.Embrace
ELanz.Embrace

Reputation: 21

The reason your application is crashing is because it is trying to update a UITableView cell that Apple is already deallocating. When your table renders, cellForRowAtIndexPath is called for each cell. Once the cell is returned, its memory belongs to Apple, and Apple will deallocate it automatically when it goes offscreen. If you then try to update that cell, after deallocation, you will get this type of crash.

In the video posted, it's apparent to me that there are maybe 12 cells here (just using this number for reference). In the case where there are 12 cells, thee method for TableView is called 12 times (it's called however many times your application is loading content).

This is where the problem lies: When you cover and show thee cells as you load your side menu, your method does two asynchronous activities:

  • Loading data from a database (possibly, a user's name and other info)
  • Loading an image from SDWebImage

The tableView wants a UITableViewCell as soon as it's called.

There are a few methods to solving the problem: First, what I'd suggest is rearchitecting your app to anticipate what your user needs. It seems to me that your app is loading content only when your user scrolls. Instead, it's better for your user to see content/posts almost instantly! You don't want to get one cell's content with each scroll. To do this, you might have a cache class which pre-fetches images upon startup.

The second is testing the asynchronous returns if the table cell you want to return still exists or not and evaluating the solution from there.

Finally, if you enable the zombies diagnostic setting on your scheme, it would cause your application to break in the debugger when the cell is updated. This might help you better understand what is going on here.

TL;DR Rearchitect your app to pre-cache content and images so there are no crashes AND your users have a more seamless experience.

Upvotes: 1

drekka
drekka

Reputation: 21883

The first thing to do is to find out what is leaking. To do that you can set break points and use the Debug Memory Graph to see what's in memory and what's referring to it. The memory graph will also put indicators against instances that have leaked to aid you in figuring this out.

Once you've got an idea of what's leaking, you can then look at the points in the code where it's created and referred to, to figure out why it's leaking.

Crash analytics and crash logs like the one you've posted are good for identifying when something has crashed. But things like memory leaks are often somewhere else in the code and are rarely illustrated in a crash report.

Finally it's been my experience that crashes are not usually caused by memory leaks, except where something has leaked and had some of it's internal references nilled out. Then if it's triggered by a notification or other event it can crash with an unexpected nil. Often memory leaks either just consume memory or trigger strange behaviour due to multiple objects competing to do something where only one should be running.

Upvotes: 1

Related Questions