Как сохранить свойства в 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.

Как сказал Джоу, то, что я искал, на самом деле использовало связанные объекты, поэтому я сделал (в другом контексте):

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 также не могут определять переменные экземпляра, так как же вы реализовали эти свойства?
Мартин Р

Не знаю, почему у вас плохой доступ, но ваш код не должен работать. Вы передаете разные значения в 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 и т. Д.).
Wojciech Nagrodzki 02

1
ВНИМАНИЕ! Это не работает для структур, поскольку библиотека времени выполнения NSObjectProtocol
target

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

@VyachaslavGerchicov [String]?- это структура, это тот же случай, что и для Int, Bool. Вы можете использовать NSArrayдля хранения коллекции NSStringэкземпляров.
Wojciech Nagrodzki

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 с Какао и Objective-C для более подробной информации о том, как обрабатываются указатели в Swift.

ОБНОВЛЕНО для 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
@ Яр Не знаю 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 Я реализовал код, подобный вашему, но при обновлении до Swift 2.0 я получаю сообщение об ошибке: не удается вызвать инициализатор для типа UInt со списком аргументов типа objc_AssociationPolicy). Код в следующем комментарии
user2363025

Как обновиться на Swift 2.0? var postDescription: String? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) как? Строка} множество {если позволить новое_значение = новое_значение {objc_setAssociatedObject (я, & AssociatedKeys.postDescription, новое_значение , как NSString ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
не работает: статическая переменная означает, что значение свойства одинаково для всех экземпляров
Фрай

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

А как использовать класс Lifted?
3lvis

Зачем тебе это надо? Зачем?
HepaKKes

Я имел в виду, как вы используете это в целом, спасибо за вашу помощь :) Можете ли вы добавить пример "Как использовать", пожалуйста?
3lvis

1
Пример «Как использовать» будет начинаться прямо под заголовком «Возможное расширение класса». Я не думаю, что пользователи должны знать, как использовать liftметод и Liftedкласс, они должны использовать однако getAssociatedObject& setAssociatedObjectфункции. Я добавлю «Пример использования» в скобках рядом с заголовком для ясности.
HepaKKes 30.09.15

1
Это, безусловно, лучшее решение, к сожалению, оно не очень хорошо объяснено. Он работает с классами, структурами и другими типами значений. Свойства также не обязательно должны быть дополнительными. Хорошая работа.
Пиччано

13

Вы не можете определять категории (расширения Swift) с новым хранилищем; любые дополнительные свойства должны быть вычислены, а не сохранены. Синтаксис работает для Objective 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...
Ян Битчек

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
Умная! Одним из возможных недостатков этого подхода является управление памятью. После освобождения объекта хоста его свойство все еще будет существовать в словаре, что потенциально может стать дорогостоящим при использовании памяти, если оно используется с большим количеством объектов.
Charlton Provatas 07

Вероятно, мы можем хранить статический список переменных оболочек, которые содержат слабую ссылку на объекты (self). И удалите запись из словаря _myComputedProperty, а также статическую переменную списка оболочек в методе didSet Wrapper (когда объект перераспределяется, вызывается didSet внутри нашего класса Wrapper, поскольку наша слабая ссылка устанавливает новое значение с nil).
Арджуна

5

Я предпочитаю писать код на чистом Swift и не полагаться на наследие Objective-C. Из-за этого я написал чистое решение Swift с двумя преимуществами и двумя недостатками.

Преимущества:

  1. Чистый код Swift

  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 используется всеми экземплярами. Если вы устанавливаете значение для одного экземпляра, вы устанавливаете значение для всех экземпляров.
Пиччано

Это нехорошо, потому что испортились с функциями. Теперь это лучше и работает по назначению.
vedrano

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

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

frameявляется свойством экземпляра. Откуда взялась классовая собственность?
vedrano

3

С помощью категорий Obj-c вы можете добавлять только методы, но не переменные экземпляра.

В вашем примере вы использовали @property как ярлык для добавления объявлений методов получения и установки. Вам все еще нужно реализовать эти методы.

Аналогично, в 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

На самом деле я не использовал это с представлениями, в моем случае я когда-либо создавал только конечное количество объектов в классе, который я использовал, поэтому я действительно не рассматривал утечки памяти. Я не могу придумать автоматический способ сделать это, но я полагаю, что в установщике выше вы могли бы добавить логику для удаления элемента, если значение == nil, а затем установить значение на nil, когда вам больше не нужен объект, или может быть, в 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
почему у этого ответа нет голосов. В конце концов, умное решение.
Автор: Джеван,

Это работает, но я думаю, что этот подход ошибочен, как указано здесь 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? Даже если это сработает для вас, этот ответ не имеет отношения, потому что Маркос спрашивает о переменных экземпляра .
kelin

Да, вы правы, это работает не для всех случаев. I Исправлено сохранением минимального и максимального значений в структуре. Извините за не по теме.
Вадим Крутов 03

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.