Objective-C에서와 같은 방식으로 Swift에 속성을 저장하는 방법은 무엇입니까?


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"를 키로 사용하는 것이 잘못되었습니다. 키가 가리키는 포인터 (실제로 주소)입니다. 다른 주소에있는 두 개의 동일한 문자열은 서로 다른 키입니다. Jou의 답변을 검토하고 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) }
    }
}

보다 읽기 쉬운 방식으로 objective-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 등을 저장하려면 어떻게해야합니까?
Vyachaslav Gerchicov 2016 년

1
객체 연결을 통해 Swift 유형을 직접 저장할 수는 없습니다. 예를 들어 저장 NSNumber하거나 NSValue원하는 유형 (Int, Bool 등)의 접근자를 추가로 작성할 수 있습니다.
Wojciech Nagrodzki

1
경고 objective-c 런타임 라이브러리는 다음을 준수하는 클래스 만 지원하기 때문에 구조체에는 작동하지 않습니다.NSObjectProtocol
Charlton Provatas

2
@CharltonProvatas이 API에는 구조체를 사용할 수 없으며 AnyObject 프로토콜을 준수하지 않습니다.
Wojciech Nagrodzki

@VyachaslavGerchicov [String]?는 구조체입니다 .Int, Bool과 동일합니다. 인스턴스 NSArray모음을 유지하는 데 사용할 수 있습니다 NSString.
Wojciech Nagrodzki

184

Objective-C에서와 같이 기존 클래스에 저장된 속성을 추가 할 수 없습니다. Objective-C 클래스를 확장하는 경우 ( UIView확실히 하나임) 연결된 객체 를 사용 하여 저장된 속성을 에뮬레이션 할 수 있습니다.

스위프트 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에서 포인터를 처리하는 방법에 대한 자세한 내용 은 Cocoa 및 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을 사용해야합니다.
Tom

1
@SimplGy 다른 사람의 답변으로 코드를 편집하는 대신 편집 내용을 추가 답변으로 추가하십시오.
JAL

11
확장의 속성을 사용하는 UIViewController 인스턴스가 두 개 이상 필요한 경우 Swift4 솔루션이 작동하지 않습니다. 소스에서 업데이트를 확인하십시오.
iur

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

스위프트 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으로 업데이트 할 때 'objc_AssociationPolicy)'의 인수 목록으로 'UInt'유형의 이니셜 라이저를 호출 할 수 없다는 오류가 발생합니다. 다음 댓글의 코드
user2363025

swift 2.0을 업데이트하는 방법? var postDescription : 문자열? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription)? String} set {if let newValue = newValue {objc_setAssociatedObject (self, & AssociatedKeys.postDescription, NSString as newValue ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}
user2363025

1
작동하지 않음 : 정적 변수는 속성 값이 모든 인스턴스에 대해 동일 함을 의미합니다
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)
        }
    }
}

그리고 Lifted 클래스는 어떻게 사용합니까?
3lvis

왜 그런가요? 무엇 때문에?
HepaKKes

나는 당신의 도움에 감사드립니다. :) 어떻게 "사용법"예제를 추가 할 수 있습니까?
3lvis

1
"사용 방법"예제는 "가능한 클래스 확장"헤더 바로 아래에서 시작됩니다. 사용자가 lift메소드와 Lifted클래스 를 사용하는 방법 을 알아야한다고 생각하지는 않지만 getAssociatedObject& & setAssociatedObject함수를 사용해야 합니다. 명확성을 위해 머리글 옆에 괄호 사이에 "사용 예"를 추가하겠습니다.
HepaKKes

1
이것은 확실히 가장 좋은 해결책이며, 너무 나쁘게 설명되어 있지 않습니다. 클래스, 구조체 및 기타 값 유형과 함께 작동합니다. 속성도 옵션 일 필요는 없습니다. 잘 하셨어요.
picciano

13

새 저장소로 범주 (Swift 확장)를 정의 할 수 없습니다. 추가 속성은 저장하지 않고 계산 해야합니다 . @property범주에서 본질적으로 "게터와 세터를 제공하겠습니다"를 의미 하므로 구문은 목표 C에 적용 됩니다. Swift에서는 계산 된 속성을 얻기 위해이를 직접 정의해야합니다. 같은 :

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

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

잘 작동합니다. setter에 새 값을 저장할 수 없으며 기존의 사용 가능한 클래스 상태에서만 작동합니다.


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 런타임에 의존하는 이유는 무엇입니까? 나는 요점을 모른다. 다음과 같은 것을 사용하면 순수한 스위프트 접근 방식 만 사용하여 저장된 속성과 거의 동일한 동작을 달성 할 수 있습니다 .

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 2018 년

아마도 우리는 객체 (자체)에 대한 약한 참조를 포함하는 래퍼의 정적 목록 변수를 보유 할 수 있습니다. Wrapper의 didSet 메소드에서 래퍼의 정적 목록 변수뿐만 아니라 _myComputedProperty 사전에서 항목을 제거하십시오 (객체가 재 할당 될 때 Wrapper 클래스 내부의 didSet이 호출되면 약한 참조가 nil로 새 값을 설정하므로).
Arjuna

5

순수한 Swift에서 코드를 작성하는 것을 선호하며 Objective-C 유산에 의존하지 않습니다. 이 때문에 두 가지 장점과 두 가지 단점이있는 순수한 Swift 솔루션을 작성했습니다.

장점 :

  1. 순수한 스위프트 코드

  2. 수업 및 완료 또는 더 구체적으로 Any객체에서 작동

단점 :

  1. 코드는 willDeinit()메모리 누수를 피하기 위해 특정 클래스 인스턴스에 연결된 객체를 해제하는 메소드 를 호출해야합니다.

  2. var frame클래스의 일부가 아닌 UIView에 대한 확장이므로이 정확한 예제를 위해 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

함수를 어지럽히 기 때문에 좋지 않습니다. 이제 더 좋으며 의도 한대로 작동합니다.
vedrano

@picciano extensionPropertyStorage 는 의도적 으로 모든 인스턴스와 공유됩니다. UILabel 인스턴스 ( NSObject)와 그 속성 ( [String: Any]) 을 먼저 참조 해제하는 것은 전역 변수 (사전)입니다 .
vedrano

@picciano 나는 이것이 class속성에 효과가 있다고 생각한다 ;)
Nicolas Miari

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

나는 실제로 이것을 뷰와 함께 사용하지 않았으며, 필자의 경우 사용중인 클래스에서 유한 한 수의 객체 만 인스턴스화했기 때문에 실제로 메모리 누수를 고려하지 않았습니다. 나는 이것을 자동으로 수행하는 방법을 생각할 수 없지만 위의 setter에서 value == nil 인 경우 요소를 제거하는 논리를 추가 한 다음 더 이상 객체가 필요하지 않을 때 값을 nil로 설정할 수 있다고 가정합니다. 아마 didReceiveMemoryWarning 또는 무엇인가에 있습니다.
Dan

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

Swift 3Swift 4에 Objective-C 관련 객체 및 계산 된 속성을 사용하는 또 다른 예

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
이 답변에 공감대가없는 이유는 무엇입니까? 결국 스마트 솔루션.
by Jeeevan

이것은 작동하지만 여기에 언급 된 대로이
Teffi

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의 모든 인스턴스에서 동일합니다? Marcos가 인스턴스 변수 에 대해 묻기 때문에 이것이 효과가 있더라도이 대답은 관련이 없습니다 .
kelin

그렇습니다. 모든 인스턴스에서 작동하지는 않습니다. 구조체에 최소값과 최대 값을 저장하여 수정했습니다. 주제를 벗어서 죄송합니다.
Vadims Krutovs

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

이 솔루션이 더 실용적이라는 것을 알았습니다.

스위프트 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.