오늘 공부한 UIKit - 컨트롤러에 변수 전달하기와 다운캐스팅
한 컨트롤러에서 다른 컨트롤러로 변수를 전달하고 싶었다.
첫 번째 시도
첫 번째 뷰에서 버튼을 클릭한 횟수를 내비게이션으로 전환되는 두 번째 뷰에 띄우고 싶었다.
먼저, 스토리보드에서 내비게이션 뷰 컨트롤러, 첫 번째 뷰, 두 번째 뷰의 각각 요소들을 설정해 줬다.
첫 번째 화면 라벨 아래에 Count 버튼을 누른 만큼의 횟수가 저장되고, 그 값을 두 번째 화면 라벨에 띄우고자 했다.
우선 꼼꼼한 재은 씨의 Swift: 기본편을 참고해 코드를 작성했다.
// First View Controller
import UIKit
class FirstViewController: UIViewController {
@IBOutlet var uiTitle: UILabel!
var count: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func countUp(_ sender: Any) {
self.count += 1
print("countUp function... \(count)")
}
@IBAction func moveNextByBarButton(_ sender: Any) {
guard let SecondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC") else {
return
}
SecondVC.countText = String(self.count)
self.navigationController?.pushViewController(SecondVC, animated: true)
}
// Second View Controller
import UIKit
class SecondViewController: UIViewController {
@IBOutlet var uiSecondLabel: UILabel!
var countText: String!
override func viewDidLoad() {
super.viewDidLoad()
uiSecondLabel.text = "count: " + countText
}
}
에러 발생^^..
내 생각대로라면, moveNextByBarButton
함수에서 생성한 SecondVC
는 SecondViewController
니까 해당 객체 안에 있는 countText
에 접근해 값을 추가할 수 있을거라 생각했다. 근데 이렇게 했더니 다음과 같은 오류가 떴다.
Value of type ‘UIViewController’ has no member ‘countText’
UIViewController
에 countText
라는 멤버가 없다는 건데… 내 의도는 SecondViewController
를 선언하고, 그 안에 있는 countText
에 값을 집어넣는 것이었다.
시도한 (잘못된) 방법
나는 방법이 잘못된 줄 알고 아예 SecondViewController()
로 인스턴스를 생성했고, 이렇게 하니 @IBOutlet
으로 만들어진 uiSecondLabel이 보이지 않았다.
내 생각엔 SecondVC 라는 스토리보드 ID를 가진 컨트롤러에 커스텀 클래스를 SecondViewController로 지정해줬기 때문에 문제가 없을 거라고 생각했지만, 아울렛 변수들은 뷰 컨트롤러를 선언한다고 해서 자동적으로 생기는 친구가 아니라는 걸 알았다.
다운캐스팅
어떤 키워드로 검색해야될지 모르겠어서 별의 별 검색을 다 하는 과정에서 스택 오버플로우나 애플 디벨로퍼 포럼에서 꾸준하게 봤던 키워드를 인지하기 시작했다.
as
…
상위에 있는 부모클래스의 타입을 자식클래스의 타입으로 타입캐스팅 하는 것을 다운캐스팅
이라 하는데, Swift에서 이 역할을 하는 키워드가 as
라고 한다.
as? 와 as!의 차이
전자는 성공시 옵셔널 타입을, 실패시 nil을 반환한다. 후자는 성공시 타입 그 자체를 반환하고, 실패시 런타임 에러를 발생시킨다.
상위 클래스가 UIViewController인 SecondViewController를 정의할 때, 해당 인스턴스가 SecondViewController임을 확실히 하여 그 안의 멤버에 접근하고자 했던 내 의도에 꼭 맞는 키워드였다.
코드 수정
그래서 문제가 됐던 부분을 다음과 같이 수정해 주었다.
// First View Controller
import UIKit
class FirstViewController: UIViewController {
...
@IBAction func moveNextByBarButton(_ sender: Any) {
let SecondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC")as! SecondViewController
SecondVC.countText = String(self.count)
self.navigationController?.pushViewController(SecondVC, animated: true)
}
}
as!
로 다운캐스팅을 해주었기 때문에 SecondVC는 UIViewController가 아닌 SecondViewController 타입으로 지정될 수 있고, 그 때문에 SecondViewController에 포함돼있는 countText 변수에 접근할 수 있게 되었다.
!
는 옵셔널 값을 강제로 처리하는 것이므로, 해당 코드를 guard else
로 바꾸어 주면 다음과 같이 할 수 있다.
// First View Controller
import UIKit
class FirstViewController: UIViewController {
...
@IBAction func moveNextByBarButton(_ sender: Any) {
guard let uvc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC")as? SecondViewController else {
return
}
SecondVC.countText = String(self.count)
self.navigationController?.pushViewController(SecondVC, animated: true)
}
}
결론
-
내가 지정한 ViewController를 직접 초기화해주는 것은 인터페이스 빌더 친구들을 제대로 불러들이지 못할 수 있다는 점,
-
Swift의 타입캐스팅, 그 중
as
키워드를 사용한 다운캐스팅으로 상위 클래스의 타입을 하위 클래스 타입으로 캐스팅할 수 있다는 점을 알았다.
그 외 TIL…
is
Swift의 타입캐스팅 키워드로, 인스턴스의 타입을 확인할 때 사용한다.
class Human {
var name: String
var age: Int
init(name: String, birthYear: Int) {
self.name = name
self.age = 2021 - birthYear + 1
}
}
class Student: Human {
var major: String
init(major: String, name: String, birthYear: Int) {
self.major = major
super.init(name: name, birthYear: birthYear)
}
}
위와 같은 클래스가 있을 때, 다음과 같이 타입을 확인하는 데 사용된다.
let ekwon = Human(name: "Eunbin", birthYear: 1996)
print(ekwon is Human) // true
print(ekwon is Student) // false
strong & weak
아울렛 변수를 선언할 때 storage에 strong 또는 weak 을 선택할 수 있게 되어있다. 두 친구는 메모리 회수 정책에 차이를 보인다.
strong은 말 그대로 센 녀석이고, weak은 말 그대로 약한 녀석이라고 생각하면 될 것 같다. 전자는 다른 곳에서 참조되고 있을 경우 메모리에서 해제되지 않고, 후자는 그런 경우일지라도 메모리에서 제거가 가능하다.
불시에 메모리에서 해제될 수 있는 위험성이 있는 weak은 왜 쓸까?
반대로 생각했을 때, strong 타입끼리만 상호 참조될 경우, 단 하나도 해제되지 않고 영원히 남게 된다. 이는 메모리 누수라는 또 다른 위험성을 가지고 있기 때문에 어떠한 변수는 weak으로 선언한다고 한다.
이 과정에 대해선 ARC(Auto Referencing Counter)를 참조하면 된다.
댓글남기기