1

我在解除分配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
	}

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

4

2 回答 2

0

你的这条线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.

于 2019-11-21T14:54:39.473 回答
0

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

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

感谢您的帮助@vpoltave

于 2019-11-21T16:57:01.430 回答