Есть ли способ установить связанные объекты в Swift?

82

Исходя из цели C, вы можете вызывать функцию objc_setAssociatedObjectмежду двумя объектами, чтобы они поддерживали ссылку, что может быть удобно, если во время выполнения вы не хотите, чтобы объект был уничтожен, пока его ссылка также не будет удалена. Есть ли у Swift что-нибудь подобное?

Амлешк
источник
3
Вы можете использовать objc_setAssociatedObjectSwift: developer.apple.com/library/prerelease/ios/documentation/Swift/…
jtbandes
ДЛИННЫЙ ПРИМЕР С
ПОЛНЫМ

Ответы:

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
1
@PhamHoan Int - это собственный тип Swift. Попробуйте вместо этого использовать NSNumber.
Клаас
1
@PhamHoan Я добавил решение для получения неинициализированного объекта.
Клаас
1
@Jawwad Я думаю, это не имеет значения. Это UInt8происходит из связанной исходной цепочки.
Клаас
1
эй @Jacky - почему ты сказал, что это должно быть КОПИРОВАНИЕ ... пожалуйста, дайте нам знать!
Fattie
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)
            }
        }
    }
}
HepaKKes
источник
Не могли бы вы объяснить мне, что означают эти флаги ассоциации? На самом деле я пытаюсь прокрутить панель навигации с помощью scrollView, поэтому я сохраняю scrollView в расширении. Какие флаги мне использовать?
метеоры
1
Также я пытаюсь сохранить массив [AnyObject] с помощью этого метода, но получатель всегда возвращает мне ноль. Каким-то образом else if из get не возвращает значение должным образом.
метеоры
Какой тип политики ассоциативности вы решите использовать, зависит от вас, это зависит от вашей программы. Для получения дополнительной информации я предлагаю вам прочитать эту статью nshipster.com/associated-objects . Я пробовал хранить массивы, вроде работает нормально. Кстати, какую версию Swift вы используете?
HepaKKes
Это великолепно. Для Swift 2.3 я переместил Generics из Lift и использовал Anyвместо них. Думаю, я
напишу
5
В Swift 3 это стало намного проще. Но в любом случае вот моя статья, основанная на вашем ответе здесь: yar2050.com/2016/11/associated-object-support-for-swift-23.html
Дэн Розенстарк
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
источник
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.

Ассоциация быстрой структуры

BB9z
источник
2

Клаас отвечает только для 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
РоданV5500
источник
0

Просто добавьте #import <objc/runtime.h>заголовочный файл brindging, чтобы получить доступ к objc_setAssociatedObject под быстрым кодом

Жомар
источник
18
или вы можете просто import ObjectiveCв Swift
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
}

``

Альпфейс
источник