Swift에서 관련 객체를 설정하는 방법이 있습니까?


82

목적 C에서 오는 objc_setAssociatedObject두 객체 사이의 함수 를 호출 하여 참조를 유지하도록 할 수 있습니다. 런타임시 참조가 제거 될 때까지 객체가 파괴되는 것을 원하지 않는 경우 유용 할 수 있습니다. 스위프트가 이와 비슷한 것이 있습니까?


3
당신은 사용할 수 있습니다 objc_setAssociatedObject: 스위프트에서 developer.apple.com/library/prerelease/ios/documentation/Swift/...
jtbandes

잘라내어 붙여 넣을 전체 코드가 포함 된 긴 예 : stackoverflow.com/documentation/swift/1085/related-objects/…
Fattie

답변:


130

다음은 jckarter의 답변 에서 파생 된 간단하지만 완전한 예제 입니다.

기존 클래스에 새 속성을 추가하는 방법을 보여줍니다. 확장 블록에서 계산 된 속성을 정의하여 수행합니다. 계산 된 속성은 관련 객체로 저장됩니다.

import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
private var AssociatedObjectHandle: UInt8 = 0

extension MyClass {
    var stringProperty:String {
        get {
            return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

편집하다:

초기화되지 않은 속성의 값을 가져오고 오류가 발생하지 않도록 지원해야하는 경우 다음 unexpectedly found nil while unwrapping an Optional value과 같이 getter를 수정할 수 있습니다.

    get {
        return objc_getAssociatedObject(self, &AssociatedObjectHandle) as? String ?? ""
    }

이런 방식으로 typealias를 따르는 함수를 설정할 수 있었습니까?
Morkrom

1
@PhamHoan Int는 Swift 네이티브 유형입니다. 대신 NSNumber를 사용해보십시오.
Klaas

1
@PhamHoan 초기화되지 않은 개체를 얻는 솔루션을 추가했습니다.
Klaas

1
@Jawwad 내 생각에, 그것은 아무런 차이가 없습니다. 는 UInt8링크 된 원래의 스레드에서 온다.
Klaas

1
헤이 @Jacky-무엇이 COPY이어야한다고 말하는지 알려주세요. 저희에게 알려주세요!
Fattie 2017-01-23

31

이 솔루션은 String, Int, Double 등과 같이 자동으로 브리지 되는 값 유형 뿐만 아니라 모든 값 유형 도 지원합니다 .

포장기

import ObjectiveC

final class Lifted<T> {
    let value: T
    init(_ x: T) {
        value = x
    }
}

private func lift<T>(x: T) -> Lifted<T>  {
    return Lifted(x)
}

func setAssociatedObject<T>(object: AnyObject, value: T, associativeKey: UnsafePointer<Void>, policy: objc_AssociationPolicy) {
    if let v: AnyObject = value as? AnyObject {
        objc_setAssociatedObject(object, associativeKey, v,  policy)
    }
    else {
        objc_setAssociatedObject(object, associativeKey, lift(value),  policy)
    }
}

func getAssociatedObject<T>(object: AnyObject, associativeKey: UnsafePointer<Void>) -> T? {
    if let v = objc_getAssociatedObject(object, associativeKey) as? T {
        return v
    }
    else if let v = objc_getAssociatedObject(object, associativeKey) as? Lifted<T> {
        return v.value
    }
    else {
        return nil
    }
}

가능한 클래스 확장 (사용 예)

extension UIView {

    private struct AssociatedKey {
        static var viewExtension = "viewExtension"
    }

    var referenceTransform: CGAffineTransform? {
        get {
            return getAssociatedObject(self, associativeKey: &AssociatedKey.viewExtension)
        }

        set {
            if let value = newValue {
                setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

연관 플래그가 무엇을 의미하는지 설명해 주시겠습니까? 실제로 scrollView로 탐색 막대 스크롤을 만들려고하므로 scrollView를 확장에 저장하고 있습니다. 어떤 플래그를 사용해야합니까?
유성

1
또한이 메서드를 사용하여 [AnyObject] 배열을 저장하려고하지만 getter는 항상 nil을 반환합니다. 어떻게 든 get의 else if 값이 제대로 반환되지 않습니다.
유성

사용하기로 결정한 연관성 정책의 종류는 사용자에게 달려 있으며 프로그램에 따라 다릅니다. 자세한 내용은 nshipster.com/related-objects 기사를 확인하시기 바랍니다 . 배열을 저장하려고 시도했지만 제대로 작동하는 것 같습니다. 그런데 어떤 Swift 버전을 사용하고 있습니까?
HepaKKes

이것은 훌륭합니다. Swift 2.3의 경우 리프트에서 Generics를 이동하고 Any대신 사용 했습니다. 나는 이것에 대해 블로그 기사를 할 것입니다.
Dan Rosenstark

5
이것은 Swift 3에서 훨씬 쉬워졌습니다. 그러나 어쨌든 여기에 귀하의 답변에 기반한 myarticle이
Dan Rosenstark

5

분명히 이것은 Objective-C 객체에서만 작동합니다. 이것에 대해 조금 다루고 나면 Swift에서 호출하는 방법은 다음과 같습니다.

import ObjectiveC

// Define a variable whose address we'll use as key.
// "let" doesn't work here.
var kSomeKey = "s"func someFunc() {
    objc_setAssociatedObject(target, &kSomeKey, value, UInt(OBJC_ASSOCIATION_RETAIN))

    let value : AnyObject! = objc_getAssociatedObject(target, &kSomeKey)
}

이 방법을 시도했지만 빠른 코드에서 다른 참조가 없을 때 내 값 개체가 즉시 할당 해제 된 것으로 보입니다. 내 대상 개체는 SKNode이고 내 값 개체는 NSObject를 확장하는 빠른 클래스입니다. 작동해야합니까?
nacross

실제로 deinit는 객체가 방금 생성되어 메서드에서 더 아래로 사용되는 경우에도 objc_setAssociatedObject 중에 호출되는 것으로 보입니다.
nacross

4

Swift 3.0 업데이트 예를 들어 이것은 UITextField입니다.

import Foundation
import UIKit
import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
var AssociatedObjectHandle: UInt8 = 0

extension UITextField
{
    var nextTextField:UITextField {
    get {
        return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UITextField
    }
    set {
        objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

3

Swift 5.1이 필요한 현대적인 도우미를 작성했습니다.

/*
 AssociatedObject.swift

 Copyright © 2020 RFUI.
 https://github.com/BB9z/iOS-Project-Template

 The MIT License
 https://opensource.org/licenses/MIT
 */

import Foundation

/**
 Objective-C associated value wrapper.

 Usage

 ```
 private let fooAssociation = AssociatedObject<String>()
 extension SomeObject {
     var foo: String? {
         get { fooAssociation[self] }
         set { fooAssociation[self] = newValue }
     }
 }
 ```
 */
public final class AssociatedObject<T> {
    private let policy: objc_AssociationPolicy

    /// Creates an associated value wrapper.
    /// - Parameter policy: The policy for the association.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
        self.policy = policy
    }

    /// Accesses the associated value.
    /// - Parameter index: The source object for the association.
    public subscript(index: AnyObject) -> T? {
        get { objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as? T }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

Swift 구조를 무료로 지원한다는 사실에 놀랄 수도 있습니다.

Swift 구조체 연결


2

Klaas는 Swift 2.1에 대해서만 답변합니다.

import ObjectiveC

let value = NSUUID().UUIDString
var associationKey: UInt8 = 0

objc_setAssociatedObject(parentObject, &associationKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)

let fetchedValue = objc_getAssociatedObject(parentObject, &associationKey) as! String

0

#import <objc/runtime.h>브리징 헤더 파일을 추가 하여 신속한 코드로 objc_setAssociatedObject에 액세스하십시오.


18
아니면 그냥 할 수있는 import ObjectiveC스위프트에서와
newacct

0

위의 친구가 귀하의 질문에 답변했지만 폐쇄 속성과 관련된 경우 다음 사항에 유의하십시오.

```

import UIKit
public extension UICollectionView {

typealias XYRearrangeNewDataBlock = (_ newData: [Any]) -> Void
typealias XYRearrangeOriginaDataBlock = () -> [Any]

// MARK:- associat key
private struct xy_associatedKeys {
    static var originalDataBlockKey = "xy_originalDataBlockKey"
    static var newDataBlockKey = "xy_newDataBlockKey"
}


private class BlockContainer {
    var rearrangeNewDataBlock: XYRearrangeNewDataBlock?
    var rearrangeOriginaDataBlock: XYRearrangeOriginaDataBlock?
}


private var newDataBlock: BlockContainer? {
    get {
        if let newDataBlock = objc_getAssociatedObject(self, &xy_associatedKeys.newDataBlockKey) as? BlockContainer {
            return newDataBlock
        }
        return nil
    }

    set(newValue) {
        objc_setAssociatedObject(self, xy_associatedKeys.newDataBlockKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
    }
}
convenience init(collectionVewFlowLayout : UICollectionViewFlowLayout, originalDataBlock: @escaping XYRearrangeOriginaDataBlock, newDataBlock:  @escaping XYRearrangeNewDataBlock) {
    self.init()


    let blockContainer: BlockContainer = BlockContainer()
    blockContainer.rearrangeNewDataBlock = newDataBlock
    blockContainer.rearrangeOriginaDataBlock = originalDataBlock
    self.newDataBlock = blockContainer
}

```

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.