Objective-C'de olduğu gibi, özellikleri Swift'de nasıl saklayabilirim?


132

Bir uygulamayı Objective-C'den Swift'e geçiriyorum, burada saklanan özelliklere sahip birkaç kategorim var, örneğin:

@interface UIView (MyCategory)

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

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

@end

Swift uzantıları bu gibi depolanmış özellikleri kabul etmediğinden, Objc koduyla aynı yapıyı nasıl koruyacağımı bilmiyorum. Depolanan özellikler uygulamam için gerçekten önemli ve Apple'ın bunu Swift ile yapmak için bazı çözümler yaratmış olması gerektiğine inanıyorum.

Jou'nun söylediği gibi, aradığım şey aslında ilişkili nesneleri kullanmaktı, ben de yaptım (başka bir bağlamda):

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

Ama şunu yaparken bir EXC_BAD_ACCESS alıyorum:

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

Herhangi bir fikir?


11
Objective-C sınıfı kategorileri de örnek değişkenleri tanımlayamaz, peki bu özellikleri nasıl anladınız?
Martin R

Neden kötü bir erişim elde ettiğinizden emin değilim, ancak kodunuz çalışmamalıdır. SetAssociateObject ve getAssociatedObject öğelerine farklı değerler iletiyorsunuz. Anahtar olarak "shapeLayer" dizesini kullanmak yanlıştır, işaret ettiği şey değil, anahtar olan işaretçidir (aslında adresidir). Farklı adreslerde bulunan iki özdeş dizi iki farklı anahtardır. Jou'nun cevabını gözden geçirin ve xoAssociationKey'i global bir değişken olarak nasıl tanımladığına dikkat edin, böylece ayarlar / alırken aynı anahtar / işaretçi olacaktır.
SafeFastExpressive

Yanıtlar:


50

İlişkili nesneler API'sinin kullanımı biraz zahmetlidir. Bir yardımcı sınıfla kazan plakasının çoğunu kaldırabilirsiniz.

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

Amaç-c sınıfına bir özelliği daha okunaklı bir şekilde "ekleyebilmeniz" koşuluyla:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Çözüm gelince:

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

Tamam, Int, Bool vb. Depolamak istersem ne yapmalıyım?
Vyachaslav Gerchicov

1
Swift türlerini doğrudan nesne ilişkilendirme yoluyla saklamak mümkün değildir. Örneğin , istediğiniz tipte (Int, Bool, vb.) Ek erişimci çifti depolayabilir NSNumberveya NSValueyazabilirsiniz.
Wojciech Nagrodzki

1
UYARI Bu yapılar için çalışmaz, çünkü amaç-c çalışma zamanı kitaplığı yalnızca uyumlu sınıfları desteklerNSObjectProtocol
Charlton Provatas

2
@CharltonProvatas Bu API ile yapıların kullanılması mümkün değildir, AnyObject protokolüne uygun değildirler.
Wojciech Nagrodzki

@VyachaslavGerchicov [String]?bir yapıdır , bu Int, Bool ile aynı durumdur. Bir örnek NSArraykoleksiyonunu saklamak için kullanabilirsiniz NSString.
Wojciech Nagrodzki

184

Objective-C'de olduğu gibi, depolanmış özelliği mevcut sınıflara ekleyemezsiniz. Bir Objective-C sınıfını genişletiyorsanız ( UIViewkesinlikle biridir), depolanan özellikleri taklit etmek için İlişkili Nesneleri kullanmaya devam edebilirsiniz :

Swift 1 için

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

İlişkilendirme anahtarı, her ilişkilendirme için benzersiz olması gereken bir göstericidir. Bunun için, özel bir global değişken oluşturuyoruz ve &operatörün anahtarı olarak onun hafıza adresini kullanıyoruz . Swift'de işaretçilerin nasıl işlendiği hakkında daha fazla ayrıntı için Swift'i Kakao ve Objective-C ile Kullanma konusuna bakın .

Swift 2 ve 3 için GÜNCELLENDİ

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

Swift 4 için GÜNCELLENDİ

Swift 4'te çok daha basit. Holder yapısı, hesaplanmış mülkümüzün dünyaya göstereceği özel değeri içerecek ve bunun yerine depolanmış bir mülkiyet davranışı yanılsamasını verecektir.

Kaynak

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

2
@Yar bilmiyordum objc_AssociationPolicy, teşekkürler! Cevabı güncelledim
jou

2
Swift2'de objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom

1
@SimplGy, lütfen kodunuzu başka birinin cevabında düzenlemek yerine düzenlemenizi ek bir cevap olarak ekleyin.
JAL

11
Uzantıdaki özelliği kullanan 1'den fazla UIViewController örneğine sahip olmak istiyorsanız Swift4 çözümü çalışmaz. Lütfen kaynaktaki güncellemeleri kontrol edin.
2019

9
Swift 4 örneği birden çok örnekle çalışmaz ve muhtemelen burada olmamalıdır.
Colin Cornaby

36

Sanırım yukarıdakilerden daha temiz çalışan bir yöntem buldum çünkü herhangi bir global değişken gerektirmiyor. Buradan aldım: http://nshipster.com/swift-objc-runtime/

İşin özü, böyle bir yapı kullanmanızdır:

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

Swift 2 için GÜNCELLEME

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 Sizinki gibi kod uyguladım, ancak hızlı 2.0'a güncelleme yaparken, 'objc_AssociationPolicy)' türünde bir bağımsız değişken listesi ile 'UInt' türü başlatıcıyı çağıramıyorum hatası alıyorum. Sonraki
yorumdaki

Swift 2.0 için nasıl güncelleme yapılır? var postDescription: Dize? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) as? Dize} kümesi {if let newValue = newValue {objc_setAssociatedObject (öz, & AssociatedKeys.postDescription, NSString ?, uint olarak newValue (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
çalışmıyor: statik var, özelliğin değerinin her örnek için aynı olduğu anlamına gelir
Fry

@fry - benim için çalışmaya devam ediyor. Evet, anahtar statiktir, ancak belirli bir nesneyle ilişkilidir, nesnenin kendisi küresel değildir.
AlexK

15

Jou'nun işaret ettiği çözüm değer türlerini desteklemiyor , bu onlarla da iyi çalışıyor

Korumalar

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

Olası bir Sınıf uzantısı (Kullanım örneği):

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

Bu gerçekten harika bir çözüm, isteğe bağlı olmayan yapıları ve değerleri içeren başka bir kullanım örneği eklemek istedim. Ayrıca AssociatedKey değerleri basitleştirilebilir.

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

Ve Lifted sınıfını nasıl kullanırım?
3lvis

Neden buna ihtiyacın var? Ne için?
HepaKKes

Bunu genel olarak nasıl kullanıyorsun demek istedim, yardımın için teşekkürler :) "Nasıl kullanılır" örneği ekleyebilir misiniz lütfen?
3lvis

1
"Nasıl kullanılır" örneği, "Olası bir Sınıf uzantısı" başlığının hemen altında başlar. Kullanıcıların liftyöntemi ve Liftedsınıfı nasıl kullanacaklarını bilmeleri gerektiğini düşünmüyorum , ancak getAssociatedObject& setAssociatedObjectişlevleri kullanmalılar . Anlaşılır olması için başlığın yanına parantez arasına "Kullanım örneği" ekleyeceğim.
HepaKKes

1
Bu kesinlikle en iyi çözüm, ne yazık ki çok iyi açıklanmıyor. Sınıflar, yapılar ve diğer değer türleri ile çalışır. Özelliklerin de isteğe bağlı olması gerekmez. İyi iş.
picciano

13

Yeni depolamayla kategoriler (Swift uzantıları) tanımlayamazsınız; herhangi bir ek özellik saklanmak yerine hesaplanmalıdır . Sözdizimi Amaç C için çalışır çünkü @propertybir kategoride esasen "Alıcı ve ayarlayıcıyı sağlayacağım" anlamına gelir. Swift'de, hesaplanmış bir mülk elde etmek için bunları kendiniz tanımlamanız gerekir; gibi bir şey:

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

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

İyi çalışmalı. Unutmayın, yeni değerleri ayarlayıcıda saklayamazsınız, sadece mevcut mevcut sınıf durumu ile çalışabilirsiniz.


7

0.02 dolarım. Bu kod Swift 2.0'da yazılmıştır

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

Pek çok çözüm denedim ve bunun bir sınıfı fazladan değişken parametrelerle genişletmenin tek yolu olduğunu buldum.


İlginç görünüyor, ancak reddediyor protokollerle çalışmıyor, type 'AssociatedKeys' cannot be defined within a protocol extension
Ian Bytchek

6

Neden objc çalışma zamanına güveniyorsunuz? Anlamıyorum. Aşağıdakine benzer bir şey kullanarak, yalnızca saf bir Swift yaklaşımı kullanarak depolanan bir mülkün neredeyse aynı davranışını elde edeceksiniz :

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
Zeki! Bu yaklaşımın olası bir dezavantajı bellek yönetimidir. Konak nesnesi serbest bırakıldıktan sonra, özelliği sözlükte bulunmaya devam eder ve bu, çok sayıda nesne ile kullanılırsa bellek kullanımı açısından pahalı olabilir.
Charlton Provatas

Muhtemelen nesnelere (öz) zayıf referans içeren statik bir sarmalayıcı liste değişkenini tutabiliriz. Ve _myComputedProperty sözlüğünden girişi ve Wrapper'ın didSet yöntemindeki sarmalayıcıların statik liste değişkenini kaldırın (Nesne yeniden tahsis edildiğinde Wrapper sınıfımızdaki didSet çağrılır çünkü zayıf referansımız sıfır ile yeni bir değer ayarlandığından).
Arjuna

5

Tamamen Swift kodlamayı tercih ediyorum ve Objective-C mirasına güvenmiyorum. Bu nedenle, iki avantajı ve iki dezavantajı olan saf Swift çözümü yazdım.

Avantajları:

  1. Saf Swift kodu

  2. Sınıflar ve tamamlamalar üzerinde veya daha spesifik olarak Anynesne üzerinde çalışır

Dezavantajları:

  1. Kod, willDeinit()bellek sızıntılarını önlemek için belirli sınıf örneğine bağlı nesneleri serbest bırakmak için yöntemi çağırmalıdır

  2. Bu kesin örnek için doğrudan UIView'a uzantı yapamazsınız çünkü UIView için bir var frameuzantıdır, sınıfın bir parçası değildir.

DÜZENLE:

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
ExtensionPropertyStorage tüm örnekler arasında paylaşıldığı için bu çalışmaz. Bir örnek için bir değer ayarlarsanız, tüm örnekler için değer ayarlamış olursunuz.
picciano

Savaşmak iyi değil çünkü işlevlerle uğraşıyor. Şimdi daha iyi ve amaçlandığı gibi çalışıyor.
vedrano

@picciano extensionPropertyStorage , tasarım gereği tüm örneklerle paylaşılır. İlk önce UILabel örneğine ( NSObject) ve sonra özelliğine ( [String: Any]) başvuran global değişkendir (Sözlük ).
vedrano

@picciano Sanırım bu classmülkler için çalışıyor ;)
Nicolas Miari

framebir örnek özelliğidir. Sınıf mülkiyeti nereden geliyor?
vedrano

3

Obj-c Kategorileri ile örnek değişkenleri değil, yalnızca yöntemleri ekleyebilirsiniz.

Örneğinizde, alıcı ve ayarlayıcı yöntem bildirimleri eklemek için bir kısayol olarak @ özelliği kullandınız. Hala bu yöntemleri uygulamanız gerekiyor.

Benzer şekilde, Swift'de örnek yöntemleri, hesaplanan özellikler vb. Eklemek için kullanım uzantıları ekleyebilirsiniz, ancak saklanan özellikleri ekleyemezsiniz.


2

Ben de bir EXC_BAD_ACCESS sorun oluşturmaya değer elde objc_getAssociatedObject()ve objc_setAssociatedObject()bir nesne olmalıdır. Ve objc_AssociationPolicyNesne ile eşleşmelidir.


3
Bu soruya gerçekten cevap vermiyor. Farklı bir sorunuz varsa, Soru Sor'a tıklayarak sorabilirsiniz . Yeterli itibara sahip olduğunuzda bu soruya daha fazla dikkat çekmek için bir ödül de ekleyebilirsiniz . - Yorumdan
AtheistP3ace

@ AtheistP3ace Bunun için çok üzgünüm. Soruya bir yorum eklemeye çalışıyorum ama yeterince itibarım yok. Ben de soruyu cevaplamaya çalışıyorum.
RuiKQ

2

Objc_setAssociatedObject'i burada birkaç cevapta belirtildiği gibi kullanmayı denedim, ancak birkaç kez başarısız olduktan sonra geri adım attım ve buna ihtiyacım olmadığını anladım. Buradaki fikirlerden birkaçını ödünç alarak, fazladan verilerimin (bu örnekte MyClass) ilişkilendirmek istediğim nesne tarafından indekslenmiş olduğu bir diziyi depolayan bu kodu buldum:

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)

bellek sızıntılarına neden olmaması için extraData'nın otomatik olarak nasıl temizleneceğine dair bir fikriniz var mı?
DoubleK

Aslında bunu görünümlerle kullanmıyordum, benim durumumda kullandığım sınıfta yalnızca sınırlı sayıda nesneyi somutlaştırıyorum, bu yüzden bellek sızıntılarını gerçekten düşünmedim. Bunu yapmanın otomatik bir yolunu düşünemiyorum, ancak yukarıdaki ayarlayıcıda, eğer değer == nil ise öğeyi kaldırmak için mantık ekleyebilir ve nesneye artık ihtiyacınız kalmadığında değeri nil olarak ayarlayabilirsiniz veya belki didReceiveMemoryWarning veya başka bir şeyde.
Dan

Tnx @Dan, değeri sıfır olarak ayarlamak için viewWillDisapper kullandım ve iyi çalışıyor, şimdi deinit çağrıldı ve her şey yolunda gidiyor. Bu çözümü yayınlamak için Tnx
DoubleK

2

İşte basitleştirilmiş ve daha etkileyici bir çözüm. Hem değer hem de başvuru türleri için çalışır. Kaldırma yaklaşımı @ HepaKKes cevabından alınmıştır.

Dernek kodu:

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

Kullanım örneği:

1) Uzantı oluşturun ve özellikleri onunla ilişkilendirin. Hem değer hem de başvuru türü özelliklerini kullanalım.

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) Artık normal mülkler olarak kullanabilirsiniz:

    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

Daha fazla detay:

  1. Değer türlerini sarmak için kaldırma gereklidir.
  2. Varsayılan ilişkili nesne davranışı korunur. İlişkili nesneler hakkında daha fazla bilgi edinmek istiyorsanız, bu makaleyi incelemenizi tavsiye ederim .

1

Objective-C ile ilişkili nesneleri ve Swift 3 ve Swift 4 için hesaplanan özellikleri kullanmanın başka bir örneği

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

Bir UIView için özel bir dize niteliği ayarlamak istiyorsanız, Swift 4'te bunu böyle yaptım

Bir UIView uzantısı oluşturun

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

Bu uzantıyı kullanmak için

let key = "COLOR"

let redView = UIView() 

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

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

1

Statik haritayı şu şekilde genişleyen sınıfa depolamaya ne dersiniz:

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
neden bu cevabın olumlu oyu yok. Sonuçta akıllı çözüm.
byJeevan

Bu işe yarıyor, ancak bu yaklaşımın burada belirtildiği gibi kusurlu olduğunu düşünüyorum medium.com/@valv0/…
Teffi

0

Nesc_getAssociatedObject, objc_setAssociatedObject kullanarak özellikleri şanssız bir şekilde depolamaya çalıştım. Amacım, metin girişi karakter uzunluğunu doğrulamak için UITextField için bir uzantı oluşturmaktı. Aşağıdaki kod benim için iyi çalışıyor. Umarım bu birine yardımcı olur.

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

Global özel değişkenler? Bunun küresel olduğunun _minve _maxUITextField'ın tüm örneklerinde aynı olacağının farkında mısınız? Sizin için işe yarasa bile, bu cevap ilgisizdir çünkü Marcos örnek değişkenleri sormaktadır .
kelin

Evet haklısın, bu tüm örnekler için çalışmıyor. Yapısalda min ve max değeri depolayarak düzelttim. Konu dışı için özür dilerim.
Vadims Krutovs

0

İşte işe yarayan bir alternatif

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

Bu çözümü daha pratik buldum

Swift 3 için GÜNCELLENDİ

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

}

... sonra kodunuzda

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}

Bunu yapmanın doğru yolu budur - ancak sorunun sorulduğu gibi uzantıda depolanan bir özellik değildir.
UKDataGeek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.