所以我的ios应用程序有点问题。当我进入控制我的评论显示的视图控制器时,我面临内存泄漏。我正在使用IGListKit,所以任何熟悉它的人都会对这个问题有很大的帮助,但是需要帮助。这是我的newCommentsViewController,它处理从firebase中提取注释并将它们发送到数据源。
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate,CommentsSectionDelegate,CommentInputAccessoryViewDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public let addHeader = "addHeader" as ListDiffable
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
comments.removeAll()
messagesRef = Database.database().reference().child("Comments").child(eventKey)
// print(eventKey)
// print(comments.count)
let query = messagesRef?.queryOrderedByKey()
query?.observe(.value, with: { (snapshot) in
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
// print(snapshot)
allObjects.forEach({ (snapshot) in
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
let commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
self.adapter.performUpdates(animated: true)
}else{
print("user is null")
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: CommentInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let commentInputAccessoryView = CommentInputAccessoryView(frame:frame)
commentInputAccessoryView.delegate = self
return commentInputAccessoryView
}()
@objc func handleSubmit(for comment: String?){
guard let comment = comment, comment.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will clear the comment text field
self.containerView.clearCommentTextField()
}
@objc func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo {
if let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue{
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
if isKeyboardShowing{
let contentInset = UIEdgeInsetsMake(0, 0, (keyboardFrame.height), 0)
collectionView.contentInset = UIEdgeInsetsMake(0, 0, (keyboardFrame.height), 0)
collectionView.scrollIndicatorInsets = contentInset
}else {
let contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
collectionView.scrollIndicatorInsets = contentInset
}
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let item = self.collectionView.numberOfItems(inSection: self.collectionView.numberOfSections - 1)-1
let lastItemIndex = IndexPath(item: item, section: self.collectionView.numberOfSections - 1)
self.collectionView.scrollToItem(at: lastItemIndex, at: UICollectionViewScrollPosition.top, animated: true)
}
})
}
}
}
override var inputAccessoryView: UIView? {
get {
return containerView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.frame = CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height-40)
view.addSubview(collectionView)
collectionView.alwaysBounceVertical = true
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
// collectionView.register(CommentHeader.self, forCellWithReuseIdentifier: "HeaderCell")
collectionView.keyboardDismissMode = .onDrag
navigationItem.title = "Comments"
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: UIImage(named: "icons8-Back-64"), style: .plain, target: self, action: #selector(GoBack))
self.navigationItem.leftBarButtonItem = backButton
}
@objc func GoBack(){
print("BACK TAPPED")
self.dismiss(animated: true, completion: nil)
}
//look here
func CommentSectionUpdared(sectionController: CommentsSectionController){
print("like")
self.fetchComments()
self.adapter.performUpdates(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchComments()
tabBarController?.tabBar.isHidden = true
//submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// collectionView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
let items:[ListDiffable] = comments
//print("comments = \(comments)")
return items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
// if let object = object as? ListDiffable, object === addHeader {
// return CommentsHeaderSectionController()
// }
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
我使用仪器和调试器图形工具进行了一些调试,它似乎指向我的commentsSectionController
import UIKit
import IGListKit
import Foundation
import Firebase
protocol CommentsSectionDelegate: class {
func CommentSectionUpdared(sectionController: CommentsSectionController)
}
class CommentsSectionController: ListSectionController,CommentCellDelegate {
weak var delegate: CommentsSectionDelegate? = nil
var comment: CommentGrabbed?
let userProfileController = ProfileeViewController(collectionViewLayout: UICollectionViewFlowLayout())
var eventKey: String?
var dummyCell: CommentCell?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
具体这个功能在这里
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
dummyCell = CommentCell(frame: frame)
dummyCell?.comment = comment
dummyCell?.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell?.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, (estimatedSize?.height)!)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as? CommentGrabbed
}
override func didSelectItem(at index: Int){
}
func optionsButtonTapped(cell: CommentCell){
print("like")
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let replyAction = UIAlertAction(title: "Reply to Comment", style: .default, handler: { (_) in
//do something here later to facilitate reply comment functionality
print("Attempting to reply to user \(comment?.user.username) comment")
})
alertController.addAction(replyAction)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
self.onItemDeleted()
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
func onItemDeleted() {
delegate?.CommentSectionUpdared(sectionController: self)
}
func handleProfileTransition(tapGesture: UITapGestureRecognizer){
userProfileController.user = comment?.user
if Auth.auth().currentUser?.uid != comment?.uid{
self.viewController?.present(userProfileController, animated: true, completion: nil)
}else{
//do nothing
}
}
deinit {
print("CommentSectionController class removed from memory")
}
}
这是我在调试器图形工具中看到的屏幕截图,即使我离开屏幕并检查调试器工具那些块仍在那里。
所以我的问题是,有没有人看到任何我看不到的功能。我真的想修复这个内存泄漏。除此之外,当我使用手机时,这种内存泄漏似乎并不明显,但是当我使用我的模拟器时,这是一个巨大的内存泄漏....任何见解都非常感谢
答案 0 :(得分:1)
一些事情:
DatabaseReference
由ViewController拥有的查询观察者正在捕获self
(您的视图控制器)。这使得所有权圈子永远不会让对方deinit
。这称为retain cycle。将[unowned self]
或[weak self]
添加到完成功能块中,如下所示:
query?.observe(.value, with: { [weak self] (snapshot) in
// your code...
}
我在这里使用了weak
,因为query
是可选的。
NotificationCenter.default.removeObserver(self)
。我的库的无耻插件:考虑Typist来管理UIKit中的键盘,它避免了与NotificationCenter
的任何交互,并且非常容易设置和使用。
答案 1 :(得分:0)
在viewWillDisappear
方法
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
答案 2 :(得分:0)
你应该在闭包中使用弱或无主参考,如下所示
query?.observe(.value, with: { [unowned self] (snapshot) in
}
答案 3 :(得分:0)
我相信你应该用[unowned self]
(或weak
)重写这个闭包:
UserService.show(forUID: uid, completion: { [unowned self] user in }
正如@Dhiru所提到的,你应该删除所有通知观察者。