|
15 | 15 | }, |
16 | 16 | hexoVersion: '6.3.0' |
17 | 17 | } </script><link rel="alternate" href="/atom.xml" title="布多的博客" type="application/atom+xml"> |
18 | | -</head><body><canvas class="fireworks"></canvas><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86"><span class="toc-number">2.</span> <span class="toc-text">关联对象的底层原理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E6%B7%BB%E5%8A%A0%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1"><span class="toc-number">2.1.</span> <span class="toc-text">如何添加关联对象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1"><span class="toc-number">2.2.</span> <span class="toc-text">如何获取关联对象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E7%A7%BB%E9%99%A4%E6%89%80%E6%9C%89%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1"><span class="toc-number">2.3.</span> <span class="toc-text">如何移除所有关联对象</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%9B%B8%E5%85%B3%E7%96%91%E9%97%AE%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"><span class="toc-number">3.</span> <span class="toc-text">相关疑问与注意事项</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E5%9C%A8%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E4%B8%AD%E4%BD%BF%E7%94%A8-weak-%E5%B1%9E%E6%80%A7"><span class="toc-number">3.1.</span> <span class="toc-text">如何在关联对象中使用 weak 属性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E7%9B%B4%E6%8E%A5%E6%B7%BB%E5%8A%A0%E6%88%90%E5%91%98%E5%8F%98%E9%87%8F"><span class="toc-number">3.2.</span> <span class="toc-text">关联对象为什么不能直接添加成员变量</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E5%92%8C%E7%9C%9F%E6%AD%A3%E7%9A%84%E5%B1%9E%E6%80%A7%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB"><span class="toc-number">3.3.</span> <span class="toc-text">关联对象和真正的属性有什么区别</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E7%9A%84%E9%87%8A%E6%94%BE%E6%97%B6%E6%9C%BA"><span class="toc-number">3.4.</span> <span class="toc-text">关联对象的释放时机</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E6%97%B6%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"><span class="toc-number">3.5.</span> <span class="toc-text">使用关联对象时的注意事项</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93"><span class="toc-number">4.</span> <span class="toc-text">技术总结</span></a></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/images/avatar.jpg"></div><div class="author-info__name text-center">布多</div><div class="author-info__description text-center">前进!前进!!不择手段地前进!!!</div><div class="follow-button"><a target="_blank" rel="noopener" href="https://github.com/internetwei">Follow Me</a></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">文章</span><span class="pull-right">14</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">标签</span><span class="pull-right">10</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">分类</span><span class="pull-right">4</span></a></div><hr><div class="author-info-links"><div class="author-info-links__title text-center">友链</div><a class="author-info-links__name text-center" target="_blank" rel="noopener" href="https://www.coderqi.com/">齐小胖之家</a></div></div></div><div id="content-outer"><div id="top-container" style="background-image: url(/images/backgroundImage.jpg)"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">布多的博客</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus"> <a class="site-page" href="/">首页</a><a class="site-page" href="/tags">标签</a><a class="site-page" href="/categories">分类</a><a class="site-page" href="/archives">归档</a><a class="site-page" href="/about">关于</a></span><span class="pull-right"><a class="site-page social-icon search"><i class="fa fa-search"></i><span> 搜索</span></a></span></div><div id="post-info"><div id="post-title">iOS分类中的关联对象:如何用运行时突破Category的存储限制</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2025-04-07</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/iOS/">iOS</a><div class="post-meta-wordcount"><span>字数总计: </span><span class="word-count">4.8k</span><span class="post-meta__separator">|</span><span>阅读时长: 15 分钟</span></div></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><blockquote> |
| 18 | +</head><body><canvas class="fireworks"></canvas><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86"><span class="toc-number">2.</span> <span class="toc-text">关联对象的底层原理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E6%B7%BB%E5%8A%A0%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1"><span class="toc-number">2.1.</span> <span class="toc-text">如何添加关联对象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1"><span class="toc-number">2.2.</span> <span class="toc-text">如何获取关联对象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E7%A7%BB%E9%99%A4%E6%89%80%E6%9C%89%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1"><span class="toc-number">2.3.</span> <span class="toc-text">如何移除所有关联对象</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%9B%B8%E5%85%B3%E7%96%91%E9%97%AE%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"><span class="toc-number">3.</span> <span class="toc-text">相关疑问与注意事项</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A6%82%E4%BD%95%E5%9C%A8%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E4%B8%AD%E4%BD%BF%E7%94%A8-weak-%E5%B1%9E%E6%80%A7"><span class="toc-number">3.1.</span> <span class="toc-text">如何在关联对象中使用 weak 属性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E7%9B%B4%E6%8E%A5%E6%B7%BB%E5%8A%A0%E6%88%90%E5%91%98%E5%8F%98%E9%87%8F"><span class="toc-number">3.2.</span> <span class="toc-text">关联对象为什么不能直接添加成员变量</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E5%92%8C%E7%9C%9F%E6%AD%A3%E7%9A%84%E5%B1%9E%E6%80%A7%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB"><span class="toc-number">3.3.</span> <span class="toc-text">关联对象和真正的属性有什么区别</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E7%9A%84%E9%87%8A%E6%94%BE%E6%97%B6%E6%9C%BA"><span class="toc-number">3.4.</span> <span class="toc-text">关联对象的释放时机</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E6%97%B6%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"><span class="toc-number">3.5.</span> <span class="toc-text">使用关联对象时的注意事项</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93"><span class="toc-number">4.</span> <span class="toc-text">技术总结</span></a></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/images/avatar.jpg"></div><div class="author-info__name text-center">布多</div><div class="author-info__description text-center">前进!前进!!不择手段地前进!!!</div><div class="follow-button"><a target="_blank" rel="noopener" href="https://github.com/internetwei">Follow Me</a></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">文章</span><span class="pull-right">14</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">标签</span><span class="pull-right">10</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">分类</span><span class="pull-right">4</span></a></div><hr><div class="author-info-links"><div class="author-info-links__title text-center">友链</div><a class="author-info-links__name text-center" target="_blank" rel="noopener" href="https://www.coderqi.com/">齐小胖之家</a></div></div></div><div id="content-outer"><div id="top-container" style="background-image: url(/images/backgroundImage.jpg)"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">布多的博客</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus"> <a class="site-page" href="/">首页</a><a class="site-page" href="/tags">标签</a><a class="site-page" href="/categories">分类</a><a class="site-page" href="/archives">归档</a><a class="site-page" href="/about">关于</a></span><span class="pull-right"><a class="site-page social-icon search"><i class="fa fa-search"></i><span> 搜索</span></a></span></div><div id="post-info"><div id="post-title">iOS分类中的关联对象:如何用运行时突破Category的存储限制</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2025-04-07</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/iOS/">iOS</a><div class="post-meta-wordcount"><span>字数总计: </span><span class="word-count">5k</span><span class="post-meta__separator">|</span><span>阅读时长: 16 分钟</span></div></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><blockquote> |
19 | 19 | <p>由 布多(budo) 发布于 2025-04-07</p> |
20 | 20 | </blockquote> |
21 | 21 | <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 iOS 开发中,关联对象(Associated Objects)是一种强大的运行时特性,它允许我们在 Category 中为已有的类动态添加”属性”。众所周知,Category 的一个重要限制是无法直接添加实例变量,这是因为 Category 是在运行时动态加载的,而类的内存布局(包括实例变量的大小和偏移量)必须在编译时就确定下来。关联对象通过 Runtime 机制巧妙地绕过了这一限制,让我们能够在运行时为对象动态关联任意值,从而实现类似实例变量的效果。</p> |
@@ -54,6 +54,7 @@ <h3 id="如何添加关联对象"><a href="#如何添加关联对象" class="hea |
54 | 54 | <li>第二个参数 <code>@selector(name)</code> 对应第二层哈希表(ObjectAssociationMap)的键;</li> |
55 | 55 | <li>第三个参数 <code>@"budo"</code> 和第四个参数 <code>OBJC_ASSOCIATION_COPY_NONATOMIC</code> 则被包装成一个 ObjcAssociation 对象。</li> |
56 | 56 | </ul> |
| 57 | +<p>另外,关于内存管理修饰符的一个重要发现:OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_RETAIN 在实际运行时的行为是完全一致的,从上面的源码中也可以发现这一点,我写了一个测试,代码在 <a target="_blank" rel="noopener" href="https://github.com/internetWei/OmniTest/blob/main/%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1NONATOMIC%E7%9A%84%E4%BD%9C%E7%94%A8/ViewController.m">这里</a>,结果显示 OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_RETAIN 在实际运行时的行为是完全一致的。这是因为关联对象的所有操作都会获取全局锁来确保线程安全,所以无论是否指定 NONATOMIC,都会得到相同级别的同步保护。这一点与属性修饰符 atomic/nonatomic 的行为有所不同。</p> |
57 | 58 | <h3 id="如何获取关联对象"><a href="#如何获取关联对象" class="headerlink" title="如何获取关联对象"></a>如何获取关联对象</h3><p>获取关联对象的值,是通过 <code>objc_getAssociatedObject</code> 函数实现的。这个函数内部会调用 <code>_object_get_associative_reference</code> 来完成实际的获取操作。整个过程也是围绕着双层哈希表进行,相关的源码整理后如下所示:</p> |
58 | 59 | <figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function">id <span class="title">objc_getAssociatedObject</span><span class="params">(id object, <span class="type">const</span> <span class="type">void</span> *key)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> _object_get_associative_reference(object, key);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">id</span><br><span class="line">_object_get_associative_reference(id object, <span class="type">const</span> <span class="type">void</span> *key) {</span><br><span class="line"> ObjcAssociation association{};</span><br><span class="line"></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// 获取全局锁(在构造函数中加锁,在析构函数中解锁)</span></span><br><span class="line"> AssociationsManager manager;</span><br><span class="line"> <span class="comment">// 获取全局关联对象哈希表</span></span><br><span class="line"> <span class="function">AssociationsHashMap &<span class="title">associations</span><span class="params">(manager.get())</span></span>;</span><br><span class="line"> <span class="comment">// 获取 object 对应的关联表(内层哈希表)</span></span><br><span class="line"> AssociationsHashMap::iterator i = associations.<span class="built_in">find</span>((objc_object *)object);</span><br><span class="line"> <span class="comment">// 判断内层表是否为空</span></span><br><span class="line"> <span class="keyword">if</span> (i != associations.<span class="built_in">end</span>()) {</span><br><span class="line"> ObjectAssociationMap &refs = i->second;</span><br><span class="line"> <span class="comment">// 通过 key 找到对应数据。</span></span><br><span class="line"> ObjectAssociationMap::iterator j = refs.<span class="built_in">find</span>(key);</span><br><span class="line"> <span class="keyword">if</span> (j != refs.<span class="built_in">end</span>()) {</span><br><span class="line"> association = j->second;</span><br><span class="line"> <span class="comment">// 获取对象并执行 retain 操作。</span></span><br><span class="line"> association.<span class="built_in">retainReturnedValue</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }<span class="comment">// 这里会执行 manager 的析构函数并释放全局锁。</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将对象加入自动释放池并返回。</span></span><br><span class="line"> <span class="keyword">return</span> association.<span class="built_in">autoreleaseReturnedValue</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure> |
59 | 60 |
|
@@ -109,7 +110,7 @@ <h3 id="关联对象和真正的属性有什么区别"><a href="#关联对象和 |
109 | 110 | <li><p>性能开销:</p> |
110 | 111 | <ul> |
111 | 112 | <li>属性访问只需要一次简单的内存偏移计算。</li> |
112 | | -<li>关联对象需要哈希表查找、加解锁等多个操作步骤,性能开销较大,我在 iPhone 8 Plus(iOS15.8.3) 设备上做了一个测试,代码在 <a target="_blank" rel="noopener" href="https://github.com/internetWei/performance-testing/blob/main/performance_testing/ViewController.m">这里</a>,结果显示属性比关联对象快了 4 倍左右,这里只测试了单线程的情况,不能反映多线程下的实际性能,结果只当作一个参考。</li> |
| 113 | +<li>关联对象需要哈希表查找、加解锁等多个操作步骤,性能开销较大,我在 iPhone 8 Plus(iOS15.8.3) 设备上做了一个测试,代码在 <a target="_blank" rel="noopener" href="https://github.com/internetWei/OmniTest/blob/main/%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1%E4%B8%8E%E5%B1%9E%E6%80%A7%E7%9A%84%E8%AF%BB%E5%86%99%E6%80%A7%E8%83%BD/ViewController.m">这里</a>,结果显示属性比关联对象快了 4 倍左右,这里只测试了单线程的情况,不能反映多线程下的实际性能,结果只当作一个参考。</li> |
113 | 114 | </ul> |
114 | 115 | <p> <img src="performance.png"></p> |
115 | 116 | </li> |
|
0 commit comments