[Swift]Associated Objects로 Delegate에서 Closure로 바꾸기
Associated Objects이란
Associated Objects
는 런타임시 사용자 속성이나 메소드들을 서브클래스를 만들지 않고 추가하거나 제거할 수 있습니다.
Objective-C 2.0 런타임의 기능으로 강력하지만 불안정한 요소를 가지고 있으며, 혹자는 악마와의 거래라고도 합니다.
runtime.h
에는 Associated Objects 관련 함수가 정의되어 있습니다.
func objc_setAssociatedObject(object: AnyObject!, key: UnsafePointer<Void>, value: AnyObject!, policy: objc_AssociationPolicy)
func objc_getAssociatedObject(object: AnyObject!, key: UnsafePointer<Void>) -> AnyObject!
func objc_removeAssociatedObjects(object: AnyObject!)
objc_setAssociatedObject는 인자로 넘기는 객체에 사용자 객체를 추가하며, 키 값은 메모리 값으로 사용합니다. 또한, 객체의 메모리 관리는 AssociationPolicy에 따라 관리합니다.
objc_getAssociatedObject는 추가된 객체를 접근할 때 사용합니다. objc_removeAssociatedObjects 추가된 객체를 제거할 때 사용합니다.
AssociationPolicy는 다음과 같습니다.
OBJC_ASSOCIATION_ASSIGN // 추가된 객체와 약한 참조
OBJC_ASSOCIATION_RETAIN_NONATOMIC // 추가된 객체와 강력 참조 및 nonatomatic으로 설정
OBJC_ASSOCIATION_COPY_NONATOMIC // 추가된 객체를 복사 및 nonatomatic으로 설정
OBJC_ASSOCIATION_RETAIN // 추가된 객체와 강력 참조 및 atomatic으로 설정
OBJC_ASSOCIATION_COPY // 추가된 객체를 복사 및 atomatic으로 설정
Delegate를 Closure로!
UIAlertView는 Delegate 방식을 취합니다. 하지만 하나의 ViewController에서 다양한 UIAlertView를 띄워야 한다면, Delegate 방식은 복잡해질 수 밖에 없습니다. 그러면 Delegate 방식을 Closure 방식으로 바꿔서 사용하도록 합니다.
메모리 값으로 사용할 키와, Closure를 가질 클래스를 선언합니다.
public typealias AVDidDismissClosure = (UIAlertView, Int) -> Void
private var associatedEventHandle: UInt8 = 0 // Closure를 관리할 객체의 메모리 주소
private final class AlertViewClosureWrapper {
private var didDismiss: AVDidDismissClosure?
}
이제 UIAlertViewDelegate로 확장된 UIAlertView를 선언합니다. 그리고 ClosureWapper 객체를 만들어 UIAlertView에 추가하고, Delegate를 Self로 정의하여 Delegate로 호출되면 ClosureWapper에 지정된 Closure를 호출합니다.
extension UIAlertView: UIAlertViewDelegate {
// UIAlertView에 closureWapper 객체 추가 및 UIAlertView의 Delegate를 Self로 설정
private var closureWrapper:AlertViewClosureWrapper {
get {
if let wrapper = objc_getAssociatedObject(self, &associatedEventHandle) as? AlertViewClosureWrapper {
return wrapper
}
self.closureWrapper = AlertViewClosureWrapper()
return self.closureWrapper
}
set {
self.delegate = self
objc_setAssociatedObject(self, &associatedEventHandle, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
// UIAlertView의 didDismiss 메소드가 호출되면 사용할 didDismiss Closure 변수 선언
public var didDismiss: AVDidDismissClosure? {
get { return self.closureWrapper.didDismiss }
set { self.closureWrapper.didDismiss = newValue }
}
// Delegate로부터 호출되면 closureWapper의 didDismiss Closure를 호출
public func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) {
self.closureWrapper.didDismiss?(alertView, buttonIndex)
}
// 사용자 초기화를 통해 Closure를 할당함.
convenience init(title: String, message: String, cancelButtonTitle: String, dismissClosure: AVDidDismissClosure?) {
self.init()
self.title = title
self.message = message
self.addButtonWithTitle(cancelButtonTitle)
self.didDismiss = dismissClosure
}
}
다음과 같이 호출하여 사용할 수 있습니다.
let alertView = UIAlertView(title: "Title", message: "Message", cancelButtonTitle: "One") { (alertView, buttonIndex) in
println("Button Index = \(buttonIndex)")
}
alertView.show()
UIAlertView를 호출하고 버튼을 누르면 다음과 같이 순서로 출력됩니다.
init(title:message:cancelButtonTitle:dismissClosure:)
alertView(_:didDismissWithButtonIndex:)
Button Index = 0
전체코드는 여기에서 보실 수 있습니다.
참고 자료
- swift 143
- associatedobject 1
- delegate 2
- closure 20
- runtime 4
- objc_setAssociatedObject 1
- objc_getAssociatedObject 1
- objc_removeAssociatedObjects 1
- uialertview 1
- extension 12
- category 5
- subclass 3
- method 9
- function 13
- property 10
- variable 4
- class 23