Làm cách nào để lưu trữ các thuộc tính trong Swift, giống như cách tôi đã có trên Objective-C?


132

Tôi đang chuyển đổi một ứng dụng từ Objective-C sang Swift, ví dụ tôi có một vài loại với các thuộc tính được lưu trữ:

@interface UIView (MyCategory)

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

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

@end

Vì các tiện ích mở rộng Swift không chấp nhận các thuộc tính được lưu trữ như thế này, tôi không biết cách duy trì cấu trúc giống như mã Objc. Các thuộc tính được lưu trữ thực sự quan trọng đối với ứng dụng của tôi và tôi tin rằng Apple phải tạo ra một số giải pháp để thực hiện điều đó trong Swift.

Như đã nói bởi jou, những gì tôi đang tìm kiếm thực sự là sử dụng các đối tượng liên quan, vì vậy tôi đã làm (trong một bối cảnh khác):

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

Nhưng tôi nhận được EXC_BAD_ACCESS khi thực hiện:

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

Có ý kiến ​​gì không?


11
Các loại lớp Objective-C cũng không thể định nghĩa các biến thể hiện, vậy làm thế nào bạn nhận ra các thuộc tính đó?
Martin R

Không chắc chắn tại sao bạn nhận được quyền truy cập xấu, nhưng mã của bạn không hoạt động. Bạn đang chuyển các giá trị khác nhau cho setAssociateObject và getAssociatedObject. Sử dụng chuỗi "shapeLayer" làm khóa là sai, đó là con trỏ (thực tế là địa chỉ) đó là khóa, không phải là điểm mà nó trỏ đến. Hai chuỗi giống nhau cư trú tại các địa chỉ khác nhau là hai khóa khác nhau. Xem lại câu trả lời của Jou và chú ý cách anh ta xác định xoAssociationKey là một biến toàn cục, do đó, đó là cùng một khóa / con trỏ khi cài đặt / nhận.
SafeFastExpressive

Câu trả lời:


50

API đối tượng liên kết là một chút rườm rà để sử dụng. Bạn có thể loại bỏ hầu hết các mẫu soạn sẵn với lớp trợ giúp.

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

Với điều kiện bạn có thể "thêm" một thuộc tính vào lớp object-c theo cách dễ đọc hơn:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Đối với giải pháp:

extension CALayer {

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

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

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

Ok phải làm gì nếu tôi muốn lưu trữ Int, Bool và vv?
Vyachaslav Gerchicov

1
Không thể lưu trữ các loại Swift thông qua liên kết đối tượng trực tiếp. Bạn có thể lưu trữ ví dụ NSNumberhoặc NSValueviết cặp truy cập bổ sung sẽ thuộc loại bạn muốn (Int, Bool, v.v.).
Wojciech Nagrodzki

1
CẢNH BÁO Điều này không hoạt động cho các cấu trúc, bởi vì thư viện thời gian chạy mục tiêu c chỉ hỗ trợ các lớp phù hợp vớiNSObjectProtocol
Charlton Provatas

2
@CharltonProvatas Không thể sử dụng các cấu trúc với API này, chúng không tuân thủ giao thức AnyObject.
Wojciech Nagrodzki

@VyachaslavGerchicov [String]?là một cấu trúc, đây là trường hợp tương tự như đối với Int, Bool. Bạn có thể sử dụng NSArrayđể giữ một bộ sưu tập các NSStringtrường hợp.
Wojciech Nagrodzki

184

Như trong Objective-C, bạn không thể thêm thuộc tính được lưu trữ vào các lớp hiện có. Nếu bạn đang mở rộng một lớp Objective-C ( UIViewchắc chắn là một lớp), bạn vẫn có thể sử dụng Đối tượng liên kết để mô phỏng các thuộc tính được lưu trữ:

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

Khóa kết hợp là một con trỏ phải là duy nhất cho mỗi liên kết. Vì vậy, chúng tôi tạo một biến toàn cục riêng và sử dụng địa chỉ bộ nhớ của nó làm khóa với &toán tử. Xem phần Sử dụng Swift với Ca cao và Mục tiêu-C để biết thêm chi tiết về cách xử lý con trỏ trong Swift.

CẬP NHẬT cho Swift 2 và 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)
        }
    }
}

CẬP NHẬT cho Swift 4

Trong Swift 4, nó đơn giản hơn nhiều. Cấu trúc Chủ sở hữu sẽ chứa giá trị riêng mà tài sản được tính toán của chúng tôi sẽ hiển thị với thế giới, tạo ảo giác về hành vi tài sản được lưu trữ thay thế.

Nguồn

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

2
@Yar Không biết objc_AssociationPolicy, cảm ơn! Tôi đã cập nhật câu trả lời
jou

2
Trong Swift2, bạn phải sử dụng objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom

1
@SimplGy vui lòng thêm chỉnh sửa của bạn dưới dạng câu trả lời bổ sung thay vì chỉnh sửa mã của bạn thành câu trả lời của người khác.
JAL

11
Giải pháp Swift4 không hoạt động nếu bạn muốn có nhiều hơn 1 phiên bản UIViewControll sử dụng thuộc tính từ tiện ích mở rộng. Vui lòng kiểm tra cập nhật trong nguồn.
IUR

9
Ví dụ Swift 4 không hoạt động với nhiều trường hợp và có lẽ không nên ở đây.
Colin Cornaby

36

Vì vậy, tôi nghĩ rằng tôi đã tìm thấy một phương pháp hoạt động sạch hơn các phương pháp trên bởi vì nó không yêu cầu bất kỳ biến toàn cục nào. Tôi đã nhận nó từ đây: http://nshipster.com/swift-objc-r nb /

Ý chính là bạn sử dụng một cấu trúc như vậy:

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

CẬP NHẬT cho 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 Tôi đã triển khai mã như của bạn nhưng khi cập nhật lên swift 2.0, tôi gặp lỗi không thể gọi trình khởi tạo cho loại 'UInt' với danh sách đối số loại 'objc_AssociationPolicy)'. Mã trong bình luận tiếp theo
user2363025

Làm cách nào để cập nhật cho swift 2.0? var postDes mô tả: Chuỗi? {get {return objc_getAssociatedObject (tự, & AssociatedKeys.postDes mô tả) là? String} set {if để newValue = newValue {objc_setAssociatedObject (bản thân, & AssociatedKeys.postDescription, newValue như NSString ?, uint (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
không hoạt động: var tĩnh có nghĩa là giá trị của tài sản là giống nhau cho tất cả các trường hợp
Fry

@fry - Nó tiếp tục làm việc cho tôi. Đúng, khóa là tĩnh, nhưng nó được liên kết với một đối tượng cụ thể, bản thân đối tượng đó không phải là toàn cầu.
AlexK

15

Giải pháp được chỉ ra bởi jou không hỗ trợ các loại giá trị , điều này cũng hoạt động tốt với chúng

Giấy gói

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

Một phần mở rộng Class có thể (Ví dụ về cách sử dụng):

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

Đây thực sự là một giải pháp tuyệt vời, tôi muốn thêm một ví dụ sử dụng khác bao gồm các cấu trúc và giá trị không phải là tùy chọn. Ngoài ra, các giá trị AssociatedKey có thể được đơn giản hóa.

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

Và làm thế nào để tôi sử dụng lớp Nâng?
3lvis

Tại sao bạn cần điều đó? Để làm gì?
HepaKKes

Ý tôi là làm thế nào để bạn sử dụng cái này nói chung, cảm ơn vì sự giúp đỡ của bạn :) Bạn có thể thêm một ví dụ "Cách sử dụng" không?
3lvis

1
Ví dụ "Cách sử dụng" sẽ bắt đầu ngay bên dưới tiêu đề "Phần mở rộng lớp có thể". Tôi không nghĩ người dùng cần biết cách sử dụng liftphương thức và Liftedlớp, họ nên sử dụng tuy nhiên getAssociatedObject& các setAssociatedObjectchức năng. Tôi sẽ thêm "Ví dụ về cách sử dụng" giữa dấu ngoặc đơn bên cạnh tiêu đề để rõ ràng.
HepaKKes

1
Đây chắc chắn là giải pháp tốt nhất, quá tệ, nó không được giải thích rõ ràng. Nó hoạt động với các lớp, cấu trúc và các loại giá trị khác. Các thuộc tính cũng không phải là tùy chọn. Công việc tốt đẹp.
picciano

13

Bạn không thể xác định danh mục (tiện ích mở rộng Swift) với bộ nhớ mới; bất kỳ thuộc tính bổ sung nào phải được tính toán thay vì được lưu trữ. Cú pháp hoạt động cho Objective C vì @propertytrong một danh mục về cơ bản có nghĩa là "Tôi sẽ cung cấp getter và setter". Trong Swift, bạn sẽ cần tự xác định những thứ này để có được một tài sản được tính toán; cái gì đó như:

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

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

Nên làm việc tốt. Hãy nhớ rằng, bạn không thể lưu trữ các giá trị mới trong setter, chỉ hoạt động với trạng thái lớp khả dụng hiện có.


7

0,02 đô la của tôi. Mã này được viết bằng 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)
            }
        }
    }
}

Tôi đã thử nhiều giải pháp và thấy đây là cách duy nhất để thực sự mở rộng một lớp với các tham số biến phụ.


Vẻ thú vị, nhưng từ chối không làm việc với các giao thức, type 'AssociatedKeys' cannot be defined within a protocol extension...
Ian Bytchek

6

Tại sao phải dựa vào thời gian chạy objc? Tôi không nhận được điểm. Bằng cách sử dụng một cái gì đó như sau, bạn sẽ đạt được hành vi gần như giống hệt của một tài sản được lưu trữ, bằng cách chỉ sử dụng một cách tiếp cận Swift thuần túy :

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
Tài giỏi! Một nhược điểm có thể có với phương pháp này là quản lý bộ nhớ. Sau khi đối tượng máy chủ được giải phóng, thuộc tính của nó sẽ vẫn tồn tại trong từ điển có khả năng gây tốn kém cho việc sử dụng bộ nhớ nếu điều này được sử dụng với nhiều đối tượng.
Charlton Provatas

Có lẽ chúng ta có thể giữ một biến danh sách tĩnh của các hàm bao chứa tham chiếu yếu đến các đối tượng (tự). Và xóa mục nhập khỏi từ điển _myComputingProperty cũng như biến danh sách tĩnh của hàm bao trong phương thức Wrapper (Khi đối tượng được phân bổ lại didset trong lớp Wrapper của chúng ta được gọi vì tham chiếu yếu của chúng ta được đặt giá trị mới bằng nil).
Arjuna

5

Tôi thích làm mã trong Swift thuần túy và không dựa vào di sản Objective-C. Vì điều này tôi đã viết giải pháp Swift thuần túy với hai ưu điểm và hai nhược điểm.

Ưu điểm:

  1. Mã Swift tinh khiết

  2. Hoạt động trên các lớp và hoàn thành hoặc cụ thể hơn trên Anyđối tượng

Nhược điểm:

  1. Mã nên gọi phương thức willDeinit()để giải phóng các đối tượng được liên kết với thể hiện của lớp cụ thể để tránh rò rỉ bộ nhớ

  2. Bạn không thể tạo tiện ích mở rộng trực tiếp cho UIView cho ví dụ chính xác này vì đây var framelà tiện ích mở rộng cho UIView, không phải là một phần của lớp.

BIÊN TẬP:

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
Điều này không hoạt động vì extensionPropertyStorage được chia sẻ giữa tất cả các trường hợp. Nếu bạn đặt giá trị cho một phiên bản, bạn đang đặt giá trị cho tất cả các phiên bản.
picciano

Nó chiến tranh không tốt vì làm rối tung chức năng. Bây giờ nó là tốt hơn và hoạt động như dự định.
vedrano

@picciano extensionPropertyStorage được chia sẻ với tất cả các trường hợp theo thiết kế. Đây là biến toàn cục (Từ điển) mà lần đầu tiên hủy tham chiếu UILabel ( NSObject) và sau đó là thuộc tính của nó ( [String: Any]).
vedrano

@picciano Tôi đoán điều này có hiệu quả đối với classtài sản;)
Nicolas Miari

framelà một tài sản cá thể. Trường hợp tài sản đến từ đâu?
vedrano

3

Với Danh mục Obj-c, bạn chỉ có thể thêm các phương thức, không phải các biến thể hiện.

Trong ví dụ của bạn, bạn đã sử dụng @property như một lối tắt để thêm khai báo phương thức getter và setter. Bạn vẫn cần phải thực hiện các phương pháp đó.

Tương tự như vậy trong Swift, bạn có thể thêm các tiện ích mở rộng để thêm các phương thức cá thể, các thuộc tính được tính toán, v.v. nhưng không được lưu trữ các thuộc tính.


2

Tôi cũng nhận được một vấn đề EXC_BAD_ACCESS. Giá trị trong objc_getAssociatedObject()objc_setAssociatedObject()phải là một Đối tượng. Và objc_AssociationPolicynên phù hợp với Object.


3
Điều này không thực sự trả lời câu hỏi. Nếu bạn có một câu hỏi khác, bạn có thể hỏi nó bằng cách nhấp vào Đặt câu hỏi . Bạn cũng có thể thêm tiền thưởng để thu hút sự chú ý hơn cho câu hỏi này một khi bạn có đủ danh tiếng . - Từ đánh giá
AtheistP3ace

@ AtheistP3ace Tôi rất xin lỗi về điều đó. Tôi cố gắng thêm một bình luận cho câu hỏi. Nhưng tôi không có đủ danh tiếng. Vì vậy, tôi cố gắng trả lời câu hỏi.
RuiKQ

2

Tôi đã thử sử dụng objc_setAssociatedObject như được đề cập trong một vài câu trả lời ở đây, nhưng sau khi thất bại với nó một vài lần tôi đã lùi lại và nhận ra không có lý do gì tôi cần điều đó. Mượn từ một vài ý tưởng ở đây, tôi đã nghĩ ra mã này chỉ đơn giản là lưu trữ một mảng của bất kỳ dữ liệu bổ sung nào của tôi (MyClass trong ví dụ này) được lập chỉ mục bởi đối tượng tôi muốn liên kết với nó:

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)

Bạn có biết làm thế nào để tự động xóa ExtraData để nó không gây rò rỉ bộ nhớ không?
DoubleK

Tôi đã không thực sự sử dụng điều này với các khung nhìn, trong trường hợp của tôi, tôi chỉ khởi tạo một số lượng hữu hạn các đối tượng trong lớp tôi đang sử dụng, vì vậy tôi thực sự không coi là rò rỉ bộ nhớ. Tôi không thể nghĩ ra một cách tự động để làm điều này, nhưng tôi cho rằng trong setter ở trên, bạn có thể thêm logic để loại bỏ phần tử if value == nil, và sau đó đặt giá trị thành nil khi bạn không còn cần đối tượng, hoặc có thể trong didReceiveMemoryWarning hoặc một cái gì đó.
Dan

Tnx @Dan, tôi đã sử dụng viewWillDisapper để đặt giá trị thành nil và nó hoạt động tốt, bây giờ deinit được gọi và mọi thứ đều hoạt động tốt. Tnx đã đăng giải pháp này
DoubleK

2

Đây là giải pháp đơn giản và biểu cảm hơn. Nó hoạt động cho cả hai loại giá trị và tham chiếu. Cách tiếp cận nâng được lấy từ câu trả lời của @HepaKKes.

Mã hiệp hội:

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

Ví dụ về cách sử dụng:

1) Tạo phần mở rộng và liên kết các thuộc tính với nó. Hãy sử dụng cả hai thuộc tính loại giá trị và tham chiếu.

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) Bây giờ bạn có thể sử dụng như các thuộc tính thông thường:

    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

Thêm chi tiết:

  1. Nâng là cần thiết để bọc các loại giá trị.
  2. Hành vi đối tượng liên quan mặc định được giữ lại. Nếu bạn muốn tìm hiểu thêm về các đối tượng liên quan, tôi khuyên bạn nên kiểm tra bài viết này .

1

Một ví dụ khác với việc sử dụng các đối tượng liên quan đến Objective-C và các thuộc tính được tính cho Swift 3Swift 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

nếu bạn đang tìm cách đặt thuộc tính chuỗi tùy chỉnh thành UIView, đây là cách tôi đã thực hiện trên Swift 4

Tạo tiện ích mở rộng 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
    }
}

Để sử dụng tiện ích mở rộng này

let key = "COLOR"

let redView = UIView() 

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

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

1

Cách lưu trữ bản đồ tĩnh đến lớp đang mở rộng như thế này:

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
Tại sao câu trả lời này không có upvote. Giải pháp thông minh sau tất cả.
byJeevan

Cách này hiệu quả nhưng tôi nghĩ cách tiếp cận này còn thiếu sót như đã nêu ở đây Medium.com/@valv0/ Khăn
Teffi

0

Tôi đã cố gắng lưu trữ các thuộc tính bằng cách sử dụng objc_getAssociatedObject, objc_setAssociatedObject, mà không gặp may mắn. Mục tiêu của tôi là tạo tiện ích mở rộng cho UITextField, để xác thực độ dài ký tự nhập văn bản. Mã sau hoạt động tốt cho tôi. Hy vọng điều này sẽ giúp được ai đó.

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

Biến tư nhân toàn cầu? Bạn có nhận ra điều đó _minvà mang _maxtính toàn cầu và sẽ giống nhau trong tất cả các trường hợp của UITextField không? Thậm chí nếu nó làm việc cho bạn, câu trả lời này là không liên quan vì Marcos hỏi về dụ biến.
kelin

Có bạn đúng, điều này không làm việc cho tất cả các trường hợp. Tôi đã sửa bằng cách lưu trữ giá trị tối thiểu và tối đa trong struct. Xin lỗi vì lạc đề.
Vadims Krutovs

0

Đây là một thay thế cũng hoạt động

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

Tôi thấy giải pháp này thiết thực hơn

CẬP NHẬT cho 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)
    }

}

... sau đó trong mã của bạn

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}

Đây là cách đúng đắn để làm điều đó - nhưng không thực sự là một tài sản được lưu trữ trong phần mở rộng như câu hỏi yêu cầu.
UKDataGeek
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.