Bagaimana cara menyimpan properti di Swift, dengan cara yang sama yang saya miliki di Objective-C?


132

Saya mengganti aplikasi dari Objective-C ke Swift, yang saya punya beberapa kategori dengan properti tersimpan, misalnya:

@interface UIView (MyCategory)

- (void)alignToView:(UIView *)view
          alignment:(UIViewRelativeAlignment)alignment;
- (UIView *)clone;

@property (strong) PFObject *xo;
@property (nonatomic) BOOL isAnimating;

@end

Karena ekstensi Swift tidak menerima properti tersimpan seperti ini, saya tidak tahu bagaimana mempertahankan struktur yang sama dengan kode Objc. Properti yang disimpan sangat penting untuk aplikasi saya dan saya percaya Apple pasti telah menciptakan beberapa solusi untuk melakukannya di Swift.

Seperti yang dikatakan oleh jou, apa yang saya cari sebenarnya menggunakan objek terkait, jadi saya melakukannya (dalam konteks lain):

import Foundation
import QuartzCore
import ObjectiveC

extension CALayer {
    var shapeLayer: CAShapeLayer? {
        get {
            return objc_getAssociatedObject(self, "shapeLayer") as? CAShapeLayer
        }
        set(newValue) {
            objc_setAssociatedObject(self, "shapeLayer", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    var initialPath: CGPathRef! {
        get {
            return objc_getAssociatedObject(self, "initialPath") as CGPathRef
        }
        set {
            objc_setAssociatedObject(self, "initialPath", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }
}

Tapi saya mendapatkan EXC_BAD_ACCESS saat melakukan:

class UIBubble : UIView {
    required init(coder aDecoder: NSCoder) {
        ...
        self.layer.shapeLayer = CAShapeLayer()
        ...
    }
}

Ada ide?


11
Kategori kelas Objective-C juga tidak bisa mendefinisikan variabel instan, jadi bagaimana Anda menyadari properti itu?
Martin R

Tidak yakin mengapa Anda mendapatkan akses yang buruk, tetapi kode Anda seharusnya tidak berfungsi. Anda memberikan nilai yang berbeda ke setAssociateObject dan getAssociatedObject. Menggunakan string "shapeLayer" sebagai kunci salah, itu adalah penunjuk (sebenarnya alamat itu) yang merupakan kunci, bukan apa yang ditunjukkannya. Dua string identik yang berada di alamat berbeda adalah dua kunci yang berbeda. Tinjau jawaban Jou dan perhatikan bagaimana ia mendefinisikan xoAssociationKey sebagai variabel global, jadi itu adalah kunci / penunjuk yang sama saat mengatur / mendapatkan.
SafeFastExpressive

Jawaban:


50

Objek terkait API agak rumit untuk digunakan. Anda dapat menghapus sebagian besar pelat dengan kelas pembantu.

public final class ObjectAssociation<T: AnyObject> {

    private let policy: objc_AssociationPolicy

    /// - Parameter policy: An association policy that will be used when linking objects.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

        self.policy = policy
    }

    /// Accesses associated object.
    /// - Parameter index: An object whose associated object is to be accessed.
    public subscript(index: AnyObject) -> T? {

        get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

Asalkan Anda dapat "menambahkan" properti ke kelas objektif-c dengan cara yang lebih mudah dibaca:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

        get { return SomeType.association[self] }
        set { SomeType.association[self] = newValue }
    }
}

Adapun solusinya:

extension CALayer {

    private static let initialPathAssociation = ObjectAssociation<CGPath>()
    private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>()

    var initialPath: CGPath! {
        get { return CALayer.initialPathAssociation[self] }
        set { CALayer.initialPathAssociation[self] = newValue }
    }

    var shapeLayer: CAShapeLayer? {
        get { return CALayer.shapeLayerAssociation[self] }
        set { CALayer.shapeLayerAssociation[self] = newValue }
    }
}

Ok apa yang harus saya lakukan jika saya ingin menyimpan Int, Bool, dll?
Vyachaslav Gerchicov

1
Tidak mungkin untuk menyimpan tipe Swift melalui asosiasi objek secara langsung. Anda dapat menyimpan misalnya NSNumberatau NSValuedan menulis sepasang asesoris tambahan yang akan menjadi tipe yang Anda inginkan (Int, Bool, dll).
Wojciech Nagrodzki

1
PERINGATAN Ini tidak berfungsi untuk struct, karena pustaka runtime objektif-c hanya mendukung kelas yang sesuai denganNSObjectProtocol
Charlton Provatas

2
@CharltonProvatas Tidak mungkin menggunakan struct dengan API ini, mereka tidak sesuai dengan protokol AnyObject.
Wojciech Nagrodzki

@VyachaslavGerchicov [String]?adalah sebuah struct, ini adalah kasus yang sama seperti untuk Int, Bool. Anda bisa menggunakan NSArrayuntuk menyimpan koleksi NSStringinstance.
Wojciech Nagrodzki

184

Seperti di Objective-C, Anda tidak bisa menambahkan properti tersimpan ke kelas yang ada. Jika Anda memperluas kelas Objective-C ( UIViewpasti salah satu), Anda masih bisa menggunakan Objek Terkait untuk meniru properti tersimpan:

untuk Swift 1

import ObjectiveC

private var xoAssociationKey: UInt8 = 0

extension UIView {
    var xo: PFObject! {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject
        }
        set(newValue) {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN))
        }
    }
}

Kunci asosiasi adalah pointer yang harus menjadi unik untuk setiap asosiasi. Untuk itu, kami membuat variabel global pribadi dan menggunakan alamat memorinya sebagai kunci dengan &operator. Lihat Menggunakan Swift dengan Kakao dan Objective-C pada lebih detail bagaimana pointer ditangani di Swift.

DIPERBARUI untuk Swift 2 dan 3

import ObjectiveC

private var xoAssociationKey: UInt8 = 0

extension UIView {
    var xo: PFObject! {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject
        }
        set(newValue) {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }
}

DIPERBARUI untuk Swift 4

Di Swift 4, ini jauh lebih sederhana. Struct Holder akan berisi nilai pribadi yang akan diekspos oleh properti yang dikomputasi ke dunia, memberikan ilusi perilaku properti yang tersimpan sebagai gantinya.

Sumber

extension UIViewController {
    struct Holder {
        static var _myComputedProperty:Bool = false
    }
    var myComputedProperty:Bool {
        get {
            return Holder._myComputedProperty
        }
        set(newValue) {
            Holder._myComputedProperty = newValue
        }
    }
}

2
@Yar Tidak tahu objc_AssociationPolicy, terima kasih! Saya sudah memperbarui jawabannya
jou

2
Di Swift2 Anda harus menggunakan objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom

1
@SimplGy tolong tambahkan edit Anda sebagai jawaban tambahan daripada mengedit kode Anda menjadi jawaban orang lain.
JAL

11
Solusi Swift4 tidak berfungsi jika Anda ingin memiliki lebih dari 1 instance UIViewController yang menggunakan properti dari ekstensi. Silakan periksa pembaruan dalam sumber.
iur

9
Contoh Swift 4 tidak berfungsi dengan banyak instance, dan mungkin seharusnya tidak ada di sini.
Colin Cornaby

36

Jadi saya pikir saya menemukan metode yang bekerja lebih bersih daripada yang di atas karena tidak memerlukan variabel global. Saya mendapatkannya dari sini: http://nshipster.com/swift-objc-runtime/

Intinya adalah bahwa Anda menggunakan struct seperti:

extension UIViewController {
    private struct AssociatedKeys {
        static var DescriptiveName = "nsh_DescriptiveName"
    }

    var descriptiveName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.DescriptiveName,
                    newValue as NSString?,
                    UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                )
            }
        }
    }
}

UPDATE untuk Swift 2

private struct AssociatedKeys {
    static var displayed = "displayed"
}

//this lets us check to see if the item is supposed to be displayed or not
var displayed : Bool {
    get {
        guard let number = objc_getAssociatedObject(self, &AssociatedKeys.displayed) as? NSNumber else {
            return true
        }
        return number.boolValue
    }

    set(value) {
        objc_setAssociatedObject(self,&AssociatedKeys.displayed,NSNumber(bool: value),objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

@ AKWeblS Saya telah menerapkan kode seperti milik Anda tetapi ketika memperbarui ke swift 2.0, saya mendapatkan kesalahan tidak dapat memanggil penginisialisasi untuk tipe 'UInt' dengan daftar argumen tipe 'objc_AssociationPolicy)'. Kode dalam komentar berikutnya
user2363025

Bagaimana cara memperbarui untuk swift 2.0? var postKeterangan: String? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) sebagai? String} set {if membiarkan newValue = newValue {objc_setAssociatedObject (self, & AssociatedKeys.postDescription, newValue sebagai NSString ?, uint (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
tidak berfungsi: static var berarti nilai properti sama untuk semua contoh
Fry

@fry - terus bekerja untuk saya. Ya, kuncinya statis, tetapi terkait dengan objek tertentu, objek itu sendiri bukan global.
AlexK

15

Solusinya ditunjukkan oleh jou tidak mendukung jenis nilai , ini bekerja dengan baik dengan mereka juga

Pembungkus

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

Kemungkinan ekstensi Kelas (Contoh penggunaan):

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

Ini benar-benar solusi yang hebat, saya ingin menambahkan contoh penggunaan lain yang menyertakan struct dan nilai yang bukan opsional. Juga, nilai-nilai AssociatedKey dapat disederhanakan.

struct Crate {
    var name: String
}

class Box {
    var name: String

    init(name: String) {
        self.name = name
    }
}

extension UIViewController {

    private struct AssociatedKey {
        static var displayed:   UInt8 = 0
        static var box:         UInt8 = 0
        static var crate:       UInt8 = 0
    }

    var displayed: Bool? {
        get {
            return getAssociatedObject(self, associativeKey: &AssociatedKey.displayed)
        }

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

    var box: Box {
        get {
            if let result:Box = getAssociatedObject(self, associativeKey: &AssociatedKey.box) {
                return result
            } else {
                let result = Box(name: "")
                self.box = result
                return result
            }
        }

        set {
            setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.box, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    var crate: Crate {
        get {
            if let result:Crate = getAssociatedObject(self, associativeKey: &AssociatedKey.crate) {
                return result
            } else {
                let result = Crate(name: "")
                self.crate = result
                return result
            }
        }

        set {
            setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.crate, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

Dan bagaimana cara menggunakan kelas Lifted?
3lvis

Mengapa Anda membutuhkannya? Untuk apa?
HepaKKes

Maksud saya bagaimana Anda menggunakan ini secara umum, terima kasih atas bantuan Anda :) Bisakah Anda menambahkan contoh "Cara menggunakan"?
3lvis

1
Contoh "Cara menggunakan" akan mulai tepat di bawah tajuk "Kemungkinan ekstensi Kelas". Saya tidak berpikir pengguna perlu tahu cara menggunakan liftmetode dan Liftedkelas, mereka harus menggunakan getAssociatedObject& setAssociatedObjectfungsi. Saya akan menambahkan "Contoh penggunaan" antara tanda kurung di sebelah header demi kejelasan.
HepaKKes

1
Ini tentu solusi terbaik, sayang sekali tidak dijelaskan dengan baik. Ini berfungsi dengan kelas, struct, dan tipe nilai lainnya. Properti tidak harus opsional juga. Kerja bagus.
picciano

13

Anda tidak dapat menentukan kategori (ekstensi Swift) dengan penyimpanan baru; setiap properti tambahan harus dihitung daripada disimpan. Sintaks berfungsi untuk Objective C karena @propertypada suatu kategori pada dasarnya berarti "Saya akan menyediakan pengambil dan penyetel". Di Swift, Anda harus menentukan sendiri hal ini untuk mendapatkan properti yang dihitung; sesuatu seperti:

extension String {
    public var Foo : String {
        get
        {
            return "Foo"
        }

        set
        {
            // What do you want to do here?
        }
    }
}

Harus bekerja dengan baik. Ingat, Anda tidak dapat menyimpan nilai baru di setter, hanya bekerja dengan status kelas yang tersedia.


7

$ 0,02 saya Kode ini ditulis dalam Swift 2.0

extension CALayer {
    private struct AssociatedKeys {
        static var shapeLayer:CAShapeLayer?
    }

    var shapeLayer: CAShapeLayer? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.shapeLayer) as? CAShapeLayer
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(self, &AssociatedKeys.shapeLayer, newValue as CAShapeLayer?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

Saya telah mencoba banyak solusi, dan menemukan ini adalah satu-satunya cara untuk benar-benar memperluas kelas dengan parameter variabel tambahan.


Terlihat menarik, tetapi menolak tidak bekerja dengan protokol, type 'AssociatedKeys' cannot be defined within a protocol extension...
Ian Bytchek

6

Mengapa mengandalkan runtime objc? Saya tidak mengerti maksudnya. Dengan menggunakan sesuatu seperti yang berikut ini, Anda akan mencapai perilaku yang hampir sama dari properti tersimpan, dengan hanya menggunakan pendekatan Swift murni :

extension UIViewController {
    private static var _myComputedProperty = [String:Bool]()

    var myComputedProperty:Bool {
        get {
            let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
            return UIViewController._myComputedProperty[tmpAddress] ?? false
        }
        set(newValue) {
            let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
            UIViewController._myComputedProperty[tmpAddress] = newValue
        }
    }
}

1
Pintar! Satu kelemahan yang mungkin terjadi dengan pendekatan ini adalah manajemen memori. Setelah objek host dideallocated, properti itu akan tetap ada di kamus yang berpotensi bisa mahal pada penggunaan memori jika ini digunakan dengan banyak objek.
Charlton Provatas

Mungkin kita bisa memegang variabel daftar pembungkus statis yang berisi referensi lemah ke objek (diri). Dan hapus entri dari kamus _myComputedProperty juga daftar variabel statis pembungkus dalam metode didSet Wrapper (Ketika objek akan dialokasikan kembali didSet di dalam kelas Wrapper kami dipanggil karena referensi lemah kami menetapkan nilai baru dengan nihil).
Arjuna

5

Saya lebih suka melakukan kode dalam Swift murni dan tidak mengandalkan warisan Objective-C. Karena itu saya menulis solusi Swift murni dengan dua kelebihan dan dua kekurangan.

Keuntungan:

  1. Kode Swift murni

  2. Bekerja pada kelas dan penyelesaian atau lebih khusus pada Anyobjek

Kekurangan:

  1. Kode harus memanggil metode willDeinit()untuk melepaskan objek yang terhubung ke instance kelas tertentu untuk menghindari kebocoran memori

  2. Anda tidak dapat membuat ekstensi langsung ke UIView untuk contoh ini karena var frameekstensi ke UIView, bukan bagian dari kelas.

EDIT:

import UIKit

var extensionPropertyStorage: [NSObject: [String: Any]] = [:]

var didSetFrame_ = "didSetFrame"

extension UILabel {

    override public var frame: CGRect {

        get {
            return didSetFrame ?? CGRectNull
        }

        set {
            didSetFrame = newValue
        }
    }

    var didSetFrame: CGRect? {

        get {
            return extensionPropertyStorage[self]?[didSetFrame_] as? CGRect
        }

        set {
            var selfDictionary = extensionPropertyStorage[self] ?? [String: Any]()

            selfDictionary[didSetFrame_] = newValue

            extensionPropertyStorage[self] = selfDictionary
        }
    }

    func willDeinit() {
        extensionPropertyStorage[self] = nil
    }
}

4
Ini tidak berfungsi karena extensionPropertyStorage dibagikan di antara semua instance. Jika Anda menetapkan nilai untuk satu instance, Anda menetapkan nilai untuk semua instance.
picciano

Itu perang tidak baik karena mengacaukan fungsi. Sekarang lebih baik dan berfungsi sebagaimana dimaksud.
vedrano

@picciano extensionPropertyStorage dibagikan dengan semua instance oleh desain. Ini adalah variabel global (Kamus) yang pertama-tama mende-rujukan instance UILabel ( NSObject) dan kemudian propertinya ( [String: Any]).
vedrano

@picciano Saya kira ini berfungsi untuk classproperti;)
Nicolas Miari

frameadalah properti instan. Dari mana datangnya properti kelas?
vedrano

3

Dengan Kategori Obj-c Anda hanya dapat menambahkan metode, bukan variabel instan.

Dalam contoh Anda, Anda telah menggunakan @property sebagai jalan pintas untuk menambahkan deklarasi metode pengambil dan penyetel. Anda masih perlu menerapkan metode-metode itu.

Demikian pula di Swift, Anda dapat menambahkan ekstensi yang digunakan untuk menambahkan metode contoh, properti yang dihitung, dll. Tetapi tidak properti yang disimpan.


2

Saya juga mendapatkan masalah EXC_BAD_ACCESS. Nilai dalam objc_getAssociatedObject()dan objc_setAssociatedObject()harus menjadi Obyek. Dan objc_AssociationPolicyharus cocok dengan Object.


3
Ini tidak benar-benar menjawab pertanyaan itu. Jika Anda memiliki pertanyaan yang berbeda, Anda dapat menanyakannya dengan mengeklik Ajukan Pertanyaan . Anda juga dapat menambahkan hadiah untuk menarik lebih banyak perhatian ke pertanyaan ini setelah Anda memiliki reputasi yang cukup . - Dari Ulasan
AtheistP3ace

@ AtheistP3ace Saya sangat menyesal tentang itu. Saya mencoba menambahkan komentar pada pertanyaan. Tetapi saya tidak memiliki reputasi yang cukup. Jadi saya mencoba menjawab pertanyaan itu.
RuiKQ

2

Saya mencoba menggunakan objc_setAssociatedObject seperti yang disebutkan dalam beberapa jawaban di sini, tetapi setelah gagal dengannya beberapa kali saya melangkah mundur dan menyadari bahwa tidak ada alasan saya membutuhkannya. Meminjam dari beberapa ide di sini, saya datang dengan kode ini yang hanya menyimpan array apa pun data tambahan saya (MyClass dalam contoh ini) diindeks oleh objek yang ingin saya kaitkan dengan:

class MyClass {
    var a = 1
    init(a: Int)
    {
        self.a = a
    }
}

extension UIView
{
    static var extraData = [UIView: MyClass]()

    var myClassData: MyClass? {
        get {
            return UIView.extraData[self]
        }
        set(value) {
            UIView.extraData[self] = value
        }
    }
}

// Test Code: (Ran in a Swift Playground)
var view1 = UIView()
var view2 = UIView()

view1.myClassData = MyClass(a: 1)
view2.myClassData = MyClass(a: 2)
print(view1.myClassData?.a)
print(view2.myClassData?.a)

apakah Anda tahu cara menghapus ekstra data secara otomatis sehingga tidak menyebabkan kebocoran memori?
DoubleK

Saya tidak benar-benar menggunakan ini dengan pandangan, dalam kasus saya, saya hanya pernah instantiate sejumlah objek di kelas yang saya gunakan, jadi saya belum benar-benar mempertimbangkan kebocoran memori. Saya tidak bisa memikirkan cara otomatis untuk melakukan ini, tapi saya kira dalam setter di atas Anda bisa menambahkan logika untuk menghapus elemen jika nilai == nil, dan kemudian mengatur nilai ke nil ketika Anda tidak lagi membutuhkan objek, atau mungkin di didReceiveMemoryWarning atau sesuatu.
Dan

Tnx @Dan, saya menggunakan viewWillDisapper untuk menetapkan nilai ke nil dan berfungsi dengan baik, sekarang deinit dipanggil dan semuanya berfungsi dengan baik. Tnx untuk memposting solusi ini
DoubleK

2

Inilah solusi yang disederhanakan dan lebih ekspresif. Ini berfungsi untuk tipe nilai dan referensi. Pendekatan mengangkat diambil dari jawaban @HepaKKes.

Kode asosiasi:

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 associated<T>(to base: AnyObject,
                key: UnsafePointer<UInt8>,
                policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN,
                initialiser: () -> T) -> T {
    if let v = objc_getAssociatedObject(base, key) as? T {
        return v
    }

    if let v = objc_getAssociatedObject(base, key) as? Lifted<T> {
        return v.value
    }

    let lifted = Lifted(initialiser())
    objc_setAssociatedObject(base, key, lifted, policy)
    return lifted.value
}

func associate<T>(to base: AnyObject, key: UnsafePointer<UInt8>, value: T, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN) {
    if let v: AnyObject = value as AnyObject? {
        objc_setAssociatedObject(base, key, v, policy)
    }
    else {
        objc_setAssociatedObject(base, key, lift(value), policy)
    }
}

Contoh penggunaan:

1) Buat ekstensi dan kaitkan properti ke sana. Mari kita gunakan properti nilai dan tipe referensi.

extension UIButton {

    struct Keys {
        static fileprivate var color: UInt8 = 0
        static fileprivate var index: UInt8 = 0
    }

    var color: UIColor {
        get {
            return associated(to: self, key: &Keys.color) { .green }
        }
        set {
            associate(to: self, key: &Keys.color, value: newValue)
        }
    }

    var index: Int {
        get {
            return associated(to: self, key: &Keys.index) { -1 }
        }
        set {
            associate(to: self, key: &Keys.index, value: newValue)
        }
    }

}

2) Sekarang Anda dapat menggunakan properti seperti biasa:

    let button = UIButton()
    print(button.color) // UIExtendedSRGBColorSpace 0 1 0 1 == green
    button.color = .black
    print(button.color) // UIExtendedGrayColorSpace 0 1 == black

    print(button.index) // -1
    button.index = 3
    print(button.index) // 3

Keterangan lebih lanjut:

  1. Mengangkat diperlukan untuk jenis nilai pembungkus.
  2. Perilaku objek terkait default dipertahankan. Jika Anda ingin mempelajari lebih lanjut tentang objek terkait, saya sarankan memeriksa artikel ini .

1

Contoh lain dengan menggunakan Objective-C terkait objek dan properti yang dihitung untuk Swift 3 dan Swift 4

import CoreLocation

extension CLLocation {

    private struct AssociatedKeys {
        static var originAddress = "originAddress"
        static var destinationAddress = "destinationAddress"
    }

    var originAddress: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.originAddress) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.originAddress,
                    newValue as NSString?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }

    var destinationAddress: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.destinationAddress) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.destinationAddress,
                    newValue as NSString?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }

}

1

jika Anda ingin mengatur atribut string khusus ke UIView, ini adalah bagaimana saya melakukannya pada Swift 4

Buat ekstensi UIView

extension UIView {

    func setStringValue(value: String, key: String) {
        layer.setValue(value, forKey: key)
    }

    func stringValueFor(key: String) -> String? {
        return layer.value(forKey: key) as? String
    }
}

Untuk menggunakan ekstensi ini

let key = "COLOR"

let redView = UIView() 

// To set
redView.setStringAttribute(value: "Red", key: key)

// To read
print(redView.stringValueFor(key: key)) // Optional("Red")

1

Bagaimana dengan menyimpan peta statis ke kelas yang diperluas seperti ini:

extension UIView {

    struct Holder {
        static var _padding:[UIView:UIEdgeInsets] = [:]
    }

    var padding : UIEdgeInsets {
        get{ return UIView.Holder._padding[self] ?? .zero}
        set { UIView.Holder._padding[self] = newValue }
    }

}

1
mengapa jawaban ini tidak memiliki upvotes. Bagaimanapun, solusi cerdas.
Janeevan

Ini bekerja tetapi saya pikir pendekatan ini cacat seperti yang dinyatakan di sini medium.com/@valv0/…
Teffi

0

Saya mencoba menyimpan properti dengan menggunakan objc_getAssociatedObject, objc_setAssociatedObject, tanpa hasil. Tujuan saya adalah membuat ekstensi untuk UITextField, untuk memvalidasi panjang karakter input teks. Kode berikut berfungsi dengan baik untuk saya. Semoga ini bisa membantu seseorang.

private var _min: Int?
private var _max: Int?

extension UITextField {    
    @IBInspectable var minLength: Int {
        get {
            return _min ?? 0
        }
        set {
            _min = newValue
        }
    }

    @IBInspectable var maxLength: Int {
        get {
            return _max ?? 1000
        }
        set {
            _max = newValue
        }
    }

    func validation() -> (valid: Bool, error: String) {
        var valid: Bool = true
        var error: String = ""
        guard let text = self.text else { return (true, "") }

        if text.characters.count < minLength {
            valid = false
            error = "Textfield should contain at least \(minLength) characters"
        }

        if text.characters.count > maxLength {
            valid = false
            error = "Textfield should not contain more then \(maxLength) characters"
        }

        if (text.characters.count < minLength) && (text.characters.count > maxLength) {
            valid = false
            error = "Textfield should contain at least \(minLength) characters\n"
            error = "Textfield should not contain more then \(maxLength) characters"
        }

        return (valid, error)
    }
}

Variabel pribadi global? Apakah Anda menyadari itu _mindan _maxbersifat global dan akan sama dalam semua hal di UITextField? Bahkan jika itu bekerja untuk Anda, jawaban ini tidak berhubungan karena Marcos bertanya tentang variabel instan .
kelin

Ya Anda benar, ini tidak berfungsi untuk semua instance. Saya Memperbaiki dengan menyimpan nilai min dan maks dalam struct. Maaf untuk off topic.
Vadims Krutovs

0

Berikut adalah alternatif yang berfungsi juga

public final class Storage : AnyObject {

    var object:Any?

    public init(_ object:Any) {
        self.object = object
    }
}

extension Date {

    private static let associationMap = NSMapTable<NSString, AnyObject>()
    private struct Keys {
        static var Locale:NSString = "locale"
    }

    public var locale:Locale? {
        get {

            if let storage = Date.associationMap.object(forKey: Keys.Locale) {
                return (storage as! Storage).object as? Locale
            }
            return nil
        }
        set {
            if newValue != nil {
                Date.associationMap.setObject(Storage(newValue), forKey: Keys.Locale)
            }
        }
    }
}



var date = Date()
date.locale = Locale(identifier: "pt_BR")
print( date.locale )

-1

Saya menemukan solusi ini lebih praktis

DIPERBARUI untuk Swift 3

extension UIColor {

    static let graySpace = UIColor.init(red: 50/255, green: 50/255, blue: 50/255, alpha: 1.0)
    static let redBlood = UIColor.init(red: 102/255, green: 0/255, blue: 0/255, alpha: 1.0)
    static let redOrange = UIColor.init(red: 204/255, green: 17/255, blue: 0/255, alpha: 1.0)

    func alpha(value : CGFloat) -> UIColor {
        var r = CGFloat(0), g = CGFloat(0), b = CGFloat(0), a = CGFloat(0)
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        return UIColor(red: r, green: g, blue: b, alpha: value)
    }

}

... lalu dalam kode Anda

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

    override func viewDidLoad() {
        self.view.backgroundColor = UIColor.graySpace

    }
}

Ini adalah cara yang tepat untuk melakukannya - tetapi tidak benar-benar properti yang disimpan dalam ekstensi seperti yang ditanyakan.
UKDataGeek
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.