본문 바로가기

iOS

Swift에서 AssociatedObject를 사용해 보아요

안녕하세요! 오늘은 AssociatedObject을 소개해 보겠습니다.
적재적소에 잘 사용한다면 강력한 기능과 더불어 꽤 멋진 코드 구조를 만들어낼 수 있습니다.

이 AssociatedObject를 이용하면 적용한 객체에 임의의 행동을 추가해줄 수 있습니다. (객체에 객체를 추가..)

코드를 보면 이해가 보다 쉬울 수 있습니다. 예를 들어, 임의의 뷰에 TapGesture를 달아주고 싶을때
someView.addTapGestureRecognizer { /* someAction() */ }
이렇게 쓸 수 있습니다. 간단하고 강력하죠?

함수명에 따르면, someView에 Tapgesture를 선언해준것 같고, 그 이후 클로저를 인수로 받은것을 확인할 수 있습니다.
UIView의 Extension으로 Tapgesture를 핸들링하는 메소드인가보다!
넵 맞습니다. 이를 사용하게 된 계기는 다음과 같습니다.

연습삼아 만들고 있는 프로젝트에서 RxSwift의 의존도를 낮춰보고 있습니다. (정확히는 RxCocoa)
도메인로직과 비지니스로직에는 적극적으로 사용하고, View 이벤트는 UIKit만을 이용하는 것입니다.

따라서 `button.rx.tap`을 비롯해서, 여러 UIKit의 딜리게이트를 클로저 블럭으로 마이그레이션 하는데에
이 AssociatedObject가 도움을 많이 주고 있습니다. 런타임에 결정되는 강력한 Objective-C 메소드입니다.

ref
https://github.com/Reflejo/LambdaKit
http://minsone.github.io/mac/ios/how-to-covert-delegate-to-closure-from-uialertview-using-associated-objects
https://nshipster.com/associated-objects/




관련해서 세가지 메소드를 이용합니다.

 

  • objc_setAssociatedObject
  • objc_getAssociatedObject
  • objc_removeAssociatedObjects

setAssociatedObject
func objc_setAssociatedObject(object: AnyObject!, key: UnsafePointer, value: AnyObject!, policy: objc_AssociationPolicy)
object - 대상 객체
key - 속성에 대한 키 값. 메모리 값으로 사용. 메모리 관리는 밑에 policy에 따라서 관리합니다.
value - 객체와 연결하려는 속성 값
policy - association Policy

getAssociatedObject
func objc_getAssociatedObjects(object: AnyObject!, key: UnsafePointer) -> AnyObject!
추가된 객체에 접근할때 사용

removeAssociatedObjects

func objc_removeAssociatedObjects(object: AnyObject!)

직관적인 해석으로는,
1 UIVIew에 setAssociatedObject를 통해, 연관된 객체인 tapGesuteRecognizer를 연결해줍니다.
2 유도한 액션이 트리거된다 -> getAssociatedObject를 통해서 연결된 클로저를 실행

NSObject를 상속하는 모든 서브클래스들에게 적용이 가능합니다.

메소드 파라미터들은 코드와 함께 알아보아요

이해가 어렵지 않습니다
중간에 `public func addTapGestureRecognizer(_ handler: (() -> Void)?)`을 외부에 열어놓고 있는것 보이시나요?
외부에서 이 함수를 호출하도록 만든것이고, 메소드 내부에서 self에 tapgesture를 걸어둔걸 확인하실 수 있어요

셀렉터 함수에 메소드나 파라미터를 전달할 수 없는 점을 적극적으로 개선할 수 있습니다.
private var tapGestureRecognizerHandler: GestureHandler? {
   get {
          return objc_getAssociatedObject(
          self,
          &GestureAssociatedKey.tapGestureKey
          ) as? GestureHandler
   }
   set {
            if let newValue = newValue {
            objc_setAssociatedObject(
            self,
            &GestureAssociatedKey.tapGestureKey,
            newValue,
            objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
       }
     }
  }

저는 현재 클로저만 전달했지만, 딕셔너리나 다른 파라미터를 넣을 수도 있겠다는걸 알 수 있습니다

객체의 메모리를 관리하는 정책인 AssociationPolicy에 대해서 알아보겠습니다 (빨간색 부분)
ObjectiveC의 런타임에 메모리 값을 통해 객체에 접근하는 것이므로, 정확히 알고 사용하는게 중요하겠습니다.
OBJC_ASSOCIATION_ASSIGN // 추가된 객체와 약한 참조
OBJC_ASSOCIATION_RETAIN_NONATOMIC // 추가된 객체와 강력 참조 및 nonatomatic으로 설정 OBJC_ASSOCIATION_COPY_NONATOMIC // 추가된 객체를 복사 및 nonatomatic으로 설정
OBJC_ASSOCIATION_RETAIN // 추가된 객체와 강력 참조 및 atomatic으로 설정
OBJC_ASSOCIATION_COPY // 추가된 객체를 복사 및 atomatic으로 설정


이렇게 간단히 알아보았는데, 여러 Delegate를 대신해서 Closure으로 바꿔줄 수 있습니다.
그러면서도 선언된 Delegate를 수정하거나 클래스 파일을 막 수정해야 하는것이 아니니
조금 더 폭넓게 사용하면 좋겠다라는 생각이 듭니다.

다양한 예제는 람다킷 https://github.com/Reflejo/LambdaKit 을 참고하세용


고민 1
기본 코드블럭이 너무 못생겼어요ㅠㅠ gist로는 한계가 있네요
그래서 코드블럭을 일부러 안 걸었답니다??