Чи є спосіб встановити пов'язані об'єкти в Swift?


82

Виходячи з цілі C, ви можете викликати функцію objc_setAssociatedObjectміж 2 об’єктами, щоб вони підтримували посилання, що може бути зручно, якщо під час виконання ви не хочете, щоб об’єкт був знищений, поки його посилання також не буде видалено. Чи є у Свіфта щось подібне до цього?


3
Ви можете використовувати objc_setAssociatedObjectз Swift: developer.apple.com/library/prerelease/ios/documentation/Swift/…
jtbandes

ДОВГИЙ ПРИКЛАД З ПОВНИМ КОДОМ для вирізання та вставки: 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 ?? ""
    }

Чи вдалося вам встановити функцію, яка відповідає псевдоніму у такий спосіб?
Моркром,

1
@PhamHoan Int - рідний тип Swift. Спробуйте замість цього використовувати NSNumber.
Клаас,

1
@PhamHoan Я додав рішення для отримання неініціалізованого об'єкта.
Клаас,

1
@Jawwad Я думаю, це не має значення. UInt8Походить від пов'язаного оригінального потоку.
Клаас,

1
привіт @Jacky - що змушує тебе сказати, що це має бути КОПІЮВАННЯ .. будь ласка, дайте нам знати!
Фатті

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] за допомогою цього методу, але геттер завжди повертає мені нуль. Якось інакше if з get не повертає належним чином значення.
метеори

Тип політики асоціативності, яку ви вирішили використовувати, залежить від вас, це залежить від вашої програми. Для отримання додаткової інформації я пропоную вам ознайомитись із цією статтею, nshipster.com/associated-objects . Я намагався зберігати масиви, здається, це працює чудово. Яку версію Swift ви до речі використовуєте?
HepaKKes

Це чудово. Для Swift 2.3 я в кінцевому підсумку перемістив Generics з ліфта і використовував 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. Чи має це працювати?
Накрос

Насправді 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.

Швидка асоціація структури


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

0

Просто додайте #import <objc/runtime.h>до свого заголовкового файлу, щоб отримати доступ до objc_setAssociatedObject під швидким кодом


18
а можна просто import ObjectiveCв
Свіфті

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
}

``

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.