当前位置: 首页 > news >正文

【iOS】——属性关键字的底层原理

strong,retain,copy,atomic,nonatomic c++源码

@interface propertyTest : NSObject
@property (nonatomic, strong) NSString *nsstring___StrongTest;
@property (nonatomic, retain) NSString *nsstring___RetainTest;
@property (nonatomic, copy) NSString *nsstring___CopyTest;
@property (atomic, copy) NSString *nsstring___AtomicCopyTest;
@end

用clang -rewrite-objc propertyTest.m生成c++源码看如下:

// 实现propertyTest类的定义
@implementation propertyTest// 以下是对不同属性类型的实现细节// 对于强引用属性(__strong)的get方法
static NSString * _I_propertyTest_nsstring___StrongTest(propertyTest * self, SEL _cmd) {// 强引用的get方法直接返回实例变量的值。// 使用*(NSString **)来解引用指针,((char *)self + ...)计算出实例变量在内存中的位置。return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest));
}// 对于强引用属性(__strong)的set方法
static void _I_propertyTest_setNsstring___StrongTest_(propertyTest * self, SEL _cmd, NSString *nsstring___StrongTest) {// 强引用的set方法直接赋值给实例变量。(*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest)) = nsstring___StrongTest;
}// 对于retain属性的get方法
static NSString * _I_propertyTest_nsstring___RetainTest(propertyTest * self, SEL _cmd) {// retain属性的get方法同样直接返回实例变量的值。return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___RetainTest));
}// 声明objc_setProperty函数,这是一个外部定义的运行时函数。
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);// 对于retain属性的set方法
static void _I_propertyTest_setNsstring___RetainTest_(propertyTest * self, SEL _cmd, NSString *nsstring___RetainTest) {// retain属性的set方法使用objc_setProperty运行时函数,该函数负责调用retain和release。// 第四个参数为false表示不需要复制,第五个参数为false表示不需要原子性操作。objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___RetainTest), (id)nsstring___RetainTest, false, false);
}// 对于copy属性的get方法
static NSString * _I_propertyTest_nsstring___CopyTest(propertyTest * self, SEL _cmd) {// copy属性的get方法直接返回实例变量的值。return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___CopyTest));
}// 对于copy属性的set方法
static void _I_propertyTest_setNsstring___CopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___CopyTest) {// copy属性的set方法使用objc_setProperty运行时函数,该函数负责调用copy和release。// 第四个参数为false表示不需要retain,第五个参数为true表示需要复制。objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___CopyTest), (id)nsstring___CopyTest, false, true);
}// 声明objc_getProperty函数,这是一个外部定义的运行时函数。
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);// 对于原子性copy属性(atomic copy)的get方法
static NSString * _I_propertyTest_nsstring___AtomicCopyTest(propertyTest * self, SEL _cmd) {// 原子性copy属性的get方法使用objc_getProperty运行时函数,该函数负责原子性读取。// 第四个参数为true表示需要原子性操作。typedef NSString * _TYPE;return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), true);
}// 对于原子性copy属性(atomic copy)的set方法
static void _I_propertyTest_setNsstring___AtomicCopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___AtomicCopyTest) {// 原子性copy属性的set方法使用objc_setProperty运行时函数,该函数负责调用copy和release,同时保证原子性。// 第四个参数为true表示需要原子性操作,第五个参数为true表示需要复制。objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), (id)nsstring___AtomicCopyTest, true, true);
}// 结束propertyTest类的实现
@end
  • strong、copy、retain 的get方法是根据地址偏移找到对应的实例变量直接返回
  • atomic的get方法用到了objc_getProperty方法
  • strong 的set根据地址偏移找到对应的实例变量直接赋值
  • retain、copy、atomic 的set方法 用到了objc_setProperty

objc_setProperty

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy);
}

这个方法有六个参数,self指对象自身;_cmd是方法选择器;ptrdiff_t 是一个标准整型,用于表示指针之间的差值。offset是实例变量的偏移量;newValue用来赋值给属性的新值;atomic表示是否需要原子性操作;shouldCopy 代表是否应该复制属性值,如果为1,则执行拷贝操作。

objc_setProperty调用了objc_setProperty_non_gc

void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{//判断是否需要进行普通拷贝。如果 shouldCopy 非零且不等于 MUTABLE_COPY,那么需要拷贝 newValue。bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);// 判断是否需要进行可变拷贝(深拷贝)。如果 shouldCopy 等于 MUTABLE_COPY,那么需要可变拷贝 newValue。bool mutableCopy = (shouldCopy == MUTABLE_COPY);reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

这里调用了 reallySetProperty, 此函数会根据提供的参数执行属性值的设置,包括处理原子性操作和拷贝行为。该函数的最后两个参数由shouldCopy决定。

  • 如果shouldCopy=0 那么 copy = NO,mutableCopy = NO

  • 如果shouldCopy=1 那么 copy = YES,mutableCopy = NO

  • 如果shouldCopy=MUTABLE_COPY 那么 copy = NO,mutableCopy = YES

reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{// 检查 offset 是否为零。如果是零,则这意味着我们正在设置类而不是属性。if (offset == 0) {// 使用 object_setClass 函数来改变对象的类。object_setClass(self, newValue);return;}// 用于存储旧属性值的变量。id oldValue;// 计算属性槽的地址,这是对象中存储属性值的位置。id *slot = (id*) ((char*)self + offset);// 如果 copy 为真,则调用 copyWithZone: 方法创建一个新对象作为属性值。if (copy) {newValue = [newValue copyWithZone:nil];} // 如果 mutableCopy 为真,则调用 mutableCopyWithZone: 方法创建一个可变拷贝作为属性值。else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} // 否则,保留 newValue 的引用计数,除非它已经与现有的属性值相同。else {if (*slot == newValue) return;newValue = objc_retain(newValue);}// 如果设置是非原子的,直接替换旧值。if (!atomic) {oldValue = *slot;*slot = newValue;} // 如果设置是原子的,使用 spinlock 锁来保证线程安全。else {// 获取指向 slot 对应的 spinlock 的引用。spinlock_t& slotlock = PropertyLocks[slot];// 锁定 spinlock。slotlock.lock();oldValue = *slot;*slot = newValue;// 解锁 spinlock。slotlock.unlock();}// 释放旧属性值的引用计数。objc_release(oldValue);
}
  • 如果 offset 是 0,说明我们不是在设置普通的属性,而是在尝试更改对象的类,这时调用 object_setClass 函数。
  • 计算属性值在对象内存布局中的具体位置
  • 如果 copy 为真,则调用 copyWithZone: 方法进行不可变拷贝。
  • 如果 mutableCopy 为真,则调用 mutableCopyWithZone: 方法创建一个可变拷贝。
  • 如果两者都为假,检查新值是否与旧值相同;如果不相同,保留新值的引用计数。
  • 如果 atomic 为假,直接用新值替换旧值。
  • 如果 atomic 为真,使用 spinlock 锁来确保在多线程环境中设置属性的操作是线程安全的。
  • 最后,释放旧属性值的引用计数,以便正确管理内存。

从上面的源码可以得出:

copy关键字的实现最终还是调用了copyWithZone方法,用copy修饰的属性,赋值的时候,不管本身是否是可变对象,赋值给属性之后都是不可变对象。

如果copy和mutableCopy都为NO,会调用 objc_retain

retain调用set方法

retain调用set方法时调用的还是reallySetProperty函数,传入的参数为atomic参数为NO,copy参数为NO,mutableCopy也为NO, 最终的实现相当于是:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{id oldValue;id *slot = (id*) ((char*)self + offset);if (*slot == newValue) return;newValue = objc_retain(newValue);oldValue = *slot;*slot = newValue;objc_release(oldValue);
}

copy调用set方法

copy调用的还是reallySetProperty函数,传入的参数为atomic参数为NO,copy参数为YES,mutableCopy为NO, 最终的实现相当于是:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{id oldValue;//取出变量id *slot = (id*) ((char*)self + offset);newValue = [newValue copyWithZone:nil];oldValue = *slot;*slot = newValue;objc_release(oldValue);
}

atomic和copy调用set方法

调用的还是reallySetProperty函数,传入的参数为atomic参数为YES,copy参数为YES,mutableCopy为NO, 最终的实现相当于是:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{id oldValue;id *slot = (id*) ((char*)self + offset);newValue = [newValue copyWithZone:nil];spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();oldValue = *slot;*slot = newValue;        slotlock.unlock();objc_release(oldValue);
}

objc_getProperty

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{return objc_getProperty_non_gc(self, _cmd, offset, atomic);
}

这里跟objc_setProperty方法一样都相当于接口函数,调用更深层的objc_getProperty_non_gc方法

id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{// 如果 offset 是 0,这通常意味着我们试图获取的是类本身,而非普通属性。if (offset == 0) {// 调用 object_getClass 函数来获取对象的类。return object_getClass(self);}// 计算属性值所在的内存地址。id *slot = (id*) ((char*)self + offset);// 如果设置为非原子操作,直接返回属性槽中的值,不涉及引用计数操作。if (!atomic) return *slot;// 原子操作,需要锁定以防止并发修改。// 获取指向属性槽对应的 spinlock 锁。spinlock_t& slotlock = PropertyLocks[slot];// 锁定 spinlock。slotlock.lock();// 读取属性槽中的值并增加其引用计数,确保即使在读取后被其他线程释放也能安全访问。id value = objc_retain(*slot);// 解锁 spinlock。slotlock.unlock();// 为了性能考虑,我们安全地在 spinlock 外部执行 autorelease,这样可以避免锁的竞争。// objc_autoreleaseReturnValue 将值放入 autorelease pool 中,当 pool 清理时会自动释放。return objc_autoreleaseReturnValue(value);
}
  • 如果 offset 是 0,这表明我们可能试图获取对象的类,而不是属性值。此时调用 object_getClass 函数来获取对象的类。
  • 计算属性值在对象内存布局中的具体位置
  • 如果 atomicNO,那么函数直接返回属性槽中的值,没有引用计数的增加或减少操作。
  • 获取指向属性槽对应的 spinlock 锁,锁定 spinlock
  • 读取属性槽中的值,并通过 objc_retain 函数增加其引用计数
  • 解锁 spinlock,结束对属性值修改的锁定
  • 使用 objc_autoreleaseReturnValue 函数将 value 放入 autorelease pool 中,这会在适当的时机自动释放 value 的引用,从而避免内存泄漏

可以看到atomic为yes的时候,对应属性的set、get方法用到了spinlock_t锁,保证了set、get方法的线程安全,但是并不能保证其他操作的线程安全,比如对属性进行进行release操作。

相关文章:

【iOS】——属性关键字的底层原理

strong,retain,copy,atomic,nonatomic c源码 interface propertyTest : NSObject property (nonatomic, strong) NSString *nsstring___StrongTest; property (nonatomic, retain) NSString *nsstring___RetainTest; property (n…...

电影类平台如何选择服务器

电影类平台如何选择服务器 1、数据存储 电影网站对服务器的要求是比较高的,对存储空间的需求特别大,所以在服务器选择上首先要确保足够大的存储空间。另外,当你的网站内容特别多时,内存不够用,可以选择增加内存&#x…...

递归神经网络(RNN)及其预测和分类的Python和MATLAB实现

递归神经网络(Recurrent Neural Networks,RNN)是一种广泛应用于序列数据建模的深度学习模型。相比于传统的前馈神经网络,RNN具有记忆和上下文依赖性的能力,适用于处理具有时序关联性的数据,如文本、语音、时…...

以flask为后端的博客项目——星云小窝

以flask为后端的博客项目——星云小窝 文章目录 以flask为后端的博客项目——星云小窝前言一、星云小窝项目——项目介绍(一)二、星云小窝项目——项目启动(二)三、星云小窝项目——项目结构(三)四、谈论一…...

CUDA编程02 - 数据并行介绍

一:概述 数据并行是指在数据集的不同部分上执行计算工作,这些计算工作彼此相互独立且可以并行执行。许多应用程序都具有丰富的数据并行性,使其能够改造成可并行执行的程序。因此,对于程序员来说,熟悉数据并行的概念以及使用并行编程语言来编写数据并行的代码是非常重要的。…...

Android 视频音量图标

attrs.xml <?xml version"1.0" encoding"utf-8"?> <resources><!--图标颜色--><attr name"ijkSolid" format"color|reference" /><!--喇叭底座宽度--><attr name"ijkCornerWidth" form…...

VScode 修改 Markdown Preview Enhanced 字体以及大纲编号

修改字体和背景颜色 按快捷键 Ctrl , 打开设置&#xff0c;搜索 markdown-preview-enhanced.previewTheme&#xff0c;选择一个黑色主题的css&#xff0c;如 github-dark.css. 修改自动编号和背景颜色 背景颜色 按 F1 或者 Ctrl Shift P&#xff0c;输入 Customize CSS…...

TCP的FIN报文可否携带数据

问题发现&#xff1a; 发现FTP-DATA数据传输完&#xff0c;TCP的挥手似乎只有两次 实际发现FTP-DATA报文中&#xff0c;TCP层flags中携带了FIN标志 piggyback FIN 问题转化为 TCP packet中如果有FIN flag&#xff0c;该报文还能携带data数据么&#xff1f; 答案是肯定的 RFC7…...

【GoF23种设计模式+简单工厂模式】

一、设计模式概述与类型 1.1、设计模式的一般定义&#xff1a; 设计模式&#xff08;Design Pattern&#xff09;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结&#xff0c;使用设计模式是为了可重用代码&#xff0c;让代码更容易被他人理解并且保证代…...

北醒单点激光雷达更改id和波特率以及Ubuntu20.04下CAN驱动

序言&#xff1a; 需要的硬件以及软件 1、USB-CAN分析仪使用顶配pro版本&#xff0c;带有支持ubuntu下的驱动包的&#xff0c;可以读取数据。 2、电源自备24V电源 3、单点激光雷达接线使用can线可以组网。 一、更改北醒单点激光雷达的id号和波特率 安装并运行USB-CAN分析仪自带…...

【线性代数】矩阵变换

一些特殊的矩阵 一&#xff0c;对角矩阵 1&#xff0c;什么是对角矩阵 表示将矩阵进行伸缩&#xff08;反射&#xff09;变换&#xff0c;仅沿坐标轴方向伸缩&#xff08;反射&#xff09;变换。 2&#xff0c;对角矩阵可分解为多个F1矩阵&#xff0c;如下&#xff1a; 二&a…...

聚焦智慧出行,TDengine 与路特斯科技再度携手

在全球汽车行业向电动化和智能化转型的过程中&#xff0c;智能驾驶技术正迅速成为行业的焦点。随着消费者对出行效率、安全性和便利性的需求不断提升&#xff0c;汽车制造商们需要在全球范围内实现低延迟、高质量的数据传输和处理&#xff0c;以提升用户体验。在此背景下&#…...

虚拟机迁移报错:虚拟机版本与主机“x.x.x.x”的版本不兼容

1.虚拟机在VCenter上从一个ESXi迁移到另一个ESXi上时报错&#xff1a;虚拟机版本与主机“x.x.x.x”的版本不兼容。 2.例如从10.0.128.13的ESXi上迁移到10.0.128.11的ESXi上。点击10.0.128.10上的任意一台虚拟机&#xff0c;查看虚拟机版本。 3.确认要迁移的虚拟机磁盘所在位…...

【教程】vscode添加powershell7终端

win10自带的 powershell 是1.0版本的&#xff0c;太老了&#xff0c;更换为powershell7后&#xff0c;在 vscode 的集成终端中没有显示本篇教程记录在vscode添加powershell7终端的过程 打开vscode终端配置 然后来到这个页面进行设置 查看 powershell7 的安装位置&#xff…...

如何乘上第四次工业革命的大船

如何乘上第四次工业革命的大船 第四次工业革命通常被认为是信息技术和数字化时代的到来,但具体影响哪些产业,以及它将如何演变和展开,仍然是一个广泛讨论的话题。 然而,已经可以看到一些领域可能受到第四次工业革命的深远影响,例如人工智能、物联网、大数据、生物技术、可…...

RKNN执行bash ./build-linux_RK3566_RK3568.sh 报错

目录 报错信息: 原因分析: 解决办法: 报错信息: CMake Error at /usr/share/cmake-3.22/Modules/CMakeDetermineCCompiler.cmake:49 (message): Could not find compiler set in environment variable CC: aarch64-linux-gnu-gcc. Call Stack (most recent call fir…...

Linux常用命令整理

本文将分享一些常用的Linux命令。根据功能的不同&#xff0c;大概分为以下几个方面&#xff0c;一是文件相关命令&#xff0c;二是进程相关命令&#xff0c;三是网络相关命令&#xff0c;四是磁盘相关命令&#xff0c;五是用户管理相关命令&#xff0c;六是系统命令。 1. 文件…...

python 闭包、装饰器

一、闭包&#xff1a; 1. 外部函数嵌套内部函数 2. 外部函数返回内部函数 3.内部函数可以访问外部函数局部变量 闭包&#xff08;Closure&#xff09;是指在一个函数内部定义的函数&#xff0c;并且内部函数可以访问外部函数的局部变量&#xff0c;即使外部函数已经执行…...

[pycharm]解决pycharm运行程序出现卡住scanning files to index索引的问题

有时候会出现索引问题&#xff0c;显示scanning files to index 解决方法&#xff1a; in pycharm, go to the "File" on the left top, then select "invalidate caches/restart...", and press "invalidate and restart". 然后等它自己重启…...

python每日学习11:numpy库的用法(下)

python每日学习11&#xff1a;numpy库的用法(下) 数组的拼接 名方法称说明concatenate连接沿现有轴的数组序列hstack水平堆叠序列中的数组&#xff08;列方向&#xff09;vstack竖直堆叠序列中的数组&#xff08;行方向&#xff09;concatenate函数用于沿指定轴连接相同形状的两…...

AI 培训报名:主流机构专业度对比分析

引言 随着人工智能技术的快速发展&#xff0c;AI 培训市场也日益火爆。无论是企业还是个人&#xff0c;都希望通过专业的培训来提升对 AI 技术的应用能力。然而&#xff0c;当前 AI 培训市场鱼龙混杂&#xff0c;机构众多&#xff0c;质量参差不齐。企业和个人在选择 AI 培训机…...

Lean 4:用数学证明构建高可靠软件的革命性工具

Lean 4&#xff1a;用数学证明构建高可靠软件的革命性工具 【免费下载链接】lean4 Lean 4 programming language and theorem prover 项目地址: https://gitcode.com/GitHub_Trending/le/lean4 问题&#xff1a;当系统崩溃成为不可承受之重 2024年3月&#xff0c;某医疗…...

我把DeepSeek调教成了我的‘专属文案总监’:角色扮演Prompt的实战配置手册

把DeepSeek调教成你的「专属文案总监」&#xff1a;高阶Prompt工程实战指南 当市场部的Lisa第一次用AI生成产品文案时&#xff0c;她得到的是一篇充满技术术语的说明文&#xff1b;而运营总监Mike让AI写的周报&#xff0c;读起来像学术论文。这就像给米其林大厨一台高级烤箱&a…...

从河南农村到泰国拳台:张家乐在Bangla Boxing Stadium加冕泰拳冠军的荣耀

2017年&#xff0c;泰国普吉岛Bangla Boxing Stadium的聚光灯下&#xff0c;来自中国河南的拳手张家乐高举冠军奖杯&#xff0c;在这片泰拳发源地的擂台上&#xff0c;书写了中国格斗选手的荣耀篇章。这场胜利&#xff0c;不仅是他个人职业生涯的高光时刻&#xff0c;更让世界看…...

Lua代码混淆实战:基于Prometheus的Unity项目保护指南

1. 为什么你的Unity项目需要Lua代码混淆 最近有个做独立游戏的朋友跟我吐槽&#xff0c;他花半年开发的游戏上线不到一周就被破解了。更气人的是&#xff0c;破解版直接去掉了内购系统&#xff0c;还挂在第三方平台免费下载。这种情况在游戏圈太常见了&#xff0c;特别是使用Lu…...

告别格式困扰:WebPShop插件全场景应用方案

告别格式困扰&#xff1a;WebPShop插件全场景应用方案 【免费下载链接】WebPShop Photoshop plug-in for opening and saving WebP images 项目地址: https://gitcode.com/gh_mirrors/we/WebPShop 在数字设计与开发领域&#xff0c;WebP格式以其卓越的压缩效率成为优化图…...

FlyEnv-安装使用摸索记录

下载 官网地址&#xff1a;https://www.macphpstudy.com/zh/ 进入github下载&#xff0c;也可以百度网盘下载。 下载完后进行安装&#xff0c;我是选择为当前用户安装&#xff0c;没有为所有用户安装。 进入页面进行需要安装的软件&#xff1b;看上去还是有蛮多的&#xff0c…...

终极指南:如何免费将CAJ文件转换为高质量PDF?caj2pdf完整使用教程

终极指南&#xff1a;如何免费将CAJ文件转换为高质量PDF&#xff1f;caj2pdf完整使用教程 【免费下载链接】caj2pdf Convert CAJ (China Academic Journals) files to PDF. 转换中国知网 CAJ 格式文献为 PDF。佛系转换&#xff0c;成功与否&#xff0c;皆是玄学。 项目地址: …...

STM32CubeMX配置EXTI中断,别再在HAL_GPIO_EXTI_Callback里用HAL_Delay了!

STM32外部中断实战&#xff1a;避开HAL_Delay陷阱的三种解决方案 第一次在STM32项目中使用外部中断时&#xff0c;我遇到了一个令人困惑的问题——按下按键后程序突然卡死。经过反复排查&#xff0c;最终发现问题出在中断回调函数中的HAL_Delay调用上。这个看似简单的延时函数&…...

C++的std--ranges内存效率

C的std::ranges内存效率探析 在现代C编程中&#xff0c;std::ranges作为C20引入的重要特性&#xff0c;不仅简化了范围操作&#xff0c;还在内存效率方面展现出显著优势。对于需要高性能和低资源消耗的应用场景&#xff0c;理解std::ranges如何优化内存使用至关重要。本文将深…...