11
private let DBItemCellIdentifier = "ItemCellIdentifier"
private let DBItemSegueIdentifier = "ItemSegueIdentifier"

class DBItemsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DBItemTableViewCellDelegate {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var previousButton: UIButton!
    @IBOutlet weak var nextButton: UIButton!
    @IBOutlet weak var categoryNameLabel: UILabel!

    private var elements = [Any]()
    private var currentItemIndex = 0
    private var isFetching = false

    private weak var currentCategory: DBCategory? {
    
        didSet {
            updateView()
        }
    }

    var categories = [DBCategory]()
    var currentCategoryIndex = 0

    //MARK: - Class Methods

    //MARK: - Initialization

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 100.0
        tableView.tableFooterView = UIView(frame: CGRectZero)
    
        setupUserAndCartButtons()
        fetchItems()
    }

    deinit {
        print("deinit")
    }

    //MARK: - Actions

    @IBAction func nextButtonTapped(sender: UIButton) {
    
        currentCategoryIndex = min(currentCategoryIndex + 1, categories.count - 1)
        fetchItems()
    }

    @IBAction func previousButtonTapped(sender: UIButton) {
   
        currentCategoryIndex = max(currentCategoryIndex - 1, 0)
        fetchItems()
    }

    //MARK: - Private

    private func fetchItems() {
       
        tableView.alpha = 0
        currentCategory = nil

        if !categories.isEmpty && !isFetching {
            let category = categories[currentCategoryIndex]
            currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
        
            if currentCategory == nil {
                SVProgressHUD.show()
            }

            isFetching = true
    
            DBNetworkClient.sharedClient().itemsForCategory(category, completionBlock: { error in
            
                defer {
                    self.isFetching = false
                    SVProgressHUD.dismiss()
                    UIAlertController.showAlertFromError(error)
                }
            
                self.currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
            })
        }
    }

    private func updateView() {
    
        let category = categories[currentCategoryIndex]
        title = category.menu.location.name
    
        categoryNameLabel.text = category.name
        previousButton.hidden = currentCategoryIndex == 0 ? true : false
        nextButton.hidden = currentCategoryIndex == categories.count - 1 ? true : false
    
        prepareElements()

        tableView.reloadData()

        UIView.animateWithDuration(0.5, animations: {
            self.tableView.alpha = 1
        })
    }

    private func prepareElements() {
    
        elements.removeAll(keepCapacity: false)
    
        if let items = currentCategory?.items {
            for item in items {
                elements.append(item)
            }
        }
    
        if let sets = currentCategory?.sets {
            for set in sets {
                elements.append(set)
            }
        }
    
        elements.sortInPlace {
        
            let left = ($0 as? DBSet)?.position ?? ($0 as? DBItem)?.position
            let right = ($1 as? DBSet)?.position ?? ($1 as? DBItem)?.position
        
            return left < right
        }
    }

    //MARK: - Overridden

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    
        let element = elements[currentItemIndex]
    
        if segue.identifier == DBItemSegueIdentifier {
            let itemViewController = segue.destinationViewController as! DBItemViewController
            itemViewController.prepareWithElement(element)
        
        }
    }

    //MARK: - UITableViewDataSource

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0 //when I change to elements.count, deinit is not called
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
        let element = elements[indexPath.row]
    
        if let item = element as? DBItem {
            cell.configureCellWithItem(item)
        } else if let set = element as? DBSet {
            cell.configureCellWithSet(set)
        }
    
        cell.delegate = self
    
        return cell
    }

    //MARK: - UITableViewDelegate

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    
        currentItemIndex = indexPath.row
        performSegueWithIdentifier(DBItemSegueIdentifier, sender: tableView.cellForRowAtIndexPath(indexPath))
    }

    //MARK: - DBItemTableViewCellDelegate

    func itemTableViewCell(cell: DBItemTableViewCell, willPresentSetGroupsViewControllerForSet set: DBSet) {
        presentSetOrderControllerWithOrder(DBSetOrder(set: set))
    }

    func itemTableViewCell(cell: DBItemTableViewCell, willPresentItemMealSizesViewControllerForItem item: DBItem) {
        presentItemOrderControllerWithOrder(DBItemOrder(item: item))
    }
}

Why my deinit is not called?

Very important info

This code calls deinit. It is working. Because number of rows is 0. But I need to have there elements.count. When I change to this, deinit is not called.

Edit

func itemsForCategory(category: DBCategory, completionBlock: DBErrorHandler) {

    let query = "locations/" + category.menu.location.identifier + "/categories/" + category.identifier
    
    GET(query, parameters: nil, success: { operation, response in
    
        if let error = NSError(response: response) {
            completionBlock(error)
        } else {
            self.coreDataAssistant.parseAndSaveItemsToPersistentStore(response as? NSDictionary, completionBlock: { error in
                completionBlock(error)
            })
        }
        
        }) { operation, error in

            let responseError = NSError(response: operation.responseObject)
            completionBlock(responseError ?? error)
    }
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358

1 Answers1

43

You are assigning self as your table view cell's delegate:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
    let element = elements[indexPath.row]

    if let item = element as? DBItem {
        cell.configureCellWithItem(item)
    } else if let set = element as? DBSet {
        cell.configureCellWithSet(set)
    }

    // HERE
    cell.delegate = self

    return cell
}

The cell's delegate property is defined as follows:

var delegate: DBItemTableViewCellDelegate?

This creates a strong reference between the cell and the delegate (your view controller). The cell is also retained by the table view. This creates a retain cycle.

You will need to change the definition of the delegate property to be weak:

weak var delegate: DBItemTableViewCellDelegate?

Edit based on comment:

Your DBItemTableViewCellDelegate definition will need to be defined as a class-only protocol

protocol DBItemTableViewCellDelegate: class { 
    ...
}
Community
  • 1
  • 1
Steve Wilford
  • 8,894
  • 5
  • 42
  • 66
  • The problem is that when I try to add weak: `weak var delegate: DBItemTableViewCellDelegate?` then I have an error: weak cannot be applied to non-class type DBItemTableViewCellDelegate – Bartłomiej Semańczyk Jul 30 '15 at 07:59
  • 2
    @SteveWilford how to debug this? It seems like leaks instrument does not catch such things – Dannie P Jun 27 '16 at 10:37
  • @DannieP Apple refers to this as "Abandoned Memory" because in some sense it is still referenced by your app, unlike a leak. Here is a link for how to discover and debug abandoned memory: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/FindingAbandonedMemory.html#//apple_ref/doc/uid/TP40004652-CH80-SW1 – Mark A. Durham Oct 11 '16 at 15:00
  • But what if my delegate is connected to storyboard will it still create a strong reference ? – Darshan Mothreja Aug 24 '17 at 08:37
  • you can have weak var delegate: DBItemTableViewCellDelegate? but your protocol should be of class type. for example it could be protocol DBItemTableViewCellDelegate : class { func didUpdateItem() } – Ashim Dahal Jan 25 '19 at 03:57