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

(0)

相关推荐

  • Objective-C const常量的优雅使用方法

    目录 正文 Objective-C 的常量声明方式 在 Objective-C 中使用 let 来声明常量 正文 在编写代码时经常要使用常量,来替代 magic number.比较简单的做法是通过预处理指令 #define 来实现. #define ANIMATION_DURATION 0.3 上述预处理指令会在编译时的预处理阶段会将代码中 ANIMATION_DURATION 字符串替换为 0.3.这种定义常量的方式比较简便,但是存在两个问题: 丢失了类型信息. 若该预处理指令声明在头文件中,

  • Objective-C之Category实现分类示例详解

    目录 引言 编译时 运行时 引言 在写 Objective-C 代码的时候,如果想给没法获得源码的类增加一些方法,Category 即分类是一种很好的方法,本文将带你了解分类是如何实现为类添加方法的. 先说结论,分类中的方法会在编译时变成 category_t 结构体的变量,在运行时合并进主类,分类中的方法会放在主类中方法的前面,主类中原有的方法不会被覆盖.同时,同名的分类方法,后编译的分类方法会“覆盖”先编译的分类方法. 编译时 在编译时,所有我们写的分类,都会转化为 category_t 结

  • IOS开发Objective-C Runtime使用示例详解

    目录 前言 一些关键字 消息传递 (Messaging) KVO 关联对象 (Associated Objects) AOP(Method Swizzling) 其它 前言 Runtime 是使用 C 和汇编实现的运行时代码库,Objective-C 中有很多语言特性都是通过它来实现.了解 Runtime 开发可以帮助我们更灵活的使用 Objective-C 这门语言,我们可以将程序功能推迟到运行时再去决定怎么做,还可以利用 Runtime 来解决项目开发中的一些设计和技术问题,使开发过程更加具

  • Objective-C优雅使用KVO观察属性值变化

    目录 引言 KVOController YYCategories 引言 KVO 是苹果为我们提供的一套强大的机制,用于观察属性值的变化,但是大家在日常开发中想必多少也感受到了使用上的一些不便利,比如: 添加观察者和移除观察者的次数需要一一对应,否则会 Crash. 添加观察者和接受到属性变更通知的位置是分开的,不利于判断上下文. 多次对同一个属性值进行观察,会触发多次回调,影响业务逻辑. 为了解决上述三个问题,业界提出了一些方便开发者的开源方案,我们一起来看一下. KVOController K

  • iOS开发之Objective-c的Runtime理解指南

    目录 一.Runtime 1.概念: 2.特性:编写的代码具备有运行时.动态特性,从而衍生出 以下4.5 3.原理:Runtimer在Object-c的使用 程序在三个不同的层次上与运行时系统交互: 4.作用: 5.典型事例: 6.Objc-msgSend所做的事情 7.消息传递的关键要素 8.Msg_sender机制:先查询本类是否又该方法的实现--->如果没有逐级找父类,还有一个快速映射表(提高性能)---> 匹配方法 ---> 设置一个执行者---> 消息转发 --->

  • iOS Objective-c实现左右滑动切换页面

    本文实例为大家分享了iOS Objective-c实现左右滑动切换页面的具体代码,供大家参考,具体内容如下 ScrollView + n个view 1.storyboard布局一个ScrollView 2.拖出两个输出口,定义三个属性 @property (weak, nonatomic) IBOutlet UIScrollView *XMScrollView; @property (weak, nonatomic) IBOutlet UIView *scrollContentView; ///

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

    目录 @property 主要包含内容 存取器方法 读写权限 内存管理 数据结构 清除weak 添加weak 原子性 总结 @property @property是OC开发中常用到的关键字,今天这篇文章就为它做一个较为系统全面的总结 主要包含内容 接下来我会分别解析 存取器方法 一般访问存取器方法只需要使用.propertyName即可,需要特别指定存取器方法时可通过getter=getterName与setter=setterName,具体示例如下: // 指定getter访问名为isOpen

  • MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) P

  • Vue响应式原理模拟实现原理探究

    目录 前置知识 数据驱动 数据响应式的核心原理 Vue 2.x Vue 3.x 发布订阅和观察者模式 发布/订阅模式 观察者模式 Vue响应式原理模拟实现 Vue Observer对data中的属性进行监听 Compiler Watcher Dep 测试代码 前置知识 数据驱动 数据响应式——Vue 最标志性的功能就是其低侵入性的响应式系统.组件状态都是由响应式的 JavaScript 对象组成的.当更改它们时,视图会随即自动更新. 双向绑定——数据改变,视图改变:视图改变,数据也随之改变 数据

  • 关于Python 多重继承时metaclass conflict问题解决与原理探究

    目录 背景 什么是metaclass(元类) 类比普通class与metaclass 自定义与使用metaclass metaclass confict(元类冲突)的清晰含义 解决方案 参考 背景 最近有一个需求需要自定义一个多继承abc.ABC与django.contrib.admin.ModelAdmin两个父类的抽象子类,方便不同模块复用大部分代码,同时强制必须实现所有抽象方法,没想按想当然的写法实现多继承时,居然报错metaclass conflict: In [1]: import a

  • 简单了解java volatile关键字实现的原理

    一.volatile关键字的语义分析 1.保证可见性 对共享变量的修改,其他线程能够马上感知到.但不能保证原子性(i++) 2.保证有序性 3.volatile的原理和实现机制 有volatile修饰的共享变量进行写操作的时候会多出有 "lock"标志的汇编代码,Lock前缀的指令在多核处理器下会引发两件事情: 1)将当前处理器缓存行中的数据写回到系统内存中 2)这个写回内存的操作会使在其他cpu里缓存了该内存地址的数据无效. 二.volatile的使用场景 1.状态标志(开关模式)

  • vue双向数据绑定原理探究(附demo)

    昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与UI层的同步,数据再两者之间的任一者发生变化时都会同步更新到另一者. 双向绑定的一些方法 目前,前端实现数据双向数据绑定的方法大致有以下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定.所有绑定起来的JavaScript对象以及DOM元

  • Vue.js每天必学之内部响应式原理探究

    深入响应式原理 大部分的基础内容我们已经讲到了,现在讲点底层内容.Vue.js 最显著的一个功能是响应系统 -- 模型只是普通对象,修改它则更新视图.这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题.下面我们开始深挖 Vue.js 响应系统的底层细节. 如何追踪变化 把一个普通对象传给 Vue 实例作为它的 data 选项,Vue.js 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter.这是 ES5 特性,不能打补丁

  • C语言kmp算法简单示例和实现原理探究

    以前看过kmp算法,当时接触后总感觉好深奥啊,抱着数据结构的数啃了一中午,最终才大致看懂,后来提起kmp也只剩下"奥,它是做模式匹配的"这点干货.最近有空,翻出来算法导论看看,原来就是这么简单(下不说程序实现,思想很简单). 模式匹配的经典应用:从一个字符串中找到模式字串的位置.如"abcdef"中"cde"出现在原串第三个位置.从基础看起 朴素的模式匹配算法 A:abcdefg  B:cde 首先B从A的第一位开始比较,B++==A++,如果全

  • Java中volatile关键字实现原理

    前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好.更正确地地使用volatile关键字. CPU缓存 CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为C

  • Java Volatile关键字实现原理过程解析

    volatile的用法 volatile通常被比喻成"轻量级的synchronized",也是Java并发编程中比较重要的一个关键字.和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量.无法修饰方法及代码块等. volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了. 如以下代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用volatile关键字修饰可能被多个线程同时访问到的single

随机推荐