Proper Placement of dispatchGroup to reloadData - ios

I have a tableview function that is pulling data from a database to render cells. I want to accomplish the goal of not reloading my tableview so much. I learned that dispatch groups would be the way to go beause I don't want to return to the completion block that reloads the tableView until all the data has been pulled however when I use the dispatchGroup it never reaches the completion it just stops. The placement of my variables may be in the wrong place but i just can't really see where I should put it. I have been moving it to different places and still nothing.
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
UserService.show(forUID: followingId.key, completion: { (user) in
PostService.showFollowingEvent(for: followingId.key, completion: { (event) in
self.attendingEvents = event
var friend = Friend(friendName: (user?.username)!, events: self.attendingEvents, imageUrl: (user?.profilePic)!)
self.friends.append(friend)
})
})
}
this loop should return to the completon block in viewWillAppear following the execution of this if statement
if self.friends.count == following.count{
group.leave()
let result = group.wait(timeout: .now() + 0.01)
//will return this when done
completion(nil)
}
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
Any help is greatly appreciated

You want to place the group.leave() inside of the PostService.showFollowingEvent callback.
Now you call enter following.count-times, but you call leave only once. For the group to continue you have to leave the group as many times as you entered it:
for followingId in following {
group.enter()
UserService.show(forUID: followingId.key, completion: { (user) in
PostService.showFollowingEvent(for: followingId.key, completion: { (event) in
self.attendingEvents = event
var friend = Friend(friendName: (user?.username)!, events: self.attendingEvents, imageUrl: (user?.profilePic)!)
self.friends.append(friend)
// leave here
group.leave()
})
})
}
Moreover, I would not recommend using group.wait since you are facing a possible deadlock. If any of the callbacks that are supposed to call group.leave are happening on the same thread as group.wait was called, they will never get called and you will end up with the frozen thread. Instead, use group.notify:
group.notify(queue: DispatchQueue.main) {
if self.friends.count == following.count {
completion(nil)
}
}
This will allow the execution on the main thread, but once all the tasks are finished, it will execute the provided callback closure.

Related

Infinite scrolling UITableView with numbers

I need to create infinite table with numbers, that is, when scrolling, new cells with numbers should be created.
I create APICaller with counter, pagination, arrays and while loop.
Also I create UITableView with func scrollViewDidScroll(_ scrollView: UIScrollView) which append new values in a table.
My ViewController with UITableView
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate {
private let apiCaller = APICaller()
private let tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.register(UITableViewCell.self,
forCellReuseIdentifier: "cell")
return tableView
}()
private var data = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.dataSource = self
tableView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
apiCaller.fetchData(pagination: false, completion: { [weak self] result in
switch result {
case.success(let data):
self?.data.append(contentsOf: data)
DispatchQueue.main.async {
self?.tableView.reloadData()
}
case.failure(_):
break
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = String(describing: data[indexPath.row])
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let position = scrollView.contentOffset.y
if position > (tableView.contentSize.height-100-scrollView.frame.size.height) {
guard !apiCaller.isPaginating else { return }
apiCaller.fetchData(pagination: true) { [weak self] result in
switch result {
case .success(let moreData):
self?.data.append(contentsOf: moreData)
DispatchQueue.main.async {
self?.tableView.reloadData()
}
case .failure(_):
break
}
}
}
}
}
In this case of APICaller I have only 100 cells, which corresponds to the loop constraint (but if i remove break from the while loop, nothing appears)
class APICaller {
private var counter = 0
var isPaginating = false
func fetchData(pagination: Bool = false, completion: #escaping (Result<[Int], Error>) -> Void) {
if pagination {
isPaginating = true
}
DispatchQueue.global().asyncAfter(deadline: .now() + (pagination ? 3 : 2), execute: {
var newData: [Int] = [0]
var originalData: [Int] = [0]
while true {
self.counter += 1
originalData.append(self.counter)
if self.counter == 100 {
break
}
}
completion(.success(pagination ? newData : originalData))
if pagination {
self.isPaginating = false
}
})
}
}
So, how can i get a table with infinite numbers?
The basic idea is right. When you scroll to a point where more rows are needed, fetch them. But doing this in the UIScrollViewDelegate is an expensive place to do that. I.e., that method is called for every pixel of movement and will result in many redundant calls.
Personally, I would advise moving this logic to the appropriate table view methods. For example, at a bare minimum, you might do it in the UITableViewDataSource method (i.e., if you are handling a row more than n rows from the end of your data set, fetch more data). E.g.,
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count + 1 // NB: one extra for the final “busy” cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.row + 50) >= data.count { // scrolled within 50 rows of end
fetch()
}
if indexPath.row >= data.count { // if at last row, show spinner
return tableView.dequeueReusableCell(withIdentifier: "busy", for: indexPath)
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = String(describing: data[indexPath.row])
return cell
}
}
A few things to note there:
I'm reporting one more row than I have thus far. This is for my “busy” cell (a cell with a spinning UIActivityIndicatorView). That way, if the user ever scrolls faster than the network response can handle, we at least show the user a spinner to let them know that we are fetching more data.
Thus, the cellForRowAt checks the row, and shows the “busy cell” if necessary.
And in this case, when I'm within 50 items of the end, I will initiate a fetch of the next batch of data.
Even better than the above, I would also marry the UITableViewDataSource implementation with a UITableViewDataSourcePrefetching. Thus, set the tableView.prefetchDataSource and then implement prefetchRowsAt:
extension ViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
guard
let maxIndexPath = indexPaths.max(by: { $0.row < $1.row }), // get the last row, and
maxIndexPath.row >= data.count // see if it exceeds what we have already fetched
else { return }
fetch(pagination: true)
}
}
By the way, a few notes on fetch:
func fetch(pagination: Bool = false) {
apiCaller.fetchData(pagination: pagination) { [weak self] result in
guard
let self = self,
case .success(let values) = result
else { return }
let oldCount = self.data.count
self.data.append(contentsOf: values)
let indexPaths = (oldCount ..< self.data.count).map { IndexPath(row: $0, section: 0) }
self.tableView.insertRows(at: indexPaths, with: .automatic)
}
}
Note, I would advise not “reloading” the whole table view, but rather just “inserting” the appropriate rows. I've also moved the “am I busy” logic into the APICaller, where it belongs:
class APICaller {
private var counter = 0
private var isInProgress = false
func fetchData(pagination: Bool = false, completion: #escaping (Result<[Int], Error>) -> Void) {
guard !isInProgress else {
completion(.failure(APIError.busy))
return
}
isInProgress = true
DispatchQueue.main.asyncAfter(deadline: .now() + (pagination ? 3 : 2)) { [weak self] in
guard let self = self else { return }
let oldCounter = self.counter
self.counter += 100
self.isInProgress = false
let values = Array(oldCounter ..< self.counter)
completion(.success(values))
}
}
}
extension APICaller {
enum APIError: Error {
case busy
}
}
I not only simplified the APICaller, but also made it thread-safe (by moving all state mutation and callbacks on the the main queue). If you start some asynchronous task on a background queue, dispatch the updates and callback to the main queue. But do not mutate objects from background threads (or if you must, add some synchronization logic).

Swift - Stop TableView Reloading After Dismissing A ViewController

I have a tableview that populates after a fetch request completes. Each row contains a link that opens in a SFSafariViewController. Once the user taps the Done button in the SF Safari VC, the tableview reloads and calls the fetch request again. I don't want this to happen as the fetch request can sometimes take upwards of 15 sec and I'm afraid its doubling my API call count.
Code below - is there anything I can do to prevent the tableview reloading once the Safari VC is dismissed? I tried using a boolean to track it but it simply gets changed again when viewDidLoad /appear gets called. Should I call my fetch request outside of viewdidload/appear somehow?
import SafariServices
import UIKit
class ArticlesTVC: UITableViewController, SFSafariViewControllerDelegate {
var articles = [Article]()
let cellId = "articleCell"
var refresh: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
registerCell()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
refresh = true
if refresh == true {
DispatchQueue.main.async {
self.fetchArticles()
self.refresh = false
}
}
}
func registerCell() { tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId) }
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return articles.count }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let article = articles[indexPath.row]
cell.textLabel?.text = article.title
cell.detailTextLabel?.text = article.description
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let article = articles[indexPath.row]
loadArticle(articleURL: article.url!)
}
func loadArticle(articleURL: String) {
if let url = URL(string: articleURL) {
let vc = SFSafariViewController(url: url)
vc.delegate = self
present(vc, animated: true)
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated: true)
}
func fetchArticles() {
let baseURL = "url removed for privacy"
guard let url = URL(string: "\(baseURL)") else {
print("url failed")
return
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error)
return
}
if let safeData = data {
self.parseJSON(data: safeData)
}
}.resume()
}
func parseJSON(data: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(ArticleEnvelope.self, from: data)
let newsArticles = decodedData.news
for item in newsArticles {
let title = item.title
let description = item.description
let url = item.url
let image = item.image
let published = item.published
let article = Article(title: title, description: description, url: url, image: image, published: published)
DispatchQueue.main.async {
self.articles.append(article)
self.tableView.reloadData()
}
}
print("articles loaded successfully")
} catch {
print("Error decoding: \(error)")
}
}
Of course. You are 90% of the way there. Your viewWillAppear(_:) method checks a boolean flag refresh, and only fetches the data and reloads the table if refresh == true. However, it explicitly sets refresh = true. Your viewWillAppear(_:) function gets called every time you dismiss your other view controller and re-display the table view controller.
Move the line refresh = true into viewDidLoad(). Problem solved. (viewDidLoad() is only called once when the view controller is first created, not each time it is uncovered by dismissing/popping a view controller that is covering it.)
Edit:
Note that in this code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
refresh = true
if refresh == true {
DispatchQueue.main.async { //This bit is not needed
self.fetchArticles()
self.refresh = false
}
}
}
The call to DispatchQueue.main.async is not needed, and will make fetching data slightly slower. viewWillAppear(_:) is already always called on the main thread.
Rewrite it like this:
override func viewDidLoad() {
super.viewDidLoad()
registerCell()
refresh = true //Moved from viewWillAppear
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
//Remove refresh = true from this function.
if refresh == true {
//No need to use DispatchQueue.main.async here
self.fetchArticles()
self.refresh = false
}
}

swift: photos from firebase not showing in table view

I already looked at similar questions and problems and tried what they suggested but none seemed to work and i cant seem to find why nothing is showing in the view. The following is my code.
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var postsTableView: UITableView!
var posts = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
postsTableView.estimatedRowHeight = 521
postsTableView.rowHeight = UITableView.automaticDimension
postsTableView.delegate = self
postsTableView.dataSource = self as UITableViewDataSource
loadData()
}
func loadData() {
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
if let postsDictionary = snapshot.value as? [String: AnyObject]{
for post in postsDictionary {
self.posts.add(post.value)
}
self.postsTableView.reloadData()
}
})
}
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PostTableViewCell
let post = self.posts[indexPath.row] as! [String: AnyObject]
if let imageName = post["posts"] as? String {
let imageRef = Storage.storage().reference().child("posts/\(String(describing: imageName))")
imageRef.getData(maxSize: 15 * 1024 * 1024, completion: { (data, error) -> Void in
if error == nil {
//successful
let image = UIImage(data: data!) //create an image with data sent from database
cell.postImageView.image = image
cell.postImageView.alpha = 0
UIView.animate(withDuration: 0.4, animations: {
cell.postImageView.alpha = 1
})
} else {
//error
print("Error downloading the image: \(String(describing: error?.localizedDescription))")
}
tableView.reloadData()
})
}
return cell
}
}
The screen just shows empty cells of the table view.
This is the console when the app loads.
Also excuse me if I didnt format my question correctly.
As Prettygeek mentioned, first of all, check if the completion of getData is being called. If not, try to retain imageRef outside of tableView(cellForRowAt:) scope (just for debugging).
Check on which thread getData calls its completion, most likely it's different from the main thread, and you shouldn't make calls to UIKit from the thread other than main. DispatchQueue.main.async will get handy there.

UITableView pagination, willDisplay loading infinitely

I'm trying to implement a chat feature in my app which will mimic iMessages' pull to load more messages. My API sends 20 messages in each call along with pageIndex and other values to keep track of pages and messages.
I'm implementing pagination using TableView willDisplay and pull to refresh features.
I'm not able to add correct logic to load more messages in willDisplay and it's going into infinite loop. Can anyone point me to right direction by looking at below code?
import UIKit
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
#IBOutlet weak var messagesTable: UITableView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var messages: Messages!
var messageArray = [Message]()
// Pagination
var isLoading = false
var pageSize = 10
var totalPages: Int!
var currentPage: Int!
// Pull To Refresh
let refreshControl = UIRefreshControl()
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
activityIndicator.startAnimating()
fetchMessages(page: 1, completed: {
self.totalPages = self.messages.pageCount
self.currentPage = self.messages.currentPage
// Sort message by ID so that latest message appear at the bottom.
self.messageArray = self.messages.messages!.sorted(by: {$0.id! < $1.id!})
self.messagesTable.reloadData()
// Scroll to the bottom of table
self.messagesTable.scrollToBottom(animated: false)
self.activityIndicator.stopAnimating()
})
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messageArray.count
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if !self.isLoading && indexPath.row == 0 {
self.isLoading = true
fetchMessages(page: self.currentPage, completed: {
if self.currentPage == 0 {
self.messageArray.removeAll()
}
self.messageArray.append(contentsOf: self.messages!.messages!)
self.messageArray = self.messageArray.sorted(by: {$0.id! < $1.id!})
self.messagesTable.reloadData()
// Scroll to the top
self.messagesTable.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
self.currentPage = self.currentPage + 1
})
self.isLoading = false
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as? MessageCell else {
fatalError("Can't find cell")
}
return cell
}
private func fetchMessages(page: Int, completed: #escaping () -> ()){
guard let url = URL(string: "http://example.com/....") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error in fetching data..........")
print(error!.localizedDescription)
}
guard let data = data else { return }
if let str = String(data: data, encoding: .utf8) { print(str) }
do {
let resultData = try JSONDecoder().decode(messagesStruct.self, from: data)
DispatchQueue.main.async {
print("DispatchQueue.main.async")
self.messages = resultData.data!
completed()
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
//Pull to refresh
#objc func refresh(_ refreshControl: UIRefreshControl) {
fetchMessages(completed: {
self.messagesTable.reloadData()
})
refreshControl.endRefreshing()
}
}
willDisplayCell is not the safe place to check if tableView is actually scrolled to bottom rather use scrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.isLoading && (messagesTable.contentOffset.y >= (messagesTable.contentSize.height - messagesTable.frame.size.height)) {
self.isLoading = true
fetchMessages(page: self.currentPage, completed: {[weak self]
guard let strongSelf = self else {
return
}
strongSelf.isLoading = false
if strongSelf.currentPage == 0 {
strongSelf.messageArray.removeAll()
}
strongSelf.messageArray.append(contentsOf: strongSelf.messages!.messages!)
strongSelf.messageArray = strongSelf.messageArray.sorted(by: {$0.id! < $1.id!})
strongSelf.messagesTable.reloadData()
strongSelf.currentPage = strongSelf.currentPage + 1
})
}
}
Hope it helps

Get Error "Index out of range" When I frequently push/back viewcontroller in real device - Swift

I have a problem about my tableView.
When I frequently push/back between ChatListViewController and detailViewcontroller, I will crash and get error in ListViewController.
But I don't know where my data make it crash.
I guess Whether I use GCD to make it happen or not?
Have any advice to avoid it?
Thanks.
Crash Log:
fatal error: Index out of range
Model:
class ChatroomList:Model {
var all:[Chatroom] {
var rooms:[Chatroom] = [Chatroom]()
self.chatrooms.forEach({ (id,chatroom) in
if showType.contains(chatroom.type) {
rooms.append(chatroom)
}
})
return rooms
}
}
ViewController:
import RxCocoa
import RxSwift
import Alamofire
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let chatrooms:ChatroomList = ChatroomList()
var list:[Chatroom] = [Chatroom]()
var subscribe:Disposable?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
subscribe = rooms.notifySubject.subscribe({ json in
self.loadContents()
})
self.loadContents()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
subscribe?.dispose()
}
func loadContents() {
var idList:[String] = []
self.list.removeAll()
self.list = chatrooms.all
guard self.list.isEmpty == false else {
return
}
DispatchQueue.global().async() {
self.list = self.list.filter { (chatroom) -> Bool in
if chatroom.id.isEmpty {
return true
}
if idList.contains(chatroom.id) {
return false
}
idList.append(chatroom.id)
return true
}
self.list.sort(by: { (a,b) in
if a.message.datetime.isEmpty {
return false
}
return a.message.datetime > b.message.datetime
})
DispatchQueue.main.async() {
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if list[indexPath.row].type == .city {
let cell: ChatroomCityTableViewCell = ChatroomCityTableViewCell(style: .default, reuseIdentifier: nil)
cell.loadByCityChatroom(chatroom: list[indexPath.row], cityId: list[indexPath.row].cityId)
return cell
}else{
let cell: ChatroomTableViewCell = ChatroomTableViewCell(style: .default, reuseIdentifier: nil)
cell.loadByChatroom(chatroom: list[indexPath.row])
return cell
}
}
Enable Zombie Objects in your scheme.
Select your Application Scheme -> Run -> Diagnostics -> Check Zombie Objects.
Run your app now. This would give you more precise information about your crash.
Hope this helps :)
You can find a lot of information on the internet regarding referencing self inside a closure and its implications. Without going into that detail, I would simply recommend to use [weak self] where ever you are referencing self inside a closure. Something like
closure { [weak self] _ in
self?.whatever()
}

Resources