我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

@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表
}

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

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的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇文章主要为大家介绍了Objective-C关键字@property使用原理探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-06

Objective-C中的@Synchronized关键字怎么使用

这篇文章主要介绍“Objective-C中的@Synchronized关键字怎么使用”,在日常操作中,相信很多人在Objective-C中的@Synchronized关键字怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法
2023-07-05

详解Java中static关键字的使用和原理

static可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属于某个对象的。本文就来详细说说他的使用和原理,需要的可以参考一下
2022-11-13

夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看https://github.com/h3pl/Java-Tutorial喜欢的话麻烦点下Star哈文章首发于我的个人博客:www.how2pla
2023-06-02

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录