Objective-C 的运转时机制关联自定义属性到目的上,相对应的是swift中唯有伸张(

在oc中为了增长已有类的机能,我们日常使用分类。使用分类,我们得以在不破坏原有类的构造的前提下,对原有类进行模块化的伸张。

原文在此处,
转载请贴原文链接

只是在swift中从未分类那种写法了。相呼应的是swift中唯有扩张(Extensions)。

Objective-C 最令人诟病的或是就是无法给已有类添加属性, 可是可以透过
Objective-C 的周转时机制关联自定义属性到对象上, 大概弥补了那么些痛点.

下面是swift中扩展(Extensions)的说明

斯威夫特 Extension 比 Objective-C Category 增色不少, extension
可以给已有类添加总计型属性, 那曾经是很大的升高,
不过如故不可能添加存储属性. 斯维夫特 中也得以使用 Objective-C runtime
的关联对象(Associated
Objects
)的法门添加属性,
弥补这一痛点.

恢宏就是向一个已有的类、结构体、枚举类型或者协议项目丰硕新功用(functionality)。那包罗在一直不权限获取原始源代码的情形下伸张类型的能力(即逆向建模)。扩张和
Objective-C 中的分类(categories)类似。(可是与 Objective-C
不一样的是,Swift 的恢弘没有名字。)

关系对象(Associated Objects)

Swift 中提供三个与 Objective-C 类似的法子将自定义的性质关联到目的上:

  1. objc_setAssociatedObject
  2. objc_getAssociatedObject
  3. objc_removeAssociatedObjects

注意: 使用 objc_removeAssociatedObjects 时要小心,
那几个方法会删除对象关系的持有属性,
就可能造成把人家添加的涉及属性也删掉. 如若要去除某一个特性, 使用
objc_setAssociatedObject 方法, value 置为 nil.

上面给 UIView 添加三种差距品类的习性: isShow, displayName, width.

extension UIView {
    // 嵌套结构体
    private struct AssociatedKeys {
        static var isShowKey = "isShowKey"
        static var displayNameKey = "displayNameKey"
        static var widthKey = "widthKey"
    }

    // Bool 类型
    var isShow: Bool {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.isShowKey) as! Bool
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.isShowKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    // String 类型
    var displayName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.displayNameKey) as? String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.displayNameKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
    }

    // Float 类型
    var width: Float {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.widthKey) as! Float
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.widthKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

上述列子表达几点:

  • 嵌套私有结构体, 评释与壮大属性对应的键(key). Swift
    Extension

    提供了增加的效用, 可以在 Extension 中嵌套类型, 使用 private
    私有访问控制, 不会传染整个命名空间, 而且可以合并管理关系对象键.
  • Swift 的着力类型Int, Float, Double, Bool可以自行隐式地转换成
    Objective-C 的 NSNumber 类型, 所以不须求展示的包裹成 NSNumber
    类型举办关联.
  • 要是选拔 OBJC_ASSOCIATION_ASSIGN 关联策略时要留心,
    文档中提议是弱引用, 但不相同 weak, 更像是 unsafe_unretained
    引用, 关联对象被放飞后,关联属性依旧保留被释放的地点,
    固然不小心访问关联属性, 就会造成野指针访问出错.

Specifies a weak reference to the associated object.

那么大家怎么在swift中落实oc中的分类呢?我们可以向苹果学习。
同等是UIView类,大家独家看望oc和swift差其余落到实处格局。

抽取关联对象方法

我们能够把事关对象的办法提取成国有艺术, 在 NSObject 类的 extension
里达成, 只要继续自 NSObject 的类就可见调用关联对象方法, 通过Swift
泛型
来波及不一致品类的属性.

extension NSObject {
    func setAssociated<T>(value: T, associatedKey: UnsafeRawPointer, policy: objc_AssociationPolicy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) -> Void {
        objc_setAssociatedObject(self, associatedKey, value, policy)
    }

    func getAssociated<T>(associatedKey: UnsafeRawPointer) -> T? {
        let value = objc_getAssociatedObject(self, associatedKey) as? T
        return value;
    }
}

我们只需求在 UIView+Extension.swift 中调整地方四个格局即可,
近来只支持有可选类型的属性.

extension UIView {
    private struct AssociatedKeys {
        static var displayNameKey = "displayNameKey"
    }

    var displayName: String? {
        get {
            return getAssociated(associatedKey: &AssociatedKeys.displayNameKey)
        }
        set {
            setAssociated(value: newValue, associatedKey: &AssociatedKeys.displayNameKey)
        }
    }
}   

1.大家先看看oc中的UIView

这是 UIView中的申明的代码

#ifndef SDK_HIDE_TIDE
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment>
#else
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace>
#endif

+ (Class)layerClass;                        // default is [CALayer class]. Used when creating the underlying layer for the view.

- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled;  // default is YES. if set to NO, user events (touch, keys) are ignored and removed from the event queue.
@property(nonatomic)                                 NSInteger tag;                // default is 0
@property(nonatomic,readonly,strong)                 CALayer  *layer;              // returns view's layer. Will always return a non-nil value. view is layer's delegate

#ifndef SDK_HIDE_TIDE
- (BOOL)canBecomeFocused NS_AVAILABLE_IOS(9_0); // NO by default
@property (readonly, nonatomic, getter=isFocused) BOOL focused NS_AVAILABLE_IOS(9_0);
#endif
+ (UIUserInterfaceLayoutDirection)userInterfaceLayoutDirectionForSemanticContentAttribute:(UISemanticContentAttribute)attribute NS_AVAILABLE_IOS(9_0);
@property (nonatomic) UISemanticContentAttribute semanticContentAttribute NS_AVAILABLE_IOS(9_0);
@end

其他的性质和方法都是采纳分类的措施开展伸张的。
上边是页面渲染相关的质量和艺术

@interface UIView(UIViewRendering)

- (void)drawRect:(CGRect)rect;

- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)rect;

@property(nonatomic)                 BOOL              clipsToBounds;              // When YES, content and subviews are clipped to the bounds of the view. Default is NO.
@property(nullable, nonatomic,copy)            UIColor          *backgroundColor UI_APPEARANCE_SELECTOR; // default is nil. Can be useful with the appearance proxy on custom UIView subclasses.
@property(nonatomic)                 CGFloat           alpha;                      // animatable. default is 1.0
@property(nonatomic,getter=isOpaque) BOOL              opaque;                     // default is YES. opaque views must fill their entire bounds or the results are undefined. the active CGContext in drawRect: will not have been cleared and may have non-zeroed pixels
@property(nonatomic)                 BOOL              clearsContextBeforeDrawing; // default is YES. ignored for opaque views. for non-opaque views causes the active CGContext in drawRect: to be pre-filled with transparent pixels
@property(nonatomic,getter=isHidden) BOOL              hidden;                     // default is NO. doesn't check superviews
@property(nonatomic)                 UIViewContentMode contentMode;                // default is UIViewContentModeScaleToFill
@property(nonatomic)                 CGRect            contentStretch NS_DEPRECATED_IOS(3_0,6_0) __TVOS_PROHIBITED; // animatable. default is unit rectangle {{0,0} {1,1}}. Now deprecated: please use -[UIImage resizableImageWithCapInsets:] to achieve the same effect.

@property(nullable, nonatomic,strong)          UIView           *maskView NS_AVAILABLE_IOS(8_0);

/*
 -tintColor always returns a color. The color returned is the first non-default value in the receiver's superview chain (starting with itself).
 If no non-default value is found, a system-defined color is returned.
 If this view's -tintAdjustmentMode returns Dimmed, then the color that is returned for -tintColor will automatically be dimmed.
 If your view subclass uses tintColor in its rendering, override -tintColorDidChange in order to refresh the rendering if the color changes.
 */
@property(null_resettable, nonatomic, strong) UIColor *tintColor NS_AVAILABLE_IOS(7_0);

/*
 -tintAdjustmentMode always returns either UIViewTintAdjustmentModeNormal or UIViewTintAdjustmentModeDimmed. The value returned is the first non-default value in the receiver's superview chain (starting with itself).
 If no non-default value is found, UIViewTintAdjustmentModeNormal is returned.
 When tintAdjustmentMode has a value of UIViewTintAdjustmentModeDimmed for a view, the color it returns from tintColor will be modified to give a dimmed appearance.
 When the tintAdjustmentMode of a view changes (either the view's value changing or by one of its superview's values changing), -tintColorDidChange will be called to allow the view to refresh its rendering.
 */
@property(nonatomic) UIViewTintAdjustmentMode tintAdjustmentMode NS_AVAILABLE_IOS(7_0);

/*
 The -tintColorDidChange message is sent to appropriate subviews of a view when its tintColor is changed by client code or to subviews in the view hierarchy of a view whose tintColor is implicitly changed when its superview or tintAdjustmentMode changes.
 */
- (void)tintColorDidChange NS_AVAILABLE_IOS(7_0);

@end

关联闭包属性

支付中偶然会给已有类关联闭包属性, 比如给 UIViewController 类添加一个
pushCompletion 的闭包属性, 当导航控制器 push 动作一挥而就后调用该控制器的
pushCompletion 闭包.
先依照最主旨的艺术来涉及对象, 如下:

typealias pushCompletionClosure = ()->()

extension UIViewController {
    private struct AssociatedKeys {
        static var pushCompletionKey = "pushCompletionKey"
    }

    var pushCompletion: pushCompletionClosure? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.pushCompletionKey) as? pushCompletionClosure
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.pushCompletionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
    }
}

开快意心编译一发, 发现编译报错:

图片 1

事关闭包报错

定位难点发现出在 objc_setAssociatedObject 这几个办法上.
原来闭包属性必要包装一下才能拓展关联, 下边给出二种解决办法:

  1. 行使泛型包装闭包属性, 利用 NSObject+Extension.swift 中的
    setAssociated 方法来涉及闭包.
  2. 始建私有闭包容器类, 利用闭包容器直接关系闭包属性.

2.那是swift中的代码

UIView类中的评释

@available(iOS 2.0, *)
    public class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment {

    public class func layerClass() -> AnyClass // default is [CALayer class]. Used when creating the underlying layer for the view.

    public init(frame: CGRect)
    public init?(coder aDecoder: NSCoder)

    public var userInteractionEnabled: Bool // default is YES. if set to NO, user events (touch, keys) are ignored and removed from the event queue.
    public var tag: Int // default is 0
    public var layer: CALayer { get } // returns view's layer. Will always return a non-nil value. view is layer's delegate

    @available(iOS 9.0, *)
    public func canBecomeFocused() -> Bool // NO by default
    @available(iOS 9.0, *)
    public var focused: Bool { get }

    @available(iOS 9.0, *)
    public class func userInterfaceLayoutDirectionForSemanticContentAttribute(attribute: UISemanticContentAttribute) -> UIUserInterfaceLayoutDirection
    @available(iOS 9.0, *)
    public var semanticContentAttribute: UISemanticContentAttribute
}

页面渲染的代码

extension UIView {

    public func drawRect(rect: CGRect)

    public func setNeedsDisplay()
    public func setNeedsDisplayInRect(rect: CGRect)

    public var clipsToBounds: Bool // When YES, content and subviews are clipped to the bounds of the view. Default is NO.
    @NSCopying public var backgroundColor: UIColor? // default is nil. Can be useful with the appearance proxy on custom UIView subclasses.
    public var alpha: CGFloat // animatable. default is 1.0
    public var opaque: Bool // default is YES. opaque views must fill their entire bounds or the results are undefined. the active CGContext in drawRect: will not have been cleared and may have non-zeroed pixels
    public var clearsContextBeforeDrawing: Bool // default is YES. ignored for opaque views. for non-opaque views causes the active CGContext in drawRect: to be pre-filled with transparent pixels
    public var hidden: Bool // default is NO. doesn't check superviews
    public var contentMode: UIViewContentMode // default is UIViewContentModeScaleToFill
    // animatable. default is unit rectangle {{0,0} {1,1}}. Now deprecated: please use -[UIImage resizableImageWithCapInsets:] to achieve the same effect.

    @available(iOS 8.0, *)
    public var maskView: UIView?

    /*
     -tintColor always returns a color. The color returned is the first non-default value in the receiver's superview chain (starting with itself).
     If no non-default value is found, a system-defined color is returned.
     If this view's -tintAdjustmentMode returns Dimmed, then the color that is returned for -tintColor will automatically be dimmed.
     If your view subclass uses tintColor in its rendering, override -tintColorDidChange in order to refresh the rendering if the color changes.
     */
    @available(iOS 7.0, *)
    public var tintColor: UIColor!

    /*
     -tintAdjustmentMode always returns either UIViewTintAdjustmentModeNormal or UIViewTintAdjustmentModeDimmed. The value returned is the first non-default value in the receiver's superview chain (starting with itself).
     If no non-default value is found, UIViewTintAdjustmentModeNormal is returned.
     When tintAdjustmentMode has a value of UIViewTintAdjustmentModeDimmed for a view, the color it returns from tintColor will be modified to give a dimmed appearance.
     When the tintAdjustmentMode of a view changes (either the view's value changing or by one of its superview's values changing), -tintColorDidChange will be called to allow the view to refresh its rendering.
     */
    @available(iOS 7.0, *)
    public var tintAdjustmentMode: UIViewTintAdjustmentMode

    /*
     The -tintColorDidChange message is sent to appropriate subviews of a view when its tintColor is changed by client code or to subviews in the view hierarchy of a view whose tintColor is implicitly changed when its superview or tintAdjustmentMode changes.
     */
    @available(iOS 7.0, *)
    public func tintColorDidChange()
}

泛型包装闭包属性

setAssociated 方法要求泛型参数, 当传入闭包后, 就会把闭包包装成泛型.

set {
   setAssociated(value: newValue, associatedKey: &AssociatedKeys.pushCompletionKey)
}

3.创制大家温馨的extension

下边就让大家品尝一下,写一个温馨的swift分类
在get和set UIView的x和y属性的时候代码很麻烦,如下:

//get x
var x = self.view.frame.origin.x

//set x
var rect = self.view.frame
rect.origin.x = 100
self.view.frame = rect

我们盼望那里的代码是如此的

//get x
var x = self.view.x
//set x
self.view.x = 100

在oc中大家可以写一个分拣来落到实处,这一个难度不大。懒的要好写的可以参见那些https://github.com/findM/UIView-Positioning
在swift中咱们须求写一个extension来贯彻
3.1 新建swift文件

新建swift文件.png

3.2 代码达成

import Foundation
import UIKit

//private var PERSON_ID_NUMBER_PROPERTY = 0

extension UIView {
    public var x: CGFloat{
        get{
            return self.frame.origin.x
        }
        set{
            var r = self.frame
            r.origin.x = newValue
            self.frame = r
        }
    }
    public var y: CGFloat{
        get{
            return self.frame.origin.y
        }
        set{
            var r = self.frame
            r.origin.y = newValue
            self.frame = r
        }
    }
    //其他的篇幅原因就不在这里一一实现了
}

成套的兑现形式请参考那里https://github.com/findM/UIView-Positioning-Swift

闭包容器

运用闭包容器的主意关联闭包属性, 进程分成两步:

  1. 在 extension 中嵌套创造容器类, 容器类中定义必要关联的闭包属性.
  2. 关系对象时把容器类对象关系到已有类, 直接的就把闭包属性关联到已有类.

闭包容器的法门是把闭包属性包装到了容器中, 再把容器对象关联到已有类上,
跟泛型包装闭包有异曲同工之处, 由此必须透过容器对象来访问闭包,
假若必要给类关联的闭包属性相对较多, 那种措施也真是一种好措施,
能集合管理闭包属性, 代码层级结构也比较清晰.

typealias pushCompletionClosure = ()->()

extension UIViewController {
    private struct AssociatedKeys {
        static var pushCompletionKey = "pushCompletionKey"
    }

    // 嵌套闭包容器类
    class closureContainer {
        var pushCompletion: pushCompletionClosure?
    }

    // 关联容器属性
    var container: closureContainer? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.pushCompletionKey) as? closureContainer
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.pushCompletionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

代码在这里:
github

迎接大家留言斧正!

参照链接:
http://swift.gg/2016/10/11/swift-extensions-can-add-stored-properties/
http://stackoverflow.com/questions/24133058/is-there-a-way-to-set-associated-objects-in-swift/25428409\#25428409

相关文章