Как сохранить свойства в Swift, так же, как у меня на Objective-C?
Я переключаю приложение из 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()
...
}
}
Любые идеи?
Ответы
Ответ 1
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) }
}
}
При условии, что вы можете "добавить" свойство в class-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 }
}
}
Ответ 2
Как и в 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 с 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 все гораздо проще. Структура Holder будет содержать частную ценность, которую наше вычисленное свойство представит миру, создавая иллюзию поведения хранимых свойств.
Источник
extension UIViewController {
struct Holder {
static var _myComputedProperty:Bool = false
}
var myComputedProperty:Bool {
get {
return Holder._myComputedProperty
}
set(newValue) {
Holder._myComputedProperty = newValue
}
}
}
Ответ 3
Поэтому я думаю, что нашел метод, который работает более чистым, чем те, что были выше, потому что он не требует глобальных переменных. Я получил его отсюда:
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)
}
}
Ответ 4
Решение, указанное 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)
}
}
}
Ответ 5
Вы не можете определить категории (Swift extensions) с новым хранилищем; любые дополнительные свойства должны вычисляться, а не сохраняться. Синтаксис работает для Objective C, потому что @property
в категории по существу означает "я предоставил getter и setter". В Swift вам необходимо определить их самостоятельно, чтобы получить вычисленное свойство; что-то вроде:
extension String {
public var Foo : String {
get
{
return "Foo"
}
set
{
// What do you want to do here?
}
}
}
Должно работать нормально. Помните, что вы не можете хранить новые значения в сеттере, работайте только с существующим доступным состоянием класса.
Ответ 6
Мои $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)
}
}
}
}
Я пробовал много решений и нашел, что это единственный способ фактически расширить класс с дополнительными параметрами переменной.
Ответ 7
Я предпочитаю делать код в чистом Swift и не полагаться на наследие Objective-C. Из-за этого я написал чистое решение Swift с двумя преимуществами и двумя недостатками.
<сильные > Преимущества:
Недостатки:
-
Код должен вызывать метод willDeinit()
для выпуска объектов, связанных с конкретным экземпляром класса, чтобы избежать утечек памяти.
-
Вы не можете сделать расширение непосредственно в UIView для этого точного примера, потому что var frame
является расширением для UIView, а не частью класса.
EDIT:
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
}
}
Ответ 8
Зачем полагаться на время выполнения 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
}
}
}
Ответ 9
С категориями Obj-c вы можете добавлять только методы, а не переменные экземпляра.
В примере вы использовали @property как ярлык для добавления объявлений метода getter и setter. Вам все равно необходимо реализовать эти методы.
Аналогично в Swift вы можете добавлять расширения для добавления методов экземпляра, вычисленных свойств и т.д., но не сохраняемых свойств.
Ответ 10
Я также получаю проблему с EXC_BAD_ACCESS. Значение в objc_getAssociatedObject()
и objc_setAssociatedObject()
должно быть объектом. И objc_AssociationPolicy
должен соответствовать объекту.
Ответ 11
Я попытался использовать 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)
Ответ 12
Вот упрощенное и более выразительное решение. Он работает как для значений, так и для ссылочных типов. Подъем подъема берется из ответа @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
Подробнее:
- Подъем необходимо для обертывания типов значений.
- Поведение по умолчанию, связанное с объектом, сохраняется. Если вы хотите узнать больше о связанных объектах, я бы рекомендовал проверить эту статью.
Ответ 13
Другой пример использования 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
)
}
}
}
}
Ответ 14
Я попытался сохранить свойства с помощью 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)
}
}
Ответ 15
Вот альтернатива, которая работает также
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 )
Ответ 16
если вы хотите установить пользовательский атрибут строки в 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")
Ответ 17
Как насчет хранения статической карты в классе, который расширяется так:
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 }
}
}
Ответ 18
Я нашел это решение более практичным
ОБНОВЛЕНО для 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
}
}