萨德侯爵 發表於 2023-1-6 09:27:36

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

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>@property</li><ul class="second_class_ul"><li>主要包含内容</li><li>存取器方法</li><li>读写权限</li><li>内存管理</li><ul class="third_class_ul"><li>数据结构</li><li>清除weak</li><li>添加weak</li></ul><li>原子性</li><ul class="third_class_ul"></ul></ul><li>总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>@property</h2>
<div class="cros igoods"><div class="goodsin" data-img="https://img14.360buyimg.com/pop/g10/M00/0D/0A/rBEQWVFLyOMIAAAAAAR8cxEOz2MAACmmgLwceEABHyL204.jpg" data-name="Objective-C" data-owner="京东自营" data-price="293.69" data-tgid="38" data-url="https://union-click.jd.com/jdc?e=&amp;p=JF8BALUJK1olXDYAVV5eDkgUA19MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFkkWA2wOGFgVQl9HCANtDwJvSGh4YyB3NWRrJEQPaBxiYwlRTVcZbQEHU1tVCk4UM28LHVwVXAMCZG5dCXtcbW44GmsVWgABXFlUD0kfB2sAK1sdWTZBABsJSwhfM184GGslbQYyV24DZkpEBW0IHlwTM1tQDxoOQw9XbW8IG1gRWQYCUm5fCUoVAV84"></div></div>
<p><strong>@property</strong>是OC开发中常用到的关键字,今天这篇文章就为它做一个较为系统全面的总结</p>
<p class="maodian"></p><h3>主要包含内容</h3>
<p>接下来我会分别解析</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202301/2023010609040406.png" /></p>
<p class="maodian"></p><h3>存取器方法</h3>
<p>一般访问存取器方法只需要使用.propertyName即可,需要特别指定存取器方法时可通过<strong>getter=getterName</strong>与<strong>setter=setterName</strong>,具体示例如下:</p>
<blockquote><p>// 指定getter访问名为isOpen</p>
<p>@property (nonatomic, assign, getter=isOpen) BOOL open;</p>
<p>// 指定setter方法名为setNickName:</p>
<p>@property (nonatomic, copy, setter=setNickName:) NSString *name;</p></blockquote>
<p class="maodian"></p><h3>读写权限</h3>
<ul><li>readwrite:表示自动生成对应的&nbsp;<code>getter</code>&nbsp;和&nbsp;<code>setter</code>&nbsp;方法,即可读可写权限,&nbsp;<code>readwrite</code>是编译器的默认选项。</li><li>readonly:表示只生成&nbsp;<code>getter</code>&nbsp;,不需要生成&nbsp;<code>setter</code>&nbsp;,即只可读,不可以修改。</li></ul>
<p class="maodian"></p><h3>内存管理</h3>
<ul><li>strong:指定与目标对象存在强(拥有)的关系,修饰对象的引用计数会+1,通常用来修饰对象类型,可变集合及可变字符串类型。当对象引用计数为0,即不被任何对象持有,对象就会从内存中释放</li><li>assign:不改变修饰对象的引用计数,通常用来修饰基本数据类型(<code>NSInteger</code>,<code>NSNumber</code>,<code>CGRect</code>,<code>CGFloat</code>等),也是默认属性。需要特别注意的一点是当修饰的对象的引用计数为0对象被销毁的时候,对象指针不会自动清空成为野指针,后续再次访问会产生野指针错误:<code>EXC_BAD_ACCESS</code></li><li>copy:对象会在内存中拷贝一个副本,副本引用计数为1。一般用于不可变对象的集合类型,这是为了保证进行copy操作的时候生成的都是不可变类型。 copy分深拷贝与浅拷贝,对可变与不可变对象进行copy操作结果如下:</li></ul>
<table><tbody><tr><th>源对象类型</th><th>拷贝方式</th><th>目标对象类型</th><th>拷贝类型(深|浅)</th></tr><tr><td>mutable对象</td><td>copy</td><td>不可变</td><td>深拷贝</td></tr><tr><td>mutable对象</td><td>mutableCopy</td><td>可变</td><td>深拷贝</td></tr><tr><td>immutable对象</td><td>copy</td><td>不可变</td><td>浅拷贝</td></tr><tr><td>immutable对象</td><td>mutableCopy</td><td>可变</td><td>深拷贝</td></tr></tbody></table>
<p>可以总结以下两点:</p>
<p>对mutable对象的拷贝都是深拷贝</p>
<p>所有对象的copy结果都是不可变</p>
<p>weak:弱引用关系,修饰对象的引用计数不会增加,当修饰对象被销毁的时候,对象指针会自动置为&nbsp;<code>nil</code>,主要可以用于避免循环引用;<code>weak</code>&nbsp;只能用来修饰对象类型,且是在&nbsp;<code>ARC</code>&nbsp;下新引入的修饰词,只能修饰对象,<code>MRC</code>&nbsp;下相当于使用&nbsp;<code>assign</code>。</p>
<p class="maodian"></p><h4>数据结构</h4>
<div class="jb51code"><pre class="brush:cpp;">struct SideTable {
    spinlock_t slock;// 用于给原子性操作加锁
    RefcountMap refcnts;// 引用计数hash表
    weak_table_t weak_table;// weak对象指针hash表
}
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">/**
* 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&amp;nbsp; &amp;nbsp; num_entries;// 数组中元素的个数,数组初始化的时候默认4个,占用达到3/4会翻倍扩容
    uintptr_t mask;// 计数辅助量
    uintptr_t max_hash_displacement;// hash 元素最大偏移值
};
</pre></div>
<p class="maodian"></p><h4>清除weak</h4>
<p>对象<code>dealloc</code>的时候,会调用<code>weak_clear_no_lock</code>函数将指向该对象的弱引用指针置为<code>nil</code>,具体实现如下</p>
<div class="jb51code"><pre class="brush:cpp;">// 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-&gt;out_of_line()) {
      // referrers 是一个数组,存储所有指向 referent_id 的弱引用
      referrers = entry-&gt;referrers;
      // 弱引用数组长度
      count = TABLE_SIZE(entry);   
    }
    else {
      referrers = entry-&gt;inline_referrers;
      count = WEAK_INLINE_COUNT;
    }
    // 遍历弱引用数组,将所有指向 referent_id 的弱引用全部置为 nil
    for (size_t i = 0; i &lt; count; ++i) {
      objc_object **referrer = referrers;
      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);
}
</pre></div>
<p>总结:</p>
<p>当一个对象被销毁时,在<code>dealloc</code>方法内部经过一系列的函数调用栈,通过两次哈希查找,第一次根据对象的地址找到它所在的<code>Sidetable</code>,第二次根据对象的地址在<code>Sidetable</code>的<code>weak_table</code>中找到它的弱引用表。弱引用表中存储的是对象的地址(作为<code>key</code>)和<code>weak</code>指针地址的数组(作为<code>value</code>)的映射。<code>weak_clear_no_lock</code>函数中遍历弱引用数组,将指向对象的地址的<code>weak</code>变量全都置为<code>nil</code>。</p>
<p class="maodian"></p><h4>添加weak</h4>
<p>一个被声明为<code>__weak</code>的指针,在经过编译之后。通过<code>objc_initWeak</code>函数初始化附有<code>__weak</code>修饰符的变量,在变量作用域结束时通过<code>objc_destroyWeak</code>函数销毁该变量。</p>
<div class="jb51code"><pre class="brush:cpp;">id obj = [ init];
id __weak obj1 = obj;
/*----- 编译 -----*/
id obj1;
objc_initWeak(&amp;amp;obj1,obj);
objc_destroyWeak(&amp;amp;obj1);
</pre></div>
<p><code>objc_initWeak</code>函数调用栈如下:</p>
<div class="jb51code"><pre class="brush:cpp;">// NSObject.mm
1. objc_initWeak
2. storeWeak
// objc-weak.mm
3. weak_register_no_lock
4. weak_unregister_no_lock
</pre></div>
<p>总结:</p>
<p>一个被标记为<code>__weak</code>的指针,在经过编译之后会调用<code>objc_initWeak</code>函数,<code>objc_initWeak</code>函数中初始化<code>weak</code>变量后调用<code>storeWeak</code>。添加<code>weak</code>的过程如下:<br />经过一系列的函数调用栈,最终在<code>weak_register_no_lock()</code>函数当中,进行弱引用变量的添加,具体添加的位置是通过哈希算法来查找的。如果对应位置已经存在当前对象的弱引用表(数组),那就把弱引用变量添加进去;如果不存在的话,就创建一个弱引用表,然后将弱引用变量添加进去。(weak相关实现较为复杂后续的文章会做专门解析)</p>
<p>retain:<code>MRC</code>下使用,<code>ARC</code>下使用<code>strong</code>,用来修饰对象类型,强引用对象,其修饰对象的引用计数会 +1,不会对对象分配新的内存空间。</p>
<p>unsafe_unretained:同<code>weak</code>类似,不会对对象的引用计数 +1,只能用来修饰对象类型,修饰的对象在被销毁时,其指针不会自动清空,指向的仍然是已销毁的对象,这时再调用该指针会产生野指针<code>EXC_BAD_ACCESS</code>错误。</p>
<p class="maodian"></p><h3>原子性</h3>
<p><code>atomic</code>&nbsp;原子性:系统会自动给生成的&nbsp;<code>getter/setter</code>&nbsp;方法进行加锁操作; <code>nonatomic</code>&nbsp;非原子性:系统不会给自动生成的&nbsp;<code>getter/setter</code>&nbsp;方法进行加锁操作; 设置属性函数&nbsp;<code>reallySetProperty(...)</code>&nbsp;的原子性非原子性实现如下:</p>
<div class="jb51code"><pre class="brush:cpp;">if (!atomic) {
    oldValue = *slot;
    *slot = newValue;
} else {
    spinlock_t&amp; slotlock = PropertyLocks;
    slotlock.lock();
    oldValue = *slot;
    *slot = newValue;      
    slotlock.unlock();
}
</pre></div>
<p>获取属性函数&nbsp;<code>objc_getProperty(...)</code>&nbsp;的内部实现如下:</p>
<div class="jb51code"><pre class="brush:cpp;">    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&amp; slotlock = PropertyLocks;
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
</pre></div>
<p class="maodian"></p><h2>总结</h2>
<p>由上面代码可见<code>atomic</code>只能对存取器方法加锁,并不能保障多线程下对对象的其他操作安全。</p>
<p>以上就是Objective-C关键字@property使用原理探究的详细内容,更多关于Objective-C关键字@property的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Objective-C计时器NSTimer学习笔记</li><li>深入详解Objective-C中的@Synchronized关键字</li><li>IOS开发Objective-C Runtime使用示例详解</li><li>Objective-C优雅使用KVO观察属性值变化</li><li>Objective-C&nbsp;const常量的优雅使用方法</li><li>Objective-C之Category实现分类示例详解</li><li>Objective-C的UIStackView常用属性函数学习笔记</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Objective-C关键字@property使用原理探究