Có cách nào để đặt các đối tượng liên quan trong Swift không?


82

Từ mục tiêu C, bạn có thể gọi hàm objc_setAssociatedObjectgiữa 2 đối tượng để chúng duy trì một tham chiếu, điều này có thể hữu ích nếu trong thời gian chạy, bạn không muốn một đối tượng bị hủy cho đến khi tham chiếu của nó cũng bị xóa. Có gì tương tự như thế này không?


3
Bạn có thể sử dụng objc_setAssociatedObjecttừ Swift: developer.apple.com/library/prerelease/ios/documentation/Swift/…
jtbandes

VÍ DỤ DÀI VỚI MÃ ĐẦY ĐỦ để cắt và dán: stackoverflow.com/documentation/swift/1085/associated-objects/…
Fattie

Câu trả lời:


130

Đây là một ví dụ đơn giản nhưng đầy đủ được lấy từ câu trả lời của jckarter .

Nó chỉ ra cách thêm một thuộc tính mới vào một lớp hiện có. Nó thực hiện điều đó bằng cách xác định một thuộc tính được tính toán trong một khối mở rộng. Thuộc tính đã tính được lưu trữ dưới dạng một đối tượng liên kết:

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)
        }
    }
}

BIÊN TẬP:

Nếu bạn cần hỗ trợ lấy giá trị của thuộc tính chưa được khởi tạo và để tránh gặp lỗi unexpectedly found nil while unwrapping an Optional value, bạn có thể sửa đổi getter như sau:

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

Bạn đã có thể đặt một hàm phù hợp với kiểu chữ theo kiểu này chưa?
Morkrom

1
@PhamHoan Int là một kiểu gốc Swift. Hãy thử sử dụng NSNumber thay thế.
Klaas

1
@PhamHoan Tôi đã thêm một giải pháp để lấy một đối tượng chưa được khởi tạo.
Klaas

1
@Jawwad Tôi đoán, nó không có gì khác biệt. Các UInt8đến từ chủ đề gốc được liên kết.
Klaas

1
hey @Jacky - điều gì khiến bạn nói nó phải là BẢN SAO .. vui lòng cho chúng tôi biết!
Fattie

31

Giải pháp này cũng hỗ trợ tất cả các kiểu giá trị và không chỉ những kiểu được bắc cầu tự động , chẳng hạn như String, Int, Double, v.v.

Giấy gói

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
    }
}

Phần mở rộng Class khả thi (Ví dụ về cách sử dụng)

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)
            }
        }
    }
}

Bạn có thể vui lòng giải thích cho tôi những lá cờ hiệp hội đó có nghĩa là gì? Tôi thực sự đang cố gắng làm cho thanh điều hướng cuộn với scrollView, vì vậy tôi đang lưu trữ scrollView trong phần mở rộng. Tôi nên sử dụng cờ nào?
sao băng

1
Ngoài ra, tôi đang cố gắng lưu trữ một mảng [AnyObject] bằng cách sử dụng phương thức này nhưng getter luôn trả về cho tôi số không. Bằng cách nào đó, if of get khác không trả về đúng giá trị.
sao băng

Loại chính sách liên kết mà bạn quyết định sử dụng là tùy thuộc vào bạn, nó phụ thuộc vào chương trình của bạn. Để biết thêm thông tin, tôi đề nghị bạn kiểm tra bài viết này, nshipster.com/associated-objects , ra ngoài. Tôi đã cố gắng lưu trữ các mảng, có vẻ như nó đang hoạt động tốt. Bạn đang sử dụng phiên bản Swift nào?
HepaKKes

Điều này là tuyệt vời. Đối với Swift 2.3, tôi đã kết thúc việc chuyển Generics khỏi Lift và sử dụng Anythay thế. Tôi sẽ làm một bài viết blog về vấn đề này, tôi nghĩ
Dan Rosenstark

5
Đây nên dễ dàng hơn rất nhiều trong Swift 3. Nhưng dù sao, đây là myarticle dựa trên câu trả lời của bạn ở đây: yar2050.com/2016/11/associated-object-support-for-swift-23.html
Dan Rosenstark

5

Rõ ràng, điều này chỉ hoạt động với các đối tượng Objective-C. Sau khi tìm hiểu điều này một chút, đây là cách thực hiện cuộc gọi trong 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)
}

Tôi đã thử phương pháp này nhưng đối tượng giá trị của tôi dường như được phân bổ ngay lập tức khi không có tham chiếu nào khác đến nó từ mã nhanh. Đối tượng mục tiêu của tôi là SKNode và đối tượng giá trị của tôi là một lớp nhanh chóng mở rộng NSObject. Điều này có nên làm việc?
nacross

Trên thực tế, deinit dường như được gọi trong khi objc_setAssociatedObject mặc dù đối tượng vừa được tạo và được sử dụng thêm trong phương thức.
nacross

4

Cập nhật trong Swift 3.0 Ví dụ đây là 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

Tôi đã viết một trình trợ giúp hiện đại yêu cầu 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) }
    }
}

Bạn có thể ngạc nhiên rằng nó thậm chí còn hỗ trợ các cấu trúc Swift miễn phí.

Liên kết cấu trúc Swift


2

Câu trả lời của Klaas chỉ dành cho 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

Chỉ cần thêm #import <objc/runtime.h>vào tệp tiêu đề brindging của bạn để truy cập objc_setAssociatedObject theo mã nhanh


18
hoặc bạn có thể chỉ import ObjectiveCbằng Swift
newacct

0

Người bạn trên đã trả lời câu hỏi của bạn, nhưng nếu nó liên quan đến tài sản đóng cửa, xin lưu ý:

``

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
}

``

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.