วิธีการจัดเก็บคุณสมบัติใน 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 ไม่สามารถกำหนดตัวแปรอินสแตนซ์ได้เช่นกันคุณรู้คุณสมบัติเหล่านั้นได้อย่างไร
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

1
ไม่สามารถจัดเก็บประเภท Swift ผ่านการเชื่อมโยงวัตถุโดยตรง คุณสามารถจัดเก็บเช่นNSNumberหรือNSValueและเขียนอุปกรณ์เสริมเพิ่มเติมที่เป็นประเภทที่คุณต้องการได้ (Int, Bool ฯลฯ )
Wojciech Nagrodzki

1
คำเตือนสิ่งนี้ใช้ไม่ได้กับโครงสร้างเนื่องจากไลบรารีรันไทม์วัตถุประสงค์ -c สนับสนุนเฉพาะคลาสที่สอดคล้องกับNSObjectProtocol
Charlton Provatas

2
@CharltonProvatas ไม่สามารถใช้โครงสร้างกับ API นี้ได้เนื่องจากไม่เป็นไปตามโปรโตคอล AnyObject
Wojciech Nagrodzki

@VyachaslavGerchicov [String]?เป็นโครงสร้างซึ่งเป็นกรณีเดียวกับ Int, Bool คุณสามารถใช้NSArrayเพื่อเก็บรวบรวมNSStringอินสแตนซ์
Wojciech Nagrodzki

184

เช่นเดียวกับ Objective-C คุณไม่สามารถเพิ่มคุณสมบัติที่จัดเก็บไว้ในคลาสที่มีอยู่ได้ หากคุณกำลังขยายคลาส Objective-C ( UIViewเป็นคลาสเดียว) คุณยังคงสามารถใช้Associated Objectsเพื่อจำลองคุณสมบัติที่เก็บไว้

สำหรับ 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 กับ Cocoa และ 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 นั้นง่ายกว่ามาก โครงสร้างผู้ถือจะมีมูลค่าส่วนตัวที่ทรัพย์สินที่คำนวณของเราจะเปิดเผยต่อโลกโดยให้ภาพลวงตาของพฤติกรรมคุณสมบัติที่เก็บไว้แทน

ที่มา

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

13
โซลูชัน Swift4 ไม่ทำงานหากคุณต้องการมี UIViewController มากกว่า 1 อินสแตนซ์ที่ใช้คุณสมบัติจากส่วนขยาย โปรดตรวจสอบการอัปเดตในแหล่งที่มา
iur

10
ตัวอย่าง Swift 4 ใช้ไม่ได้กับหลายอินสแตนซ์และอาจไม่ควรอยู่ที่นี่
Colin Cornaby

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 ฉันได้รับข้อผิดพลาดไม่สามารถเรียกใช้ initializer สำหรับประเภท 'UInt' พร้อมกับรายการอาร์กิวเมนต์ประเภท 'objc_AssociationPolicy)' รหัสในความคิดเห็นถัดไป
user2363025

จะอัพเดต swift 2.0 ได้อย่างไร? var postDescription: สตริง? {รับ {return objc_getAssociatedObject (self & AssociatedKeys.postDescription) เป็น? สตริง} ชุด {ถ้าปล่อยให้ newValue = newValue {objc_setAssociatedObject (ตัวเองและ AssociatedKeys.postDescription, newValue เป็น NSString ?, uint (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
ไม่ทำงาน: var แบบคงที่หมายความว่าค่าของคุณสมบัติเหมือนกันสำหรับทุกอินสแตนซ์
ทอด

@fry - มันยังคงใช้งานได้สำหรับฉัน ใช่คีย์เป็นแบบคงที่ แต่เชื่อมโยงกับออบเจ็กต์เฉพาะอ็อบเจ็กต์นั้นไม่ได้เป็นแบบโกลบอล
AlexK

16

โซลูชันที่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
นี่เป็นทางออกที่ดีที่สุดอย่างแน่นอนแย่มากที่อธิบายได้ไม่ดีนัก ใช้ได้กับคลาสโครงสร้างและประเภทค่าอื่น ๆ คุณสมบัติไม่จำเป็นต้องเป็น optionals เช่นกัน ทำได้ดีมาก
picciano

13

คุณไม่สามารถกำหนดหมวดหมู่ (ส่วนขยาย Swift) ด้วยพื้นที่เก็บข้อมูลใหม่ ต้องคำนวณคุณสมบัติเพิ่มเติมใด ๆแทนที่จะจัดเก็บไว้ ไวยากรณ์ใช้งานได้กับ Objective C เพราะ@propertyในหมวดหมู่โดยพื้นฐานแล้วหมายถึง "ฉันจะให้ getter และ setter" ใน 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
ฉลาด! ข้อเสียที่เป็นไปได้อย่างหนึ่งของแนวทางนี้คือการจัดการหน่วยความจำ หลังจากยกเลิกการจัดสรรออบเจ็กต์โฮสต์แล้วคุณสมบัตินั้นจะยังคงมีอยู่ในพจนานุกรมซึ่งอาจมีราคาแพงในการใช้หน่วยความจำหากใช้กับวัตถุจำนวนมาก
Charlton Provatas

อาจเป็นไปได้ว่าเราสามารถเก็บตัวแปรรายการแบบคงที่ของ wrapper ซึ่งมีการอ้างอิงที่อ่อนแอไปยังวัตถุ (ตัวเอง) และลบรายการออกจากพจนานุกรม _myComputedProperty ตลอดจนตัวแปรรายการแบบคงที่ของ Wrapper ในเมธอด didSet ของ Wrapper (เมื่ออ็อบเจ็กต์ได้รับการจัดสรร didSet ภายในคลาส Wrapper ของเราจะถูกเรียกเนื่องจากการอ้างอิงที่อ่อนแอของเราถูกตั้งค่าใหม่ด้วยศูนย์)
อรชุน

5

ฉันชอบทำโค้ดใน Swift ที่บริสุทธิ์และไม่พึ่งพามรดกของ Objective-C ด้วยเหตุนี้ฉันจึงเขียนโซลูชัน Swift ที่บริสุทธิ์พร้อมข้อดีสองประการและข้อเสียสองประการ

ข้อดี:

  1. รหัส Pure 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 ถูกแชร์ระหว่างอินสแตนซ์ทั้งหมด หากคุณตั้งค่าสำหรับอินสแตนซ์เดียวคุณกำลังตั้งค่าสำหรับอินสแตนซ์ทั้งหมด
picciano

สงครามไม่ดีเพราะทำหน้าที่ยุ่งเหยิง ตอนนี้ก็ดีขึ้นและได้ผลตามที่ตั้งใจไว้
vedrano

@picciano extensionPropertyStorage ใช้ร่วมกับทุกอินสแตนซ์ตามการออกแบบ เป็นตัวแปรส่วนกลาง (พจนานุกรม) ที่ยกเลิกการอ้างอิงอินสแตนซ์ UILabel ( NSObject) ก่อนแล้วตามด้วยคุณสมบัติ ( [String: Any])
vedrano

@picciano ฉันเดาว่ามันใช้ได้กับclassคุณสมบัติ;)
Nicolas Miari

frameเป็นคุณสมบัติของอินสแตนซ์ คุณสมบัติของคลาสมาจากไหน?
vedrano

3

ด้วย Obj-c Categories คุณสามารถเพิ่มเมธอดเท่านั้นไม่ใช่ตัวแปรอินสแตนซ์

ในตัวอย่างของคุณคุณได้ใช้ @property เป็นทางลัดในการเพิ่มการประกาศเมธอด getter และ setter คุณยังต้องใช้วิธีการเหล่านั้น

เช่นเดียวกันใน Swift คุณสามารถเพิ่มส่วนขยายการใช้งานเพื่อเพิ่มวิธีการอินสแตนซ์คุณสมบัติที่คำนวณเป็นต้น แต่ไม่สามารถจัดเก็บคุณสมบัติได้


2

ฉันยังได้รับปัญหา EXC_BAD_ACCESS ค่าในobjc_getAssociatedObject()และobjc_setAssociatedObject()ควรเป็น Object และ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)

คุณมีความคิดอย่างไรในการล้างข้อมูลพิเศษโดยอัตโนมัติเพื่อไม่ให้เกิดการรั่วไหลของหน่วยความจำ?
DoubleK

ฉันไม่ได้ใช้สิ่งนี้กับมุมมองจริงๆในกรณีของฉันฉันเคยสร้างอินสแตนซ์ของวัตถุจำนวน จำกัด ในคลาสที่ฉันใช้เท่านั้นดังนั้นฉันจึงไม่ได้พิจารณาถึงการรั่วไหลของหน่วยความจำ ฉันไม่สามารถคิดวิธีอัตโนมัติในการทำสิ่งนี้ได้ แต่ฉันคิดว่าใน setter ด้านบนคุณสามารถเพิ่มตรรกะเพื่อลบองค์ประกอบได้ถ้า value == nil จากนั้นตั้งค่าเป็นศูนย์เมื่อคุณไม่ต้องการวัตถุอีกต่อไปหรือ อาจจะใน didReceiveMemoryWarning หรืออะไรก็ได้
แดน

Tnx @Dan ฉันใช้ viewWillDisapper เพื่อตั้งค่าเป็นศูนย์และใช้งานได้ดีตอนนี้เรียกใช้ 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/…
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

ฉันพบว่าโซลูชันนี้ใช้ได้จริงมากขึ้น

อัปเดตสำหรับ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.