Swiftで関連オブジェクトを設定する方法はありますか?


82

Objective Cからobjc_setAssociatedObject、2つのオブジェクト間で関数を呼び出して、参照を維持することができます。これは、実行時に、参照が削除されるまでオブジェクトを破棄したくない場合に便利です。swiftにはこれに似たものがありますか?


3
あなたは使用することができますobjc_setAssociatedObject:スウィフトからdeveloper.apple.com/library/prerelease/ios/documentation/Swift/...
jtbandes

LONG例WITH FULL CODEは、カット&ペーストしますstackoverflow.com/documentation/swift/1085/associated-objects/...
Fattie

回答:


132

これは、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する必要がある場合は、次のようにゲッターを変更できます。

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

このようにタイプエイリアスに準拠した関数を設定できましたか?
Morkrom 2014

1
@PhamHoanIntはSwiftネイティブタイプです。代わりにNSNumberを使用してみてください。
クラース2014

1
@PhamHoan初期化されていないオブジェクトを取得するためのソリューションを追加しました。
クラース2014

1
@Jawwadおそらく、違いはありません。UInt8リンク元のスレッドから来ています。
クラース2016

1
ちょっと@ Jacky-それがコピーであるべきだとあなたが言う理由は..私たちに知らせてください!
Fattie 2017年

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を拡張機能に保存しています。どのフラグを使用する必要がありますか?
流星2015年

1
また、このメソッドを使用して[AnyObject]の配列を格納しようとしていますが、ゲッターは常にnilを返します。どういうわけか、getのelseifが適切に値を返していません。
流星2015年

使用することを決定する結合性ポリシーの種類はあなた次第であり、それはあなたのプログラムに依存します。詳細については、この記事nshipster.com/associated-objectsを確認することをお勧めします。配列を保存しようとしましたが、問題なく動作しているようです。ちなみに、どのSwiftバージョンを使用していますか?
HepaKKes 2015年

これは素晴らしいです。Swift 2.3の場合、私はGenericsをLiftから移動し、Any代わりに使用することになりました。私はこれについてブログ記事を
書くつもり

5
これはSwift3ではるかに簡単になりました。しかし、とにかく、ここにあなたの答えに基づいたmyarticleがあります:yar2050.com/2016/11/associated-object-support-for-swift-23.html
Dan Rosenstark 2016年

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 2014

実際には、オブジェクトが作成されたばかりで、メソッドのさらに下で使用されている場合でも、deinitはobjc_setAssociatedObject中に呼び出されているように見えます。
nacross 2014

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

私はSwift5.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はSwift2.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>brindgingヘッダーファイルを追加するだけで、迅速なコードでobjc_setAssociatedObjectにアクセスできます


18
またはimport ObjectiveC、Swiftで行うこともできます
newacct 2014年

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.