storyboard를 사용한다는 가정 하에, 일반적인 UI component들은 @IBAction
으로 action 함수를 연결하여 사용자의 상호관계 속 함수 호출을 한다.
하지만 UITableViewCell
이나 UICollectionViewCell
의 경우, 해당 UI instance를 생성하는 과정이 다르기에 cell의 내부 component들은 @IBAction
으로 함수 호출을 유도할 수 없다.
cell이 생성되는 함수는 다음과 같다.
tableView(_:cellForRowAt:)
Asks the data source for a cell to insert in a particular location of the table view.
- tableView
A table-view object requesting the cell.- indexPath
An index path locating a row in tableView.
collectionView(_:cellForItemAt:)
Asks your data source object for the cell that corresponds to the specified item in the collection view.
- collectionView
The collection view requesting this information.- indexPath
The index path that specifies the location of the item.
cell을 포함하는 tableView/collectionView와 cell의 위치를 알려주는 indexPath를 받아 cell을 생성한다. 조건에 따라 다르게 UI 및 기능을 설정하지 않았다면 동일한 UI component를 지닌 cell instance를 반복해서 생성한다.
따라서 만약 cell 내부의 component를 @IBAction
으로 연결을 했다면, 모든 cell instance가 해당 함수와 연결이 되어 있으므로, 해당 함수가 호출될 조건이 갖춰지면 cell의 개수만큼 반복해서 @IBAction
함수가 실행될 것이다.
이런 연유로 cell의 component들은 @IBAction
을 활용할 수 없다.
참고)
UIButton을 비롯해서 UIControl들은 tag로 구분한다. tag 할당값으로 action 함수에서 component를 구분하는 기준이 된다.
따라서 코드로 UI를 구성할 때 활용하는 addTarget 메서드를 이용해서 마치 @IBAction
처럼 함수 호출을 한다.
addTarget(_:action:for:)
Associates a target object and action method with the control.
- target
The target object—that is, the object whose action method is called. If you specify nil, UIKit searches the responder chain for an object that responds to the specified action message and delivers the message to that object.- action
A selector identifying the action method to be called. You may specify a selector whose signature matches any of the signatures in Listing 1. This parameter must not be nil.- controlEvents
A bitmask specifying the control-specific events for which the action method is called. Always specify at least one constant. For a list of possible constants, see UIControl.Event.
target은 action 메서드를 호출하려는 대상
action은 호출될 함수
controlEvents는 어떤 사용자의 상호작용으로 호출될 지 case를 나타낸다.
대부분의 경우, target은 self로 설정되어 action 함수가 속한 ViewController 자신임을 나타낸다.
controlEvents는 저번에 정리한 UIControl.Event를 나타낸다.
iPhone이 등장하기 전부터, Mac용 프레임워크인 Cocoa를 사용할 때 이미 Target-Action 디자인 패턴을 활용하고 있었다.
Target-Action을 비롯해서 C와 Objective-C로 작성된 legacy 메서드들과 라이브러리들이 존재하고 잘 활용되고 있었다.
이후 2007년 iPhone이 등장하고 관련 프레임워크의 이름이 Cocoa Touch Class인 것을 보며 우리는 Cocoa 프레임워크에 touch interface용으로 접목 시킨 내용이 많을 것임을 예측할 수 있었다.
이는 2014년 Swift가 등장하고 나서도 legacy로 남은 Obj-C로 작성된 방식을 활용하는 개발 방향이 아직 많이 남은 것을 보면 알 수 있다.
다시 돌아와서 addTarget
메서드의 action parameter를 작성하는 방식은 과거 Obj-C로 작성하는 방식을 활용해야 한다.
다행히 개발자가 100% 쌩으로 Obj-C 문법으로 작성하지 않아도 된다. 우리는 그저 Swift가 알아서 처리해줄 Obj-C 스타일처럼 작성하면 된다.
#selector(objcMethodName)
Like target-action, some Objective-C APIs accept method or property names as parameters, then use those names to dynamically call or access the methods or properties. In Swift, you use the #selector and #keyPath expressions to represent those method or property names as selectors or key paths, respectively.
selector는 다음과 같이 정의한다.
In Objective-C, a selector is a type that refers to the name of an Objective-C method. In Swift, Objective-C selectors are represented by the Selector structure, and you create them using the #selector expression. In Swift, you create a selector for an Objective-C method by placing the name of the method within the #selector expression.
Obj-C method 이름을 가리키는 타입. Swift에서는 #selector
로 해당 struct의 instance를 만들어 활용한다.
Objc-C method는 다음과 같이 @objc
attribute를 작성해서 알려주면 끝이다.
let button = UIButton()
button.addTarget(self, #selector(buttonTapped), .touchUpInside)
@objc buttonTapped() { }
100% Swift로 Foundation Framework를 작성하기 시작했다.
미래에는 C나 Obj-C 스타일로 작성하는 문법이 사라질 수도...?
Future of Foundation
앞에서 우린 cell이 조건에 따라 다른 UI 구성의 cell을 다양하게 활용하지 않는다면 동일 구성의 cell을 반복적으로 생성해서 활용한다고 말했다.
하지만 cell을 생성하는 것은 data의 개수만큼 하지 않는다.
네트워킹 통신으로 불러와야 할 데이터가 10,000개라고 가정해봐도 cell 10,000개를 생성하는 시간은 꽤 걸릴 것이다.
cell 개수가 20개라도 20개를 다 생성하지 않는다. 예를 들어 화면에 보일 수 있는 17개를 생성해서 보여준다. 사용자가 하단으로 스크롤링을 하면 20개까지 남은 instance 3개를 생성한다. 물론 data가 그보다 많으면 cell을 계속해서 재활용해서 data를 보여준다.
재활용될 때마다 위의 cellForRowAt
함수가 호출된다. 재사용되더라도 indexPath.row로 해당 cell의 위치에 해당하는 data를 전달하기 위해서.
그러면 여기서 의문이 들 수 있다.
cell 내부의 UI component, 예를 들어 UIButton은, cell이 재활용될 때 호출되는 cellForRowAt에서 매번 addTarget으로 action 함수를 가리키는데, 가리키는 함수가 중첩되지 않는가?
만약 중첩된다면 addTarget으로 연결한 Obj-C 메서드가 호출되는 상황에서 재활용된 수만큼 반복적으로 호출될 수 있을 것이다.
확인을 위해 다음 타입을 확인해봤다.
allTargets
Returns all target objects associated with the control.
var allTargets: Set<AnyHashable> { get }
Return Value
A set of all target objects associated with the control. The returned set may include one or more NSNull objects to indicate actions that are dispatched to the responder chain.
UIControl instance와 연결된 모든 target 객체를 collection으로 보여준다.
Set으로 정의되어 있다는 점이 중요하다. Set은 중복을 허용하지 않는다. 따라서 동일한 target을 Set에 추가해도 중첩되지 않을 것이다.
따라서 재활용되는 cell의 UI component들의 target은 계속해서 1개일 것이다.
참고
Obj-C runtime features in Swift: selector and keyPath
좋은 정보 감사합니다