欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Objective-C关键字@property使用原理探究,

来源: 开发者 投稿于  被查看 44707 次 评论:58

Objective-C关键字@property使用原理探究,


目录
  • @property
    • 主要包含内容
    • 存取器方法
    • 读写权限
    • 内存管理
      • 数据结构
      • 清除weak
      • 添加weak
    • 原子性
    • 总结

      @property

      @property是OC开发中常用到的关键字,今天这篇文章就为它做一个较为系统全面的总结

      主要包含内容

      接下来我会分别解析

      存取器方法

      一般访问存取器方法只需要使用.propertyName即可,需要特别指定存取器方法时可通过getter=getterNamesetter=setterName,具体示例如下:

      // 指定getter访问名为isOpen

      @property (nonatomic, assign, getter=isOpen) BOOL open;

      // 指定setter方法名为setNickName:

      @property (nonatomic, copy, setter=setNickName:) NSString *name;

      读写权限

      • readwrite:表示自动生成对应的 getter 和 setter 方法,即可读可写权限, readwrite是编译器的默认选项。
      • readonly:表示只生成 getter ,不需要生成 setter ,即只可读,不可以修改。

      内存管理

      • strong:指定与目标对象存在强(拥有)的关系,修饰对象的引用计数会+1,通常用来修饰对象类型,可变集合及可变字符串类型。当对象引用计数为0,即不被任何对象持有,对象就会从内存中释放
      • assign:不改变修饰对象的引用计数,通常用来修饰基本数据类型(NSInteger,NSNumberCGRect,CGFloat等),也是默认属性。需要特别注意的一点是当修饰的对象的引用计数为0对象被销毁的时候,对象指针不会自动清空成为野指针,后续再次访问会产生野指针错误:EXC_BAD_ACCESS
      • copy:对象会在内存中拷贝一个副本,副本引用计数为1。一般用于不可变对象的集合类型,这是为了保证进行copy操作的时候生成的都是不可变类型。 copy分深拷贝与浅拷贝,对可变与不可变对象进行copy操作结果如下:
      源对象类型拷贝方式目标对象类型拷贝类型(深|浅)
      mutable对象copy不可变深拷贝
      mutable对象mutableCopy可变深拷贝
      immutable对象copy不可变浅拷贝
      immutable对象mutableCopy可变深拷贝

      可以总结以下两点:

      对mutable对象的拷贝都是深拷贝

      所有对象的copy结果都是不可变

      weak:弱引用关系,修饰对象的引用计数不会增加,当修饰对象被销毁的时候,对象指针会自动置为 nil,主要可以用于避免循环引用;weak 只能用来修饰对象类型,且是在 ARC 下新引入的修饰词,只能修饰对象,MRC 下相当于使用 assign

      数据结构

      struct SideTable {
          spinlock_t slock;// 用于给原子性操作加锁
          RefcountMap refcnts;// 引用计数hash表
          weak_table_t weak_table;// weak对象指针hash表
      }
      
      /**
      * The global weak references table. Stores object ids as keys,
      * and weak_entry_t structs as their values.
      */
      struct weak_table_t {
          weak_entry_t *weak_entries;// 存储 weak 对象信息的 hash 数组
          size_t    num_entries;// 数组中元素的个数,数组初始化的时候默认4个,占用达到3/4会翻倍扩容
          uintptr_t mask;// 计数辅助量
          uintptr_t max_hash_displacement;// hash 元素最大偏移值
      };
      

      清除weak

      对象dealloc的时候,会调用weak_clear_no_lock函数将指向该对象的弱引用指针置为nil,具体实现如下

      // objc-weak.mm
      /** 
       * Called by dealloc; nils out all weak pointers that point to the 
       * provided object so that they can no longer be used.
       * 
       * @param weak_table 
       * @param referent The object being deallocated. 
       */
      void 
      weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
      {
          // 获得 weak 指向的地址,即对象内存地址
          objc_object *referent = (objc_object *)referent_id; 
          // 找到管理 referent 的 entry 容器
          weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); 
          // 如果 entry == nil,表示没有弱引用需要置为 nil,直接返回
          if (entry == nil) { 
              /// XXX shouldn't happen, but does with mismatched CF/objc
              //printf("XXX no entry for clear deallocating %p\n", referent);
              return;
          }
          // zero out references
          weak_referrer_t *referrers;
          size_t count;
          if (entry->out_of_line()) { 
              // referrers 是一个数组,存储所有指向 referent_id 的弱引用
              referrers = entry->referrers; 
              // 弱引用数组长度
              count = TABLE_SIZE(entry);    
          } 
          else {
              referrers = entry->inline_referrers;
              count = WEAK_INLINE_COUNT;
          }
          // 遍历弱引用数组,将所有指向 referent_id 的弱引用全部置为 nil
          for (size_t i = 0; i < count; ++i) {
              objc_object **referrer = referrers[i];
              if (referrer) {
                  if (*referrer == referent) {
                      *referrer = nil;
                  }
                  else if (*referrer) {
                      _objc_inform("__weak variable at %p holds %p instead of %p. "
                                   "This is probably incorrect use of "
                                   "objc_storeWeak() and objc_loadWeak(). "
                                   "Break on objc_weak_error to debug.\n", 
                                   referrer, (void*)*referrer, (void*)referent);
                      objc_weak_error();
                  }
              }
          }
          // 从 weak_table 中移除对应的弱引用的管理容器
          weak_entry_remove(weak_table, entry);
      }
      

      总结:

      当一个对象被销毁时,在dealloc方法内部经过一系列的函数调用栈,通过两次哈希查找,第一次根据对象的地址找到它所在的Sidetable,第二次根据对象的地址在Sidetableweak_table中找到它的弱引用表。弱引用表中存储的是对象的地址(作为key)和weak指针地址的数组(作为value)的映射。weak_clear_no_lock函数中遍历弱引用数组,将指向对象的地址的weak变量全都置为nil

      添加weak

      一个被声明为__weak的指针,在经过编译之后。通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destroyWeak函数销毁该变量。

      id obj = [[NSObject alloc] init];
      id __weak obj1 = obj;
      /*----- 编译 -----*/
      id obj1;
      objc_initWeak(&amp;obj1,obj);
      objc_destroyWeak(&amp;obj1);
      

      objc_initWeak函数调用栈如下:

      // NSObject.mm
      1. objc_initWeak
      2. storeWeak
      // objc-weak.mm
      3. weak_register_no_lock
      4. weak_unregister_no_lock
      

      总结:

      一个被标记为__weak的指针,在经过编译之后会调用objc_initWeak函数,objc_initWeak函数中初始化weak变量后调用storeWeak。添加weak的过程如下:
      经过一系列的函数调用栈,最终在weak_register_no_lock()函数当中,进行弱引用变量的添加,具体添加的位置是通过哈希算法来查找的。如果对应位置已经存在当前对象的弱引用表(数组),那就把弱引用变量添加进去;如果不存在的话,就创建一个弱引用表,然后将弱引用变量添加进去。(weak相关实现较为复杂后续的文章会做专门解析)

      retain:MRC下使用,ARC下使用strong,用来修饰对象类型,强引用对象,其修饰对象的引用计数会 +1,不会对对象分配新的内存空间。

      unsafe_unretained:同weak类似,不会对对象的引用计数 +1,只能用来修饰对象类型,修饰的对象在被销毁时,其指针不会自动清空,指向的仍然是已销毁的对象,这时再调用该指针会产生野指针EXC_BAD_ACCESS错误。

      原子性

      atomic 原子性:系统会自动给生成的 getter/setter 方法进行加锁操作; nonatomic 非原子性:系统不会给自动生成的 getter/setter 方法进行加锁操作; 设置属性函数 reallySetProperty(...) 的原子性非原子性实现如下:

      if (!atomic) {
          oldValue = *slot;
          *slot = newValue;
      } else {
          spinlock_t& slotlock = PropertyLocks[slot];
          slotlock.lock();
          oldValue = *slot;
          *slot = newValue;        
          slotlock.unlock();
      }
      

      获取属性函数 objc_getProperty(...) 的内部实现如下:

          if (offset == 0) {
              return object_getClass(self);
          }
          // Retain release world
          id *slot = (id*) ((char*)self + offset);
          if (!atomic) return *slot;
          // Atomic retain release world
          spinlock_t& slotlock = PropertyLocks[slot];
          slotlock.lock();
          id value = objc_retain(*slot);
          slotlock.unlock();
          // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
          return objc_autoreleaseReturnValue(value);
      

      总结

      由上面代码可见atomic只能对存取器方法加锁,并不能保障多线程下对对象的其他操作安全。

      以上就是Objective-C关键字@property使用原理探究的详细内容,更多关于Objective-C关键字@property的资料请关注3672js教程其它相关文章!

      您可能感兴趣的文章:
      • iOS开发之Objective-c的Runtime理解指南
      • Objective-C优雅使用KVO观察属性值变化
      • Objective-C const常量的优雅使用方法
      • Objective-C之Category实现分类示例详解
      • iOS Objective-c实现左右滑动切换页面
      • IOS开发Objective-C Runtime使用示例详解

      用户评论