Friday, April 27, 2018

Instruments show “_NSContiguousstring” memory leak when scrolling UITableView

Leave a Comment

I have a UItableView which consist of 10 element. I opened Instruments to catch the memory leaks and when I scroll tableView, It started to give memory leaks. In Instruments I tried to find which causes the leaks but can't figure out, It says "_NScontiguousstring" for whole leaks.

I found some solutions for Objective-C which they check If cell is nil in "CellForRowAt" function. I don't think it is useful for Swift but I tried and as expected It doesn't work.

Memory Leak UITableView

My question is what can cause this kind of memory leak?

Controller Class;

class TableViewController: UITableViewController { let teamModel = TeamModel(uid: "adsada", name: "First Team ", idea: "idea 1", slogan: "Slogan 1", university: "dasda", image: "info", isActive: true) let teamModel2 = TeamModel(uid: "adsada", name: "Team 2", idea: "idea 2", slogan: "adasd", university: "dasda", image: "info", isActive: true) let teamModel3 = TeamModel(uid: "adsada", name: "Team 3", idea: "idea 3", slogan: "adasd", university: "dasda", image: "info", isActive: true) let teamModel4 = TeamModel(uid: "adsada", name: "Team 4", idea: "idea 4", slogan: "adasd", university: "dasda", image: "info", isActive: true) let teamModel5 = TeamModel(uid: "adsada", name: "Team 5", idea: "idea 5", slogan: "adasd", university: "dasda", image: "info", isActive: true) let teamModel6 = TeamModel(uid: "adsada", name: "Team 6", idea: "idea 6", slogan: "adasd", university: "dasda", image: "info", isActive: true) let teamModel7 = TeamModel(uid: "adsada", name: "Team 7", idea: "idea 7", slogan: "adasd", university: "dasda", image: "info", isActive: true)  var data: [TeamModel] = [] override func viewDidLoad() {     super.viewDidLoad()     tableView.register(mainTableCell.self, forCellReuseIdentifier: "mainTableCell")     data = [teamModel,teamModel2,teamModel3,teamModel4,teamModel5,teamModel6,teamModel7] } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int {     // #warning Incomplete implementation, return the number of sections     return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {     // #warning Incomplete implementation, return the number of rows     return data.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {     let cell = tableView.dequeueReusableCell(withIdentifier: "mainTableCell", for: indexPath) as! mainTableCell     let cell_data = data[indexPath.row]      cell.cell_data = cell_data      return cell } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {     return 120 } } 

Cell Class;

class mainTableCell: UITableViewCell{ var cell_data: TeamModel?{     didSet{         guard let unwrappedCell = cell_data else { return }         if let url = unwrappedCell.imageURL{             profileImage.image = UIImage(named: "info")         } else{             self.profileImage.image = UIImage(named: "info")         }         self.teamLbl.text = unwrappedCell.name        mainBackground.backgroundColor = UIColor.gray     } }  let mainBackground: UIView = {     let v = UIView()     v.layer.cornerRadius = 8     v.layer.masksToBounds = true     return v }()  var isVoteable: Bool = false  //let shadowView = ShadowView()  let profileImage: UIImageView = {     let imageView = UIImageView()     imageView.contentMode = .scaleAspectFill     imageView.clipsToBounds = true     imageView.layer.masksToBounds = true     return imageView }()  let teamLbl: UILabel = {     let label = UILabel()     label.textColor = UIColor.black     label.numberOfLines = 1     label.adjustsFontSizeToFitWidth = true     return label }()  let myVoteLbl: UILabel = {     let l = UILabel()     l.text = "Oyum: --"     return l }() let voteBtn: UIButton = {    let b = UIButton(type: .custom)     b.setImage(UIImage(named: "info"), for: .normal)      return b }() override init(style: UITableViewCellStyle, reuseIdentifier: String?){     super.init(style: style, reuseIdentifier: reuseIdentifier)      setupViews() } func setupViews() {      self.backgroundColor = UIColor.white       self.mainBackground.addSubview(profileImage)     self.mainBackground.addSubview(teamLbl)     self.mainBackground.addSubview(voteBtn)     self.mainBackground.addSubview(myVoteLbl)      //self.addSubview(shadowView)     self.addSubview(mainBackground)     self.backgroundColor = UIColor.clear      profileImage.translatesAutoresizingMaskIntoConstraints = false     teamLbl.translatesAutoresizingMaskIntoConstraints = false     voteBtn.translatesAutoresizingMaskIntoConstraints = false     myVoteLbl.translatesAutoresizingMaskIntoConstraints = false     mainBackground.translatesAutoresizingMaskIntoConstraints = false     //shadowView.translatesAutoresizingMaskIntoConstraints = false  } override func layoutSubviews() {     super.layoutSubviews()     // self.setCircularImageView()     mainBackground.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)     // shadowView.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: 0, heightConstant: 0)      profileImage.anchor(nil, left: self.mainBackground.leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 10, bottomConstant: 0, rightConstant: 0, widthConstant: 60, heightConstant: 60)     profileImage.anchorCenterYToSuperview()       teamLbl.anchor(self.mainBackground.topAnchor, left: profileImage.rightAnchor, bottom: nil, right: self.voteBtn.leftAnchor, topConstant: 20, leftConstant: 20, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 40)     myVoteLbl.anchor(nil, left: profileImage.rightAnchor, bottom: self.mainBackground.bottomAnchor, right: nil, topConstant: 0, leftConstant: 20, bottomConstant: 20, rightConstant: 0, widthConstant: 0, heightConstant: 0)      voteBtn.anchor(nil, left: nil, bottom: nil, right: self.mainBackground.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 80, heightConstant: 80)     voteBtn.anchorCenterYToSuperview() }  func setCircularImageView() {     self.profileImage.layer.cornerRadius = CGFloat(roundf(Float(self.profileImage.frame.size.width / 2.0))) } required init?(coder aDecoder: NSCoder) {     fatalError("init(coder:) has not been implemented") } } 

First SS

Second SS

EDIT: I add prepareForReuse method regarding to below answer but now cells show just white. Am I doing something wrong?

    override func prepareForReuse() {     super.prepareForReuse()      teamLbl.text = ""     myVoteLbl.text = ""     profileImage.image = nil } 

I also make cell_data variable "weak var cell_data: TeamModel? and nil it in prepareForReuse() method but still same leaks are coming.

EDIT 2: Full project;

https://github.com/emreond/TableView-Memory-Leak-Project

EDIT 3: I found that when I add If check and change attributes inside didst of cell_data, some cells views look ruined. For example when I change teamLbl text color inside cell_data. In some cells, It looks ruined.

1 Answers

Answers 1

First things first.

Inside layoutSubviews you are setting anchores. Why ? It's going to be called each time your cells will be called.

Instead of this, move

// self.setCircularImageView()     mainBackground.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)     // shadowView.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: 0, heightConstant: 0)      profileImage.anchor(nil, left: self.mainBackground.leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 10, bottomConstant: 0, rightConstant: 0, widthConstant: 60, heightConstant: 60)     profileImage.anchorCenterYToSuperview()       teamLbl.anchor(self.mainBackground.topAnchor, left: profileImage.rightAnchor, bottom: nil, right: self.voteBtn.leftAnchor, topConstant: 20, leftConstant: 20, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 40)     myVoteLbl.anchor(nil, left: profileImage.rightAnchor, bottom: self.mainBackground.bottomAnchor, right: nil, topConstant: 0, leftConstant: 20, bottomConstant: 20, rightConstant: 0, widthConstant: 0, heightConstant: 0)      voteBtn.anchor(nil, left: nil, bottom: nil, right: self.mainBackground.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 80, heightConstant: 80)     voteBtn.anchorCenterYToSuperview() 

Just bellow setupViews() call.

Next thing is that you didn't implement prepareForReuse(), you need to nil the values which u are setting in cellForRow, willDislayCell` etc.

EDIT

var cell_data: TeamModel? - Marking it as optional doesn't make it weak reference, mark it as weak var and nil it every time cell gets reused.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment