想到要如何为所有的对象增加实例变量吗?我们知道,使用Category可以很方便地为现有的类增加方法,但却无法直接增加实例变量。不过从Mac OS X v10.6开始,系统提供了Associative References,这个问题就很容易解决了。这种方法也就是所谓的关联(association),我们可以在runtime期间动态地添加任意多的属性,并且随时读取。所用到的两个重要runtime API是:

1
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
1
2
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

现在我们结合一个实际的例子来说明他们的用法。假设我们现在打算利用category对UILabel进行属性补充,添加FlashColor属性。一般我们有个原则:能用category扩展就不用继承,因为随着继承深度的增加,代码的可维护性也会增加很多。使用category可以这么做:

1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
 
@interface UILabel (Associate)//单单从头文件看是不是很像一个类?再看看.m文件你就知道这些都是假象了
 
- (nonatomic, retain) UIColor *FlashColor;
 
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
#import "UILabel+Associate.h"
 
@implementation UILabel (Associate)<br><br>@dynamic FlashColor;
 
static char flashColorKey;
 
- (void) setFlashColor:(UIColor *) flashColor{
    objc_setAssociatedObject(self, &flashColorKey, flashColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIColor *) getFlashColor{
   return objc_getAssociatedObject(self, &flashColorKey);
}
@end

上面的例子有几个需要注意的地方:

1、key:我们注意到在函数签名中key的类型const void *,这表示key仅仅是一个地址,而不是字符串的内容,这也是为说明flashColorKey没有初始化的原因,因为具体指向什么内容我们无所谓,我们要的仅仅是地址!如果在setAssocaitedObject中你传入的是flashColorKey,那get方法得到的值将会是nil。正确的应该是传入地址&flashColorKey。

2、policy:这里的policy跟属性声明中的retain、assign、copy是一样的,不再赘述

3、在implement开始处的@dynamic声明。一般来说@dynamic与@synthesize都可以用来声明属性,@synthesize是默认的声明,意思是编译器在编译阶段自动为你的属性生成setter与getter;而@dynamic则告诉编译器,别慌,小子,编译阶段不用为我生成setter与getter,在runtime我已经自己实现了setter与getter。此处我们选择@dynamic。事实上,二者曾引起stackOverFlow上强烈的争论:请点这里

下面我们再来看另一个例子,来源于APPLE GUIDE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
  
int main (int argc, const char * argv[]) {
  
    @autoreleasepool {
    /*Seciton 0. 关联数据的Key和Value*/
    static char overviewKey;
    static const char *myOwnKey = "VideoProperty\0";
    static const char intValueKey = 'i';
  
    NSArray *array = [[NSArray alloc]
            initWithObjects:@ "One", @"Two", @"Three"nil];
 
    // For the purposes of illustration, use initWithFormat: to ensure
    // we get a deallocatable string
    NSString *overview = [[NSString alloc]
            initWithFormat:@"%@", @"First three numbers"];
    NSString *videoKeyValue = @"This is a video";
    NSNumber *intValue = [[NSNumber alloc]initWithInt:5];
 
    /*Section 1. 关联数据设置部分*/
    objc_setAssociatedObject (
            array,
            &overviewKey,
            overview,
            OBJC_ASSOCIATION_RETAIN
        );
        [overview release];
 
    objc_setAssociatedObject (
        array,
        myOwnKey,
        videoKeyValue,
        OBJC_ASSOCIATION_RETAIN
    );
 
    objc_setAssociatedObject (
        array,
        &intValueKey,
        intValue,
        OBJC_ASSOCIATION_RETAIN
    );
  
    /*Section 3. 关联数据查询部分*/
    NSString *associatedObject =  (NSString *) objc_getAssociatedObject (array, &overviewKey);
    NSLog(@"associatedObject: %@", associatedObject);
    NSString *associatedObject2 = (NSString *) objc_getAssociatedObject(array, myOwnKey);
    NSLog(@"Video Key value is %@", associatedObject2);
    NSString *assObject3 = (NSString *) objc_getAssociatedObject(array, &myOwnKey);
    if( assObject3 )
    {
        NSLog(@"不会进入这里! assObject3 应当为nil!");
    }
    else
    {
        NSLog(@"OK. 通过myOwnKey的地址是得不到数据的!");
    }
    NSNumber *assKeyValue = (NSNumber *) objc_getAssociatedObject(array, &intValueKey);
    NSLog(@"Int value is %d",[assKeyValue intValue]);
     
    /*Section 3. 关联数据清理部分*/
    objc_setAssociatedObject (
            array,
            &overviewKey,
            nil,
            OBJC_ASSOCIATION_ASSIGN
        );
 
    objc_setAssociatedObject (
        array,
        myOwnKey,
        nil,
        OBJC_ASSOCIATION_ASSIGN
    );
     
    objc_setAssociatedObject (
        array,
        &intValueKey,
        nil,
        OBJC_ASSOCIATION_ASSIGN
    );
        [array release];
  
    }
    return 0;
}

编程小翁@博客园,谢谢 前辈资料.

iOS动态性:动态添加属性的方法——关联(e.g. 向Category添加属性)的更多相关文章

  1. 【原】iOS动态性(一):动态添加属性的方法——关联(e.g. 向Category添加属性)

    想到要如何为所有的对象增加实例变量吗?我们知道,使用Category可以很方便地为现有的类增加方法,但却无法直接增加实例变量.不过从Mac OS X v10.6开始,系统提供了Associative ...

  2. Python属性、方法和类管理系列之----__slots__属性

    一句话说明 __slots__是用来限制实例的属性的,__slots__可以规定实例是否应该有__dict__属性:__slots__不能限制类的属性. 只有__slots__列表内的这些变量名可赋值 ...

  3. iOS Category 添加属性实现原理 - 关联对象

    iOS Category 添加属性实现原理 - 关联对象 RunTime为Category动态关联对象 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系.对象一开始初始化的时候其属性 ...

  4. prototype为对象添加属性和方法

    可以通过prototype来为已经定义好的的"类"添加属性和方法.这里来了解一下prototype的基础知识.prototype是"构造函数"的属性,不是实例的 ...

  5. C#动态引用DLL的方法

    C#编程中,使用dll调用是经常的事,这样做的好处是非常多的,比如把某些功能封装到一个dll中,然后主程序动态调用这个dll. 废话不多说,举例说明如下. 首先,我们需要封装一个dll,vs2008下 ...

  6. Javascript 面向对象(共有方法,私有方法,特权方法,静态属性和方法,静态类)示例讲解

    一,私有属性和方法 私有方法:私有方法本身是可以访问类内部的所有属性(即私有属性和公有属性),但是私有方法是不可以在类的外部被调用. <script> /* * 私有方法:私有方法本身是可 ...

  7. ScriptManager的几个属性和方法

    ScriptManager的几个属性和方法   一.EnablePageMethods ScriptManager的EnablePageMethods属性用于设定客户端javascript直接调用服务 ...

  8. JavaScript:如何获得 Private、Privileged、Public 和 Static 成员(属性和方法)【翻译+整理】

    本文内容 背景 把我们的对象放在一起 添加一个私有(Private)的属性 添加一个特权(Privileged)的方法 添加一个公共(Public)的属性和方法 添加一个静态(Static)的属性 我 ...

  9. 《前端之路》- TypeScript (四) class 中各类属性、方法,抽象类、多态

    目录 一.TypeScript 中的类 二.TypeScript 中类的继承 三.TypeScript 中公共,私有与受保护的修饰符 3-1.属性的 public 3-2.属性的 private 3- ...

随机推荐

  1. 源码怎么找之rest_framework的用户认证

    首先得有一点常识,比如用户认证,就是authenticate 比如一个函数,应该有返回值, 比如一个类里面的self,真的是代表本身这个类吗 再比如看到一个东西加括号,就两种情况,一种是函数,一种是类 ...

  2. WebService的基本介绍

    一.WebService的基本介绍    1.WebService是什么? WebService ---> Web Service web的服务    2.思考问题: WebService是we ...

  3. Ceph编译安装教程

    Ceph官方版本目前支持的纠删码很有限,实验室这块希望能够整合我们自主开发的纠删码BRS(Binary Reed–Solomon encoding),所以需要编译Ceph环境.Ceph官方目前推荐的安 ...

  4. 2712:细菌繁殖-poj

    2712:细菌繁殖 总时间限制:  1000ms 内存限制:  65536kB 描述 一种细菌的繁殖速度是每天成倍增长.例如:第一天有10个,第二天就变成20个,第三天变成40个,第四天变成80个,… ...

  5. 网页的居中显示,使用了margin、clear:both

    很久没写过页面了,现在写起来也觉得捡起来还是挺快的. 当时遇到了这样的问题,我有一个大的div包涵了整个网站,有网页头部,中部还有底部.头部就是一个标题,中部就是几张图片跟文字排版,结果左右两边的图片 ...

  6. 晓莲说-何不原创:如何通过jad把class批量反编译成java文件

    背景:前几天在项目开发的时候遇到一个问题,那就是利用myeclipse编写好的一个项目打包成jar包后上传部署到服务器里,之后本地的项目被自己改来改去出现了一些问题,想着把上传到服务器里面的war包下 ...

  7. Spring JDBC(一)jdbcTemplate

    前言 最近工作中经常使用Spring JDBC操作数据库,也断断续续的看了一些源码,便有了写一些总结的想法,希望在能帮助别人的同时,也加深一下自己对Spring JDBC的理解. Spring JDB ...

  8. MySql的技术规范-企业版(来源于MySql官网)

    MySQL技术规范 Oracle MySQL云服务 MySQL数据库即服务 灵活的架构 开源 多线程 可插拔的存储引擎架构 InnoDB,NDB集群 MyISAM,合并,内存,存档 ANSI SQL标 ...

  9. vs 2015工具栏添加Tab Order

    1. 在工具栏右键,弹出菜单,选中“Customize”菜单项. 2. 选中Commands标签页,选择Toolbar,选择自己要加入的Tab order的类别,之后点击“Add Command”按钮 ...

  10. BFS求最短路 Abbottt's Revenge UVa 816

    本题的题意是输入起点,朝向和终点,求一条最短路径(多解时任意输出一个即可) 本题的主要代码是bfs求解,就是以下代码中的slove的主要部分,通过起点按照路径的长度来寻找最短路径,输出最先到终点的一系 ...