금주의 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 에서 버튼 확장을 사용할 lazy 의 UIButton 인스턴스를 생성해 보겠습니다. 따라서 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 인스턴스가 생성됩니다.
viewDidLoad 의 MainViewController 에 있는 버튼에 선택기를 할당했음을 기억하십시오. 따라서 이 뷰 컨트롤러를 로드할 때마다 해당 선택기를 버튼에 다시 쌓을 것입니다.

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

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

좋은 웹페이지 즐겨찾기