我们在 iOS 开发中经常需要使用分类(Category),为已经存在的类添加属性的需求,但是使用 @property 并不能在分类中正确创建实例变量和存取方法。

不过,通过 Objective-C 运行时中的关联对象,也就是 Associated Object,我们可以实现上述需求。

写在前面

这篇文章包含了两方面的内容:

注:如果你刚刚入门 iOS 开发,笔者相信了解第一部分的内容会对你的日常开发中有所帮助,不过第二部分的内容可能有些难以理解。

如果你对关联对象的使用非常熟悉,可以直接跳过第一部分的内容,从这里开始深入了解其底层实现。

关联对象的应用

关于关联对象的使用相信已经成为了一个老生常谈的问题了,不过为了保证这篇文章的完整性,笔者还是会在这里为各位介绍这部分的内容的。

分类中的 @property

@property 可以说是一个 Objective-C 编程中的“宏”,它有元编程的思想。

  1. @interface DKObject : NSObject
  2. @property (nonatomic, strong) NSString *property;
  3. @end

在使用上述代码时会做三件事:

  • 生成实例变量 _property
  • 生成 getter 方法 - property
  • 生成 setter 方法 - setProperty:
  1. @implementation DKObject {
  2. NSString *_property;
  3. }
  4. - (NSString *)property {
  5. return _property;
  6. }
  7. - (void)setProperty:(NSString *)property {
  8. _property = property;
  9. }
  10. @end

这些代码都是编译器为我们生成的,虽然你看不到它,但是它确实在这里,我们既然可以在类中使用 @property 生成一个属性,那么为什么在分类中不可以呢?

我们来做一个小实验:创建一个 DKObject 的分类 Category,并添加一个属性 categoryProperty

  1. @interface DKObject (Category)
  2. @property (nonatomic, strong) NSString *categoryProperty;
  3. @end

看起来还是很不错的,不过 Build 一下这个 Demo,会发现有这么一个警告:

 
objc-ao-warning-category-property

在这里的警告告诉我们 categoryProperty 属性的存取方法需要自己手动去实现,或者使用 @dynamic 在运行时实现这些方法。

换句话说,分类中的 @property 并没有为我们生成实例变量以及存取方法,而需要我们手动实现。

使用关联对象

Q:我们为什么要使用关联对象?

A:因为在分类中 @property 并不会自动生成实例变量以及存取方法,所以一般使用关联对象为已经存在的类添加『属性』

上一小节的内容已经给了我们需要使用关联对象的理由。在这里,我们会介绍 ObjC 运行时为我们提供的与关联对象有关的 API,并在分类中实现一个伪属性

  1. #import "DKObject+Category.h"
  2. #import <objc/runtime.h>
  3. @implementation DKObject (Category)
  4. - (NSString *)categoryProperty {
  5. return objc_getAssociatedObject(self, _cmd);
  6. }
  7. - (void)setCategoryProperty:(NSString *)categoryProperty {
  8. objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  9. }
  10. @end

这里的 _cmd 代指当前方法的选择子,也就是 @selector(categoryProperty)

我们使用了两个方法 objc_getAssociatedObject 以及 objc_setAssociatedObject 来模拟『属性』的存取方法,而使用关联对象模拟实例变量。

在这里有必要解释两个问题:

  1. 为什么向方法中传入 @selector(categoryProperty)
  2. OBJC_ASSOCIATION_RETAIN_NONATOMIC 是干什么的?

关于第一个问题,我们需要看一下这两个方法的原型:

  1. id objc_getAssociatedObject(id object, const void *key);
  2. void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

@selector(categoryProperty) 也就是参数中的 key,其实可以使用静态指针 static void * 类型的参数来代替,不过在这里,笔者强烈推荐使用 @selector(categoryProperty) 作为 key 传入。因为这种方法省略了声明参数的代码,并且能很好地保证 key 的唯一性。

OBJC_ASSOCIATION_RETAIN_NONATOMIC 又是什么呢?如果我们使用 Command 加左键查看它的定义:

  1. typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
  2. OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
  3. OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
  4. * The association is not made atomically. */
  5. OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
  6. * The association is not made atomically. */
  7. OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
  8. * The association is made atomically. */
  9. OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
  10. * The association is made atomically. */
  11. };

从这里的注释我们能看到很多东西,也就是说不同的 objc_AssociationPolicy 对应了不通的属性修饰符:

objc_AssociationPolicy modifier
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic, copy
OBJC_ASSOCIATION_RETAIN atomic, strong
OBJC_ASSOCIATION_COPY atomic, copy

而我们在代码中实现的属性 categoryProperty 就相当于使用了 nonatomicstrong 修饰符。

关于属性修饰符的区别,并不是这篇文章的主要内容,如果你需要了解它们的区别,Google 是一个很好的选择。

到这里,我们已经完成了对关联对象应用的介绍,再来回顾一下小节的内容。

@property` 其实有元编程的思想,它能够为我们自动生成实例变量以及存取方法,而这三者构成了属性这个类似于语法糖的概念,为我们提供了更便利的点语法来访问属性:

  1. self.property <=> [self property]
  2. self.property = value <=> [self setProperty:value]

在分类中,因为类的实例变量的布局已经固定,使用 @property 已经无法向固定的布局中添加新的实例变量(这样做可能会覆盖子类的实例变量),所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素

如果你是一个 iOS 开发方面的新手,我相信这篇文章的前半部分对已经足够使用了,不过,如果你还对关联对象的实现非常感兴趣,也可以尝试阅读下面的内容。

关联对象的实现

探索关联对象的实现一直是我想要做的一件事情,直到最近,我才有足够的时间来完成这篇文章,希望能够对各位读者有所帮助。

这一部分会从三个 objc 运行时的方法为入口来对关联对象的实现一探究竟,其中两个方法是上一部分使用到的方法:

  1. void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
  2. id objc_getAssociatedObject(id object, const void *key);
  3. void objc_removeAssociatedObjects(id object);

三个方法的作用分别是:

  • 以键值对形式添加关联对象
  • 根据 key 获取关联对象
  • 移除所有关联对象

而接下来的内容自然就是围绕这三个方法进行的,我们会对它们的实现进行分析。

objc_setAssociatedObject

首先是 objc_setAssociatedObject 方法,这个方法的调用栈并不复杂:

  1. void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
  2. └── void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy)
  3. └── void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)

调用栈中的 _object_set_associative_reference 方法实际完成了设置关联对象的任务:

  1. void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
  2. ObjcAssociation old_association(0, nil);
  3. id new_value = value ? acquireValue(value, policy) : nil;
  4. {
  5. AssociationsManager manager;
  6. AssociationsHashMap &associations(manager.associations());
  7. ObjectAssociationMap *refs = i->second;
  8. ...
  9. }
  10. if (old_association.hasValue()) ReleaseValue()(old_association);
  11. }

在这里的实现省略了大多的实现代码,而且忽略了很多逻辑上的顺序,不过不要在意这里的代码能否执行。

我们需要注意其中的几个类和数据结构,因为在具体分析这个方法的实现之前,我们需要了解其中它们的作用:

  • AssociationsManager
  • AssociationsHashMap
  • ObjcAssociationMap
  • ObjcAssociation

AssociationsManager

AssociationsManager 在源代码中的定义是这样的:

  1. class AssociationsManager {
  2. static spinlock_t _lock;
  3. static AssociationsHashMap *_map;
  4. public:
  5. AssociationsManager() { _lock.lock(); }
  6. ~AssociationsManager() { _lock.unlock(); }
  7. AssociationsHashMap &associations() {
  8. if (_map == NULL)
  9. _map = new AssociationsHashMap();
  10. return *_map;
  11. }
  12. };
  13. spinlock_t AssociationsManager::_lock;
  14. AssociationsHashMap *AssociationsManager::_map = NULL;

它维护了 spinlock_tAssociationsHashMap 的单例,初始化它的时候会调用 lock.lock() 方法,在析构时会调用 lock.unlock(),而 associations 方法用于取得一个全局的 AssociationsHashMap 单例。

也就是说 AssociationsManager 通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作

如何存储 ObjcAssociation

ObjcAssociation 就是真正的关联对象的类,上面的所有数据结构只是为了更好的存储它。

首先,AssociationsHashMap 用与保存从对象的 disguised_ptr_tObjectAssociationMap 的映射:

  1. class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
  2. public:
  3. void *operator new(size_t n) { return ::malloc(n); }
  4. void operator delete(void *ptr) { ::free(ptr); }
  5. };

ObjectAssociationMap 则保存了从 key 到关联对象 ObjcAssociation 的映射,这个数据结构保存了当前对象对应的所有关联对象

  1. class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
  2. public:
  3. void *operator new(size_t n) { return ::malloc(n); }
  4. void operator delete(void *ptr) { ::free(ptr); }
  5. };

最关键的 ObjcAssociation 包含了 policy 以及 value

  1. class ObjcAssociation {
  2. uintptr_t _policy;
  3. id _value;
  4. public:
  5. ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
  6. ObjcAssociation() : _policy(0), _value(nil) {}
  7. uintptr_t policy() const { return _policy; }
  8. id value() const { return _value; }
  9. bool hasValue() { return _value != nil; }
  10. };

举一个简单的例子来说明关联对象在内存中以什么形式存储的,以下面的代码为例:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. NSObject *obj = [NSObject new];
  4. objc_setAssociatedObject(obj, @selector(hello), @"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  5. }
  6. return 0;
  7. }

这里的关联对象 ObjcAssociation(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @"Hello") 在内存中是这么存储的:

 
objc-ao-associateobjcect

接下来我们可以重新回到对 objc_setAssociatedObject 方法的分析了。

在这里会将方法的执行分为两种情况:

  • new_value != nil 设置/更新关联对象的值
  • new_value == nil 删除一个关联对象

new_value != nil

先来分析在 new_value != nil 的情况下,该方法的执行是什么样的:

  1. void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
  2. ObjcAssociation old_association(0, nil);
  3. id new_value = value ? acquireValue(value, policy) : nil;
  4. {
  5. AssociationsManager manager;
  6. AssociationsHashMap &associations(manager.associations());
  7. disguised_ptr_t disguised_object = DISGUISE(object);
  8. AssociationsHashMap::iterator i = associations.find(disguised_object);
  9. if (i != associations.end()) {
  10. ObjectAssociationMap *refs = i->second;
  11. ObjectAssociationMap::iterator j = refs->find(key);
  12. if (j != refs->end()) {
  13. old_association = j->second;
  14. j->second = ObjcAssociation(policy, new_value);
  15. } else {
  16. (*refs)[key] = ObjcAssociation(policy, new_value);
  17. }
  18. } else {
  19. ObjectAssociationMap *refs = new ObjectAssociationMap;
  20. associations[disguised_object] = refs;
  21. (*refs)[key] = ObjcAssociation(policy, new_value);
  22. object->setHasAssociatedObjects();
  23. }
  24. }
  25. if (old_association.hasValue()) ReleaseValue()(old_association);
  26. }
  1. 使用 old_association(0, nil) 创建一个临时的 ObjcAssociation 对象(用于持有原有的关联对象,方便在方法调用的最后释放值)

  2. 调用 acquireValuenew_value 进行 retain 或者 copy

    1. static id acquireValue(id value, uintptr_t policy) {
    2. switch (policy & 0xFF) {
    3. case OBJC_ASSOCIATION_SETTER_RETAIN:
    4. return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
    5. case OBJC_ASSOCIATION_SETTER_COPY:
    6. return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    7. }
    8. return value;
    9. }
  3. 初始化一个 AssociationsManager,并获取唯一的保存关联对象的哈希表 AssociationsHashMap

    1. AssociationsManager manager;
    2. AssociationsHashMap &associations(manager.associations());
  4. 先使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap

  5. 如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法,表明当前对象含有关联对象

    1. ObjectAssociationMap *refs = new ObjectAssociationMap;
    2. associations[disguised_object] = refs;
    3. (*refs)[key] = ObjcAssociation(policy, new_value);
    4. object->setHasAssociatedObjects();
  6. 如果找到了对应的 ObjectAssociationMap,就要看 key 是否存在了,由此来决定是更新原有的关联对象,还是增加一个

    1. ObjectAssociationMap *refs = i->second;
    2. ObjectAssociationMap::iterator j = refs->find(key);
    3. if (j != refs->end()) {
    4. old_association = j->second;
    5. j->second = ObjcAssociation(policy, new_value);
    6. } else {
    7. (*refs)[key] = ObjcAssociation(policy, new_value);
    8. }
  7. 最后的最后,如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值

    1. struct ReleaseValue {
    2. void operator() (ObjcAssociation &association) {
    3. releaseValue(association.value(), association.policy());
    4. }
    5. };
    6. static void releaseValue(id value, uintptr_t policy) {
    7. if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
    8. ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
    9. }
    10. }

到这里,该条件下的方法实现就结束了。

new_value == nil

如果 new_value == nil,就说明我们要删除对应 key 的关联对象,实现如下:

  1. void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
  2. ObjcAssociation old_association(0, nil);
  3. id new_value = value ? acquireValue(value, policy) : nil;
  4. {
  5. AssociationsManager manager;
  6. AssociationsHashMap &associations(manager.associations());
  7. disguised_ptr_t disguised_object = DISGUISE(object);
  8. AssociationsHashMap::iterator i = associations.find(disguised_object);
  9. if (i != associations.end()) {
  10. ObjectAssociationMap *refs = i->second;
  11. ObjectAssociationMap::iterator j = refs->find(key);
  12. if (j != refs->end()) {
  13. old_association = j->second;
  14. refs->erase(j);
  15. }
  16. }
  17. }
  18. if (old_association.hasValue()) ReleaseValue()(old_association);
  19. }

这种情况下方法的实现与前面的唯一区别就是,我们会调用 erase 方法,擦除 ObjectAssociationMapkey 对应的节点。

setHasAssociatedObjects()

其实上面的两种情况已经将 objc_setAssociatedObject 方法的实现分析得很透彻了,不过,这里还有一个小问题来等待我们解决,setHasAssociatedObjects() 方法的作用是什么?

  1. inline void objc_object::setHasAssociatedObjects() {
  2. if (isTaggedPointer()) return;
  3. retry:
  4. isa_t oldisa = LoadExclusive(&isa.bits);
  5. isa_t newisa = oldisa;
  6. if (!newisa.indexed) return;
  7. if (newisa.has_assoc) return;
  8. newisa.has_assoc = true;
  9. if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
  10. }

它会将 isa 结构体中的标记位 has_assoc 标记为 true,也就是表示当前对象有关联对象,在这里我还想祭出这张图来介绍 isa 中的各个标记位都是干什么的。

 
objc-ao-isa-struct

如果想要了解关于 isa 的知识,可以阅读从 NSObject 的初始化了解 isa

objc_getAssociatedObject

我们既然已经对 objc_setAssociatedObject 的实现已经比较熟悉了,相信对于 objc_getAssociatedObject 的理解也会更加容易。

方法的调用栈和 objc_setAssociatedObject 非常相似:

  1. id objc_getAssociatedObject(id object, const void *key)
  2. └── id objc_getAssociatedObject_non_gc(id object, const void *key);
  3. └── id _object_get_associative_reference(id object, void *key)

_object_get_associative_reference 相比于前面方法的实现更加简单。

  1. id _object_get_associative_reference(id object, void *key) {
  2. id value = nil;
  3. uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
  4. {
  5. AssociationsManager manager;
  6. AssociationsHashMap &associations(manager.associations());
  7. disguised_ptr_t disguised_object = DISGUISE(object);
  8. AssociationsHashMap::iterator i = associations.find(disguised_object);
  9. if (i != associations.end()) {
  10. ObjectAssociationMap *refs = i->second;
  11. ObjectAssociationMap::iterator j = refs->find(key);
  12. if (j != refs->end()) {
  13. ObjcAssociation &entry = j->second;
  14. value = entry.value();
  15. policy = entry.policy();
  16. if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
  17. }
  18. }
  19. }
  20. if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
  21. ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
  22. }
  23. return value;
  24. }

代码中寻找关联对象的逻辑和 objc_setAssociatedObject 差不多:

  1. 获取静态变量 AssociationsHashMap

  2. DISGUISE(object) 为 key 查找 AssociationsHashMap

  3. void *key 为 key 查找 ObjcAssociation

  4. 根据 policy 调用相应的方法

    1. if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
    2. if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
    3. ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    4. }
  5. 返回关联对象 ObjcAssociation 的值

objc_removeAssociatedObjects

关于最后的 objc_removeAssociatedObjects 方法,其实现也相对简单,这是方法的调用栈:

  1. void objc_removeAssociatedObjects(id object)
  2. └── void _object_remove_assocations(id object)

这是简化版本的 objc_removeAssociatedObjects 方法实现:

  1. void objc_removeAssociatedObjects(id object) {
  2. if (object && object->hasAssociatedObjects()) {
  3. _object_remove_assocations(object);
  4. }
  5. }

为了加速移除对象的关联对象的速度,我们会通过标记位 has_assoc 来避免不必要的方法调用,在确认了对象和关联对象的存在之后,才会调用 _object_remove_assocations 方法移除对象上所有的关联对象:

  1. void _object_remove_assocations(id object) {
  2. vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
  3. {
  4. AssociationsManager manager;
  5. AssociationsHashMap &associations(manager.associations());
  6. if (associations.size() == 0) return;
  7. disguised_ptr_t disguised_object = DISGUISE(object);
  8. AssociationsHashMap::iterator i = associations.find(disguised_object);
  9. if (i != associations.end()) {
  10. ObjectAssociationMap *refs = i->second;
  11. for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
  12. elements.push_back(j->second);
  13. }
  14. delete refs;
  15. associations.erase(i);
  16. }
  17. }
  18. for_each(elements.begin(), elements.end(), ReleaseValue());
  19. }

方法会将对象包含的所有关联对象加入到一个 vector 中,然后对所有的 ObjcAssociation 对象调用 ReleaseValue() 方法,释放不再被需要的值。

小结

关于应用

本来在这个系列的文章中并不会涉及关联对象这个话题,不过,有人问过我这么一个问题:在分类中到底能否实现属性?其实在回答这个问题之前,首先要知道到底属性是什么?而属性的概念决定了这个问题的答案。

  • 如果你把属性理解为通过方法访问的实例变量,我相信这个问题的答案是不能,因为分类不能为类增加额外的实例变量
  • 不过如果属性只是一个存取方法以及存储值的容器的集合,那么分类是可以实现属性的。

分类中对属性的实现其实只是实现了一个看起来像属性的接口而已

关于实现

关联对象又是如何实现并且管理的呢:

  • 关联对象其实就是 ObjcAssociation 对象
  • 关联对象由 AssociationsManager 管理并在 AssociationsHashMap 存储
  • 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap
  • ObjectAssociationMap 则是用于存储关联对象的数据结构
  • 每一个对象都有一个标记位 has_assoc 指示对象是否含有关联对象

关注仓库,及时获得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

原文链接: http://draveness.me/ao

  1.  

关联对象 AssociatedObject 完全解析的更多相关文章

  1. 使用关联对象(AssociatedObject)为UIButton添加Block响应

    在开发中,要给UIButton添加点击事件的话,通常的做法是这样的 UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [ ...

  2. AssociatedObject关联对象原理实现

    介绍 关联对象(AssociatedObject)是Objective-C 2.0运行时的一个特性,允许开发者对已经存在的类在扩展中添加自定义的属性.在实际生产过程中,比较常用的方式是给分类(Cate ...

  3. 【OC底层】AssociatedObject 关联对象

    如何实现给分类“添加成员变量”? 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中.但可以通过关联对象来间接实现 关联对象提供了以下API 1> 添加关联对象 void objc_s ...

  4. Effective Objective-C 2.0 — 第10条:在既有类中使用关联对象存放自定义数据

    可以通过“关联对象”机制来把两个对象连起来 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系” 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于 ...

  5. ios 关联对象运用 objc_setAssociatedObject

    点按钮的时候,给alertView添加一个关联对象(被点击这个按钮), objc_setAssociatedObject(alert, &kRepresentedObject, sender, ...

  6. Mybatis之ResultMap一个简短的引论,关联对象

    基础部分能够查看我的还有一篇博客http://blog.csdn.net/elim168/article/details/40622491 MyBatis中在查询进行select映射的时候.返回类型能 ...

  7. iOS开发--Runtime的简单使用之关联对象

    一.Runtime关联对象的方法简介: 在<objc/runtime.h>中,有三个关联的方法,分别是: objc_setAssociatedObject objc_getAssociat ...

  8. 小解系列-自关联对象.Net MVC中 json序列化循环引用问题

    自关联对象在实际开发中用的还是比较多,例如常见的树形菜单.本文是自己实际的一个小测试,可以解决循环引用对象的json序列化问题,文笔不好请多见谅,如有错误请指出,希望有更好的解决方案,一起进步. 构造 ...

  9. MyBatis之ResultMap简介,关联对象

    MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap,resultType是直接表示返回类型的,而resultMap则是对外部ResultM ...

随机推荐

  1. Android WebView回退

    在使用webView时,会出现点击按钮让网页页面回到上一个页面的需求,这时可以使用goBack方法. 但是有的安卓用户会习惯点击手机自带的返回按钮,这时会直接关闭当前的activity,而不是网页页面 ...

  2. JQurey大纲

  3. 不懂技术也可以轻松开发一款APP

    这是个衣食住行都离不开手机的时代,甚至可以说,我们不用考虑其他的东西,只要拿着手机,就可以出门做自己想做的事情. 这就是手机app的强大之处,覆盖面极广,小到聊天交友,大到投资理财.每次都是app为我 ...

  4. ZBrush中如何反选遮罩

    通过对ZBrush的学习,我们知道了如何手动创建遮罩,手动创建遮罩相对来说是最简单有效的方法,在某些特定的使用场合会起到事半功倍的效果.创建遮罩我们可以结合Ctrl键在物体保持编辑的状态下来执行,您可 ...

  5. ZBrush软件中的笔触类型

    在ZBrush® 中我们通过各种笔触类型,确定在使用ZBrush®画笔进行绘制时画笔的变化方式及状态.使用多种画笔绘制根据选择不同的笔触组合绘制,能够得到繁多变化丰富的制作效果. 选择笔触的类型 点击 ...

  6. 搭建hadoop、hdfs环境--ubuntu(完全分布式)

    最近在学习hadoop相关知识,就在本机上安装了hadoop,遇到了一些坑,也学到了不少.仅此记录我的安装过程,及可能遇到的问题.供参考.交流沟通见页末. 软件准备 >  虚拟机(VMware) ...

  7. 洛谷 p2618 数字工程 记忆化搜索_ 线性筛

    我们在线筛的同时处理出每个数的所有质因子,记忆化搜索的时候直接枚举质因子即可. 时间复杂度为 O(nlogn)O(nlogn)O(nlogn) Code: #include<cstdio> ...

  8. JS 日历

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx. ...

  9. CefSharp获取页面Html代码的两种方式

    CefSharp在NuGet的简介是“The CefSharp Chromium-based browser component”,机翻的意思就是“基于Cefsharp Chromium的浏览器组件” ...

  10. ZOJ 1081 Points Within( 判断点在多边形内外 )

    链接:传送门 题意:给出n个点围成的一个多边形,现在有m个点p,询问p是否在多边形内,你可以认为这些点均不同且输入的顶点是多边形中相邻的两个顶点,最后的顶点与第一个相邻并且每一个顶点都连接两条边( 左 ...