【问题标题】:IGListKitSections doesn't get deallocatedIGListKitSections 没有被释放
【发布时间】:2019-11-21 14:23:51
【问题描述】:

我在 IGListKit 部分释放时遇到问题。正在尝试使用 Xcode 内存图来调试问题。

我的设置是 AuthController -> AuthViewModel -> AuthSocialSectionController -> AuthSocialViewModel 和其他一些部分。 如果用户未登录,则

AuthController 会从应用程序的多个部分呈现。当我点击关闭时,AuthViewModel 和 AuthController 会被释放,但它的底层部分不会.在这种情况下,内存图没有显示任何泄漏,但 deinit 方法没有被调用。

但是当我尝试使用社交帐户授权(成功)然后查看内存图时,它会显示这些部分,不会像这样被释放:

在这种情况下,AuthViewModel 也不会被释放,但一段时间后它会释放,但它可能发生也可能不会发生。

我检查了每个闭包和委托的弱引用,但仍然没有运气。

我认为最有意义的代码:

class AuthViewController: UIViewController {
	fileprivate let collectionView: UICollectionView = UICollectionView(frame: .zero,
	                                                                    collectionViewLayout: UICollectionViewFlowLayout())
	lazy var adapter: ListAdapter
		= ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)

	fileprivate lazy var previewProxy: SJListPreviewProxy = {
		SJListPreviewProxy(adapter: adapter)
	}()

	fileprivate let viewModel: AuthViewModel

	fileprivate let disposeBag = DisposeBag()

	init(with viewModel: AuthViewModel) {
		self.viewModel = viewModel

		super.init(nibName: nil, bundle: nil)

		hidesBottomBarWhenPushed = true
		setupObservers()
	}

	private func setupObservers() {
		NotificationCenter.default.rx.notification(.SJAProfileDidAutoLogin)
			.subscribe(
				onNext: { [weak self] _ in
					self?.viewModel.didSuccessConfirmationEmail()
					self?.viewModel.recoverPasswordSuccess()
			})
			.disposed(by: disposeBag)
	}

	required init?(coder _: NSCoder) {
		fatalError("init(coder:) has not been implemented")
	}

	// MARK: - View Controller Lifecycle

	override func viewDidLoad() {
		super.viewDidLoad()
		setup()
	}

	// MARK: - Private

	@objc private func close() {
		dismiss(animated: true, completion: nil)
	}

	/// Метод настройки экрана
	private func setup() {
		if isForceTouchEnabled() {
			registerForPreviewing(with: previewProxy, sourceView: collectionView)
		}

		view.backgroundColor = AppColor.instance.gray
		title = viewModel.screenName
		let item = UIBarButtonItem(image: #imageLiteral(resourceName: "close.pdf"), style: .plain, target: self, action: #selector(AuthViewController.close))
		item.accessibilityIdentifier = "auth_close_btn"
		asViewController.navigationItem.leftBarButtonItem = item
		navigationItem.titleView = UIImageView(image: #imageLiteral(resourceName: "logo_superjob.pdf"))

		collectionViewSetup()
	}

	// Настройка collectionView
	private func collectionViewSetup() {
		collectionView.keyboardDismissMode = .onDrag
		collectionView.backgroundColor = AppColor.instance.gray
		view.addSubview(collectionView)
		adapter.collectionView = collectionView
		adapter.dataSource = self
		collectionView.snp.remakeConstraints { make in
			make.edges.equalToSuperview()
		}
	}
}

// MARK: - DataSource CollectionView

extension AuthViewController: ListAdapterDataSource {

	func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
		return viewModel.sections(for: listAdapter)
	}

	func listAdapter(_: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
		return viewModel.createListSectionController(for: object)
	}

	func emptyView(for _: ListAdapter) -> UIView? {
		return nil
	}
}

// MARK: - AuthViewModelDelegate

extension AuthViewController: AuthViewModelDelegate {
	func hideAuth(authSuccessBlock: AuthSuccessAction?) {
		dismiss(animated: true, completion: {
			authSuccessBlock?()
		})
	}

	func reload(animated: Bool, completion: ((Bool) -> Void)? = nil) {
		adapter.performUpdates(animated: animated, completion: completion)
	}

	func showErrorPopover(with item: CommonAlertPopoverController.Item,
	                      and anchors: (sourceView: UIView, sourceRect: CGRect)) {
		let popover = CommonAlertPopoverController(with: item,
		                                           preferredContentWidth: view.size.width - 32.0,
		                                           sourceView: anchors.sourceView,
		                                           sourceRect: anchors.sourceRect,
		                                           arrowDirection: .up)
		present(popover, animated: true, completion: nil)
	}
}

class AuthViewModel {

	fileprivate let assembler: AuthSectionsAssembler

	fileprivate let router: AuthRouter

	fileprivate let profileFacade: SJAProfileFacade

	fileprivate let api3ProfileFacade: API3ProfileFacade

	fileprivate let analytics: AnalyticsProtocol

	fileprivate var sections: [Section] = []

	weak var authDelegate: AuthDelegate?
	weak var vmDelegate: AuthViewModelDelegate?
  
  var authSuccessBlock: AuthSuccessAction?
  
  private lazy var socialSection: AuthSocialSectionViewModel = { [unowned self] in
		self.assembler.socialSection(delegate: self)
	}()

  init(assembler: AuthSectionsAssembler,
	     router: AuthRouter,
	     profileFacade: SJAProfileFacade,
	     api3ProfileFacade: API3ProfileFacade,
	     analytics: AnalyticsProtocol,
	     delegate: AuthDelegate? = nil,
	     purpose: Purpose) {
		self.purpose = purpose
		authDelegate = delegate
		self.assembler = assembler
		self.router = router
		self.profileFacade = profileFacade
		self.api3ProfileFacade = api3ProfileFacade
		self.analytics = analytics
		sections = displaySections()
	}
  
  private func authDisplaySections() -> [Section] {
		let sections: [Section?] = [vacancySection,
		                            authHeaderSection,
		                            socialSection,
		                            authLoginPasswordSection,
		                            signInButtonSection,
		                            switchToSignUpButtonSection,
		                            recoverPasswordSection]
		return sections.compactMap { $0 }
	}
}

class AuthSocialSectionController: SJListSectionController, SJUpdateCellsLayoutProtocol {
	fileprivate let viewModel: AuthSocialSectionViewModel

	init(viewModel: AuthSocialSectionViewModel) {
		self.viewModel = viewModel
		super.init()
		minimumInteritemSpacing = 4
		viewModel.vmDelegate = self
	}

	override func cellType(at _: Int) -> UICollectionViewCell.Type {
		return AuthSocialCell.self
	}

	override func cellInitializationType(at _: Int) -> SJListSectionCellInitializationType {
		return .code
	}

	override func configureCell(_ cell: UICollectionViewCell, at index: Int) {
		guard let itemCell = cell as? AuthSocialCell else {
			return
		}
		let item = viewModel.item(at: index)
		itemCell.imageView.image = item.image
	}

	override func separationStyle(at _: Int) -> SJCollectionViewCellSeparatorStyle {
		return .none
	}
}

extension AuthSocialSectionController {
	override func numberOfItems() -> Int {
		return viewModel.numberOfItems
	}
  
	override func didSelectItem(at index: Int) {
		viewModel.didSelectItem(at: index)
	}

}

// MARK: - AuthSocialSectionViewModelDelegate

extension AuthSocialSectionController: AuthSocialSectionViewModelDelegate {
	func sourceViewController() -> UIViewController {
		return viewController ?? UIViewController()
	}
}

protocol AuthSocialSectionDelegate: class {

	func successfullyAuthorized(type: SJASocialAuthorizationType)

	func showError(with error: Error)
}

protocol AuthSocialSectionViewModelDelegate: SJListSectionControllerOperationsProtocol, ViewControllerProtocol {
	func sourceViewController() -> UIViewController
}

class AuthSocialSectionViewModel: NSObject {
	struct Item {
		let image: UIImage
		let type: SJASocialAuthorizationType
	}

	weak var delegate: AuthSocialSectionDelegate?
	weak var vmDelegate: AuthSocialSectionViewModelDelegate?

	fileprivate var items: [Item]

	fileprivate let api3ProfileFacade: API3ProfileFacade
	fileprivate let analyticsFacade: SJAAnalyticsFacade
	fileprivate var socialButtonsDisposeBag = DisposeBag()

	init(api3ProfileFacade: API3ProfileFacade,
	     analyticsFacade: SJAAnalyticsFacade) {
		self.api3ProfileFacade = api3ProfileFacade
		self.analyticsFacade = analyticsFacade
		items = [
			Item(image: #imageLiteral(resourceName: "ok_icon.pdf"), type: .OK),
			Item(image: #imageLiteral(resourceName: "vk_icon.pdf"), type: .VK),
			Item(image: #imageLiteral(resourceName: "facebook_icon.pdf"), type: .facebook),
			Item(image: #imageLiteral(resourceName: "mail_icon.pdf"), type: .mail),
			Item(image: #imageLiteral(resourceName: "google_icon.pdf"), type: .google),
			Item(image: #imageLiteral(resourceName: "yandex_icon.pdf"), type: .yandex)
		]

		if analyticsFacade.isHHAuthAvailable() {
			items.append(Item(image: #imageLiteral(resourceName: "hh_icon"), type: .HH))
		}
	}

	// MARK: - actions

	func didSelectItem(at index: Int) {
		guard let vc = vmDelegate?.sourceViewController() else {
			return
		}

		let itemType: SJASocialAuthorizationType = items[index].type

		socialButtonsDisposeBag = DisposeBag()
		
		api3ProfileFacade.authorize(with: itemType, sourceViewController: vc)
			.subscribe(
				onNext: { [weak self] _ in
					self?.delegate?.successfullyAuthorized(type: itemType)
				},
				onError: { [weak self] error in
					if case let .detailed(errorModel)? = error as? ApplicantError {
						self?.vmDelegate?.asViewController.showError(with: errorModel.errors.first?.detail ?? "")
					} else {
						self?.vmDelegate?.asViewController.showError(with: "Неизвестная ошибка")
					}
			})
			.disposed(by: socialButtonsDisposeBag)
	}
}

// MARK: - DataSource

extension AuthSocialSectionViewModel {
	var numberOfItems: Int {
		return items.count
	}

	func item(at index: Int) -> Item {
		return items[index]
	}
}

// MARK: - ListDiffable

extension AuthSocialSectionViewModel: ListDiffable {
	func diffIdentifier() -> NSObjectProtocol {
		return ObjectIdentifier(self).hashValue as NSObjectProtocol
	}

	func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
		return object is AuthSocialSectionViewModel
	}
}

其中 assembler 负责创建everyting,例如 AuthSocialSection:

func socialSection(delegate: AuthSocialSectionDelegate?) -> AuthSocialSectionViewModel {
		let vm = AuthSocialSectionViewModel(api3ProfileFacade: api3ProfileFacade,
		                                    analyticsFacade: analyticsFacade)
		vm.delegate = delegate
		return vm
	}

如何正确调试此问题?非常感谢任何建议或帮助

【问题讨论】:

  • 您尝试过 Instruments 吗?
  • @vpoltave 对于这个问题没有,但我认为它与内存图非常相似,还是我错了?
  • 也许分析可以显示更多信息,在您的流程的每个步骤中泄漏的确切位置和内容
  • 谢谢,我试试
  • 好吧,我可以确认,我已修复它。 viewModel.vmDelegate = self 并从 IGList 上下文传递 viewController 导致了问题。也许还有其他事情......

标签: swift rx-swift iglistkit


【解决方案1】:

AuthSocialSectionController 中发现了一个问题。不知何故,通过委托从 IGList 上下文传递 viewController 会导致内存问题。当我注释掉viewModel.vmDelegate = self 时,问题就消失了。

这解释了为什么当我在未尝试授权的情况下点击关闭按钮时 AuthViewModel 会正确释放。只有当我点击授权时,才会调用 viewController 属性。

感谢@vpoltave 的帮助

【讨论】:

    【解决方案2】:

    您的AuthViewController 中的这行代码会导致泄漏吗?

    // adapter has viewController: self
    lazy var adapter: ListAdapter
            = ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
    
    fileprivate lazy var previewProxy: SJListPreviewProxy = {
        // capture self.adapter ?
        SJListPreviewProxy(adapter: adapter)
    }()
    

    我不确定,但至少你可以试试 :)


    更新

    我想知道这个惰性闭包和内部的self,它不会创建保留循环,因为lazy 初始化是@nonescaping

    【讨论】:

    • 我在这里尝试使用 [unowned self],是的,但没有帮助。我实际上发现,如果我删除 AuthSocialSectionController 中的 viewModel.vmDelegate = self,AuthViewModel 会在关闭后立即被释放,但不是它的部分
    猜你喜欢
    • 2013-05-24
    • 2021-11-20
    • 2011-06-01
    • 2019-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-27
    • 1970-01-01
    相关资源
    最近更新 更多