Як зберігати властивості у Swift, так само, як і у Objective-C?


132

Я перемикаю програму з Objective-C на Swift, у якої є пара категорій із збереженими властивостями, наприклад:

@interface UIView (MyCategory)

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

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

@end

Оскільки розширення Swift не приймають такі збережені властивості, я не знаю, як підтримувати ту саму структуру, що і код Objc. Збережені властивості дуже важливі для мого додатка, і я вважаю, що Apple, мабуть, створила якесь рішення для цього в Swift.

За словами jou, те, що я шукав, насправді використовував пов'язані об'єкти, так я і зробив (в іншому контексті):

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

Але я отримую EXC_BAD_ACCESS під час виконання:

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

Будь-які ідеї?


11
Категорії класів Objective-C також не можуть визначати змінні екземпляри, тож як ви зрозуміли ці властивості?
Martin R

Не впевнений, чому ви отримуєте поганий доступ, але ваш код не повинен працювати. Ви передаєте різні значення setAssociateObject і getAssociatedObject. Використовувати рядок "shapeLayer" як ключ невірно, ключовим є саме вказівник (це фактично адреса), а не те, на що він вказує. Два однакових рядка, що знаходяться за різною адресою, - це дві різні клавіші. Перегляньте відповідь Джо і помітіть, як він визначив xoAssociationKey як глобальну змінну, тому це той самий ключ / покажчик при встановленні / отриманні.
SafeFastExpressive

Відповіді:


50

API асоційованих об'єктів трохи громіздкий у використанні. Більшу частину котлової панелі можна зняти за допомогою допоміжного класу.

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

За умови, що ви можете "додати" властивість до класу target-c більш читаним способом:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Щодо рішення:

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

Добре, що робити, якщо я хочу зберігати Int, Bool тощо?
Вячаслав Герчичов

1
Зберігати типи Swift неможливо безпосередньо через об'єднання об'єктів. Ви можете зберігати, наприклад, NSNumberабо NSValueзаписувати додаткову пару аксесуарів, які були б потрібних типів (Int, Bool тощо).
Войцех Нагродзкі

1
ПОПЕРЕДЖЕННЯ Це не працює для структур, тому що бібліотека виконання-c Objective-c підтримує лише класи, які відповідаютьNSObjectProtocol
Charlton Provatas

2
@CharltonProvatas Неможливо використовувати структури з цим API, вони не відповідають протоколу AnyObject.
Войцех Нагродзкі

@VyachaslavGerchicov [String]?- це структура, це той самий випадок, що і для Int, Bool. Ви можете використовувати NSArrayдля збереження колекції NSStringекземплярів.
Войцех Нагродзкі

184

Як і в Objective-C, ви не можете додавати збережені властивості до існуючих класів. Якщо ви розширюєте клас Objective-C ( UIViewце, безумовно, один), ви все одно можете використовувати асоційовані об’єкти для імітації збережених властивостей:

для 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))
        }
    }
}

Ключ асоціації - це вказівник, який повинен бути унікальним для кожної асоціації. Для цього ми створюємо приватну глобальну змінну і використовуємо її пам'ять як ключову з &оператором. Докладніше про те, як вказівники обробляються в Swift, див. У розділі Використання Swift з какао та Objective-C .

ОНОВЛЕНО для Swift 2 та 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)
        }
    }
}

ОНОВЛЕНО для Swift 4

У Swift 4 це набагато простіше. Структура Holder міститиме приватну цінність, яку наша обчислювана властивість буде виставляти світові, створюючи натомість ілюзію збереженої поведінки власності.

Джерело

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

2
@Yar Не знав про це objc_AssociationPolicy, дякую! Я оновив відповідь
Jou

2
У Swift2 ви повинні використовувати objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Том,

1
@SimplGy, будь ласка, додайте свої зміни як додаткову відповідь, а не редагування свого коду в чужу відповідь.
JAL

11
Рішення Swift4 не працює, якщо ви хочете мати більше 1 примірника UIViewController, який використовує властивість з розширення. Перевірте оновлення в джерелі.
МСР

9
Приклад Swift 4 не працює з кількома екземплярами, і, мабуть, тут не повинно бути.
Колін Корнабі

36

Тому я думаю, що я знайшов метод, який працює чистіше, ніж описаний вище, оскільки він не потребує глобальних змінних. Я отримав це звідси: http://nshipster.com/swift-objc-runtime/

Суть у тому, що ви використовуєте структуру так:

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

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 Я реалізував такий код, як ваш, але під час оновлення до швидкої версії 2.0 я отримую помилку, не можу викликати ініціалізатор для типу 'UInt' зі списком аргументів типу 'objc_AssociationPolicy)'. Код у наступному коментарі
user2363025

Як оновити для swift 2.0? var postОпис: Рядок? {get {return objc_getAssociatedObject (самоврядування та AssociatedKeys.postDescription) як? Рядок} безліч {якщо дозволити новое_значеніе = новое_значеніе {objc_setAssociatedObject (я, & AssociatedKeys.postDescription, новое_значеніе , як NSString?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
не працює: статичний var означає, що значення властивості однакове для всіх примірників
Fry

@fry - це продовжує працювати на мене. Так, ключ є статичним, але його пов'язано з конкретним об'єктом, сам об'єкт не є глобальним.
AlexK

15

Рішення, на яке вказує jou , не підтримує типи значень , це добре працює і з ними

Обгортки

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

Це дійсно таке чудове рішення, я хотів додати ще один приклад використання, який включав структури та значення, які не є необов’язковими. Також значення AssociatedKey можна спростити.

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

І як я можу використовувати клас "Піднятий"?
3lvis

Навіщо це вам потрібно? Для чого?
HepaKKes

Я мав на увазі, як ви це взагалі використовуєте, дякую за вашу допомогу :) Чи можете ви додати приклад "Як це використовувати"?
3lvis

1
Приклад "Як використовувати" розпочнеться прямо під заголовком "Можливе розширення класу". Я не думаю, що користувачі не повинні знати, як використовувати liftметод і Liftedклас, вони повинні використовувати однако getAssociatedObject& setAssociatedObjectфункції. Я додамо "Приклад використання" між дужками поруч із заголовком задля наочності.
HepaKKes

1
Це, звичайно, найкраще рішення, занадто погано це не дуже добре пояснено. Він працює з класами, структурами та іншими значеннями. Властивості також не повинні бути необов’язковими. Хороша робота.
picciano

13

Ви не можете визначити категорії (розширення Swift) за допомогою нового сховища; будь-які додаткові властивості повинні бути обчислені, а не збережені. Синтаксис працює для Цілі C, оскільки @propertyв категорії по суті означає "Я надаю геттер і сеттер". У Swift вам потрібно буде визначити їх самостійно, щоб отримати обчислене властивість; щось на зразок:

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

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

Має працювати нормально. Пам'ятайте, що ви не можете зберігати нові значення в сетері, працюйте лише з наявним наявним станом класу.


7

Мої 0,02 долара. Цей код написаний у 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)
            }
        }
    }
}

Я спробував багато рішень, і виявив, що це єдиний спосіб насправді розширити клас із додатковими змінними параметрами.


Виглядає цікаво, але відмова не працює з протоколами, type 'AssociatedKeys' cannot be defined within a protocol extension
Ian Bytchek

6

Чому покладатися на objc час виконання? Я не розумію. Використовуючи щось на кшталт наведеного нижче, ви досягнете майже однакової поведінки збереженої властивості, використовуючи лише чистий підхід Swift :

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
Розумний! Один з можливих недоліків такого підходу - управління пам’яттю. Після того, як розміщується об'єкт-хост, його властивість все ще буде існувати в словнику, що потенційно може дорого використовувати пам'ять, якщо це буде використано з великою кількістю об'єктів.
Чарлтон Проватас

Ймовірно, ми можемо містити статичну змінну списку обгортки, яка містить слабке посилання на об'єкти (self). І видаліть запис із словника _myComputedProperty, а також змінну статичного списку обгортків у методі didSet Wrapper (Коли об’єкт перерозподіляється didSet всередині нашого класу Wrapper, викликається, оскільки для нашого слабкого посилання встановлюється нове значення з нулем).
Арджуна

5

Я вважаю за краще робити код у чистому Swift, а не покладатися на спадщину Objective-C. Через це я написав чисте рішення Swift з двома перевагами та двома недоліками.

Переваги:

  1. Чистий код Свіфта

  2. Працює над класами та доповненнями, а точніше над Anyоб’єктом

Недоліки:

  1. Код повинен викликати метод willDeinit()для випуску об'єктів, пов'язаних з конкретним екземпляром класу, щоб уникнути витоків пам'яті

  2. Ви не можете зробити розширення безпосередньо до UIView для цього точного прикладу, оскільки var frameце розширення для UIView, а не частина класу.

Редагувати:

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

Війна це не добре, тому що псуєш функції. Тепер краще і працює за призначенням.
ведрано

@picciano extensionPropertyStorage є спільним для всіх примірників дизайном. Це глобальна змінна (словник), яка спочатку видаляє посилання на екземпляр UILabel ( NSObject), а потім його властивість ( [String: Any]).
vedrano

@picciano Я думаю, це працює для classвластивостей;)
Ніколас Міарі

frameє властивістю екземпляра. Звідки береться майно класу?
vedrano

3

За допомогою Obj-c категорій можна додавати лише методи, а не змінні екземпляри.

У прикладі ви використовували @property як ярлик для додавання оголошень методу getter та setter. Вам ще потрібно застосувати ці методи.

Аналогічно в Swift ви можете додати розширення використання для додавання методів екземпляра, обчислених властивостей тощо, але не збережених властивостей.


2

Я також отримую проблему EXC_BAD_ACCESS. Значення в objc_getAssociatedObject()і objc_setAssociatedObject()повинно бути Об'єктом. І objc_AssociationPolicyмає відповідати Об'єкту.


3
Це насправді не відповідає на питання. Якщо у вас інше питання, ви можете задати його, натиснувши Задати питання . Ви також можете додати щедрості, щоб привернути більше уваги до цього питання, як тільки у вас буде достатня репутація . - З огляду
AtheistP3ace

@ AtheistP3ace Мені дуже шкода цього. Я намагаюся додати коментар до питання. Але у мене недостатньо репутації. Тому я намагаюся відповісти на питання.
RuiKQ

2

Я спробував використовувати objc_setAssociatedObject, як згадувалося в кількох відповідях тут, але після того, як кілька разів не вдався до нього, я відступив назад і зрозумів, що немає причини, що мені це потрібно. Позичивши деякі ідеї тут, я придумав цей код, який просто зберігає масив усіх моїх додаткових даних (MyClass в цьому прикладі), індексованих об'єктом, з яким я хочу його пов’язати:

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)

Ви маєте ідею, як автоматично очистити extraData, щоб це не спричиняло витоку пам'яті?
DoubleK

Я насправді не використовував це з переглядами, в моєму випадку я лише коли-небудь інстанціював кінцеву кількість об'єктів у класі, який я використовував, тому я не дуже розглядав витоки пам'яті. Я не можу придумати автоматичний спосіб зробити це, але я вважаю, що в сеттері вище ви можете додати логіку для видалення елемента, якщо значення == нуль, а потім встановити значення нуля, коли вам більше не потрібен об'єкт, або можливо в didReceiveMemoryWarning чи щось.
Дан

Tnx @Dan, я використовував viewWillDisapper, щоб встановити значення nil, і він працює добре, тепер deinit називається і все працює добре. Tnx для публікації цього рішення
DoubleK

2

Ось спрощене і виразніше рішення. Він працює як для значень, так і для еталонів. Підхід підйому взято з відповіді @HepaKKes.

Код асоціації:

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

Приклад використання:

1) Створіть розширення та приєднайте до нього властивості. Давайте скористаємося як значеннями, так і властивостями опорного типу.

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) Тепер ви можете використовувати як звичайні властивості:

    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

Детальніше:

  1. Підйом необхідний для загортання типів значень.
  2. Поведінка асоційованого об'єкта за замовчуванням зберігається. Якщо ви хочете дізнатися більше про асоційовані об’єкти, рекомендую переглянути цю статтю .

1

Ще один приклад використання об’єктів, пов’язаних з Objective-C та обчислених властивостей для Swift 3 та 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

якщо ви хочете встановити атрибут UIView на власному рядку, саме так я це зробив у Swift 4

Створіть розширення 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
    }
}

Щоб використовувати це розширення

let key = "COLOR"

let redView = UIView() 

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

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

1

Як щодо зберігання статичної карти до класу, який розширюється так:

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
чому ця відповідь не підтверджує. Зрештою, розумне рішення.
byJeevan

Це працює , але я думаю , що цей підхід має недоліки , як зазначено тут medium.com/@valv0 / ...
Теффі

0

Я намагався зберігати властивості, використовуючи objc_getAssociatedObject, objc_setAssociatedObject без жодної удачі. Моєю метою було створити розширення для UITextField, щоб перевірити довжину символів для введення тексту. Наступний код для мене добре працює. Сподіваюся, що це комусь допоможе.

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

Глобальні приватні змінні? Ви усвідомлюєте це _minта _maxє глобальними і будуть однаковими у всіх примірниках UITextField? Навіть якщо це працює для вас, ця відповідь не пов'язана, оскільки Маркос запитує про змінні екземпляра .
келін

Так, ви праві, це працює не для всіх випадків. I Виправлено, зберігаючи min та max значення в struct. Вибачте за тему.
Вадим Крутовс

0

Ось альтернатива, яка також працює

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

Я вважав це рішення більш практичним

ОНОВЛЕНО для 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)
    }

}

... тоді у своєму коді

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}

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