금주의 iOS 버그: View Controller가 여러 번 푸시됨

8248 단어 iosswiftmobile

소개



금주의 iOS 버그(반드시 매주는 아님)에서 흔하지 않거나 특정한 특성으로 인해 많이 문서화되지 않은 흥미로운 엣지 케이스 버그를 살펴보겠습니다.

이번 회에서는 상세 뷰 컨트롤러를 UINavigationController 의 스택으로 푸시할 때 나타나는 흥미로운 탐색 문제에 대해 설명하겠습니다.

설정



나는 앱이 모든 버튼에 대해 일관된 모양과 느낌을 가질 수 있도록 기본값으로 재사용 가능한 버튼 스타일을 만드는 것을 목표로 했습니다.

이를 달성하기 위해 이 유형의 모든 버튼에 적용하고 싶은 속성 세트로 확장UIButton했습니다.

extension UIButton {
    static let defaultStyle: UIButton = {
        let button = UIButton()
        button.layer.cornerRadius = 15.0
        button.backgroundColor = .blue
        return button
    }()
}


이 확장 기능을 사용하면 호출자는 이 구성을 사용하여 다른 버튼과 일관된 스타일을 유지하는 버튼을 쉽게 만들 수 있습니다.

여태까지는 그런대로 잘됐다. 이 버튼을 사용합시다.

예제 개요



이 예제를 간단하게 유지하기 위해 부모 뷰 컨트롤러의 버튼을 사용하여 세부 뷰 컨트롤러를 탐색 스택으로 푸시하여 탐색합니다.

우리의 예에서는 상위 뷰 컨트롤러에서 로그인 화면으로 "로그아웃"할 수도 있습니다. 이는 상위에서 세부 정보로의 탐색 흐름과 별개입니다.

화면 만들기



부모 보기 컨트롤러인 MainViewController 에서 버튼 확장을 사용할 lazyUIButton 인스턴스를 생성해 보겠습니다. 따라서 defaultStyle 에서 모든 속성을 상속해야 합니다.

class MainViewController {
    private lazy var proceedButton: UIButton = {
        let button = UIButton.defaultStyle
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
}


이제 뷰 컨트롤러의 viewDidLoad에 있는 이 버튼에 선택기를 할당해 보겠습니다.

선택기를 사용하여 비어 있는 항목DetailViewController을 만들고 탐색 스택에 푸시합니다.

@objc
private func didTapProceedButton() {
    let viewController = DetailViewController()
    navigationController?.pushViewController(viewController, animated: true)
}

override func viewDidLoad() {
    super.viewDidLoad()
    proceedButton.addTarget(self, action: #selector(didTapProceedButton), for: .touchUpInside)
}


버그



앱을 로드하고 진행 버튼을 클릭하면 상세 보기 컨트롤러로 이동합니다. 문제가 없습니다.



하지만 앱의 다른 부분으로 이동한 후 다시 MainViewController로 다시 이동하면 어떻게 될까요?

이 예에서는 일부 로그아웃 버튼을 클릭하고 내 MainViewController 에 다시 로그인하기로 결정했다고 가정합니다.
MainViewController 에 다시 로그인하면 진행 버튼을 클릭하면 자세히 보기 컨트롤러가 탐색 스택으로 두 번 푸시됩니다.

이 프로세스를 다시 반복하면 버튼이 이제 디테일 뷰 컨트롤러의 세 인스턴스를 푸시합니다. 등등.



우리는 이 동작이 MainViewController 로 돌아가는 횟수와 일치한다는 것을 관찰했습니다. 하지만 이 버그의 원인은 무엇입니까?

수정



이 문제는 UIButton 확장 프로그램의 설정으로 인해 발생했습니다.

버튼 스타일에 대한 코드에서 () 구문을 사용하여 이 함수를 호출한 다음 그 결과를 defaultStyle 속성으로 static let에 할당한다는 점에 유의하십시오.

// Incorrect approach
static let defaultStyle: UIButton = {
    let button = UIButton()
    button.layer.cornerRadius = 15.0
    button.backgroundColor = .blue
    return button
}()


이것이 왜 문제가 됩니까? 음, 한 번만 생성되므로 이 버튼 확장을 호출할 때마다 다른 곳에서 사용되는 동일한 인스턴스를 다시 가져옵니다.

이 설정을 수정해 보겠습니다.

extension UIButton {
    // Correct approach
    static func defaultStyle() -> UIButton {
        let button = UIButton()
        button.layer.cornerRadius = 15.0
        button.backgroundColor = .blue
        return button
    }
}


우리의 새로운 접근 방식에서는 해당 함수가 호출될 때마다 버튼 생성을 연기합니다. 이렇게 하면 동일한 인스턴스를 호출하는 것과는 반대로 매번 새UIButton 인스턴스가 생성됩니다.
viewDidLoadMainViewController 에 있는 버튼에 선택기를 할당했음을 기억하십시오. 따라서 이 뷰 컨트롤러를 로드할 때마다 해당 선택기를 버튼에 다시 쌓을 것입니다.

버튼의 동일한 인스턴스이므로 선택기의 각 반복을 유지합니다. 따라서 사용자가 진행 버튼을 탭할 때마다 뷰 컨트롤러를 로드할 때 할당된 선택기의 모든 인스턴스가 트리거됩니다. 따라서 한 번의 버튼 누름에 대해 DetailViewController의 여러 인스턴스가 푸시되는 이유입니다.

보시다시피 버그는 여러 단계를 거쳐 생성되며 근본 원인이 어디에 있는지 식별하는 것이 중요합니다.

좋은 웹페이지 즐겨찾기