iOS 属性 @property 详细探究

引言

@property 应该是面试过程中被问到最多的一个技术点,既能考察一个人的基础,又能挖掘一个人对技术细节的掌握度,本文着重全面,细致的介绍一下 @property 都有哪些技术点值得我们关注。

声明 @property 时,注意关键词及字符间的空格。

@property (nonios鲁多多appatomic, copy) NSString *name;

  • 本质

@property 的本质其实是线程的几种状态ivar (实例变量) + getter + setter

  • 常用关键词

// 存取器方法
1. getter=getterName
2. setter=setterName
// 读写权限
1. readonly
2. readwrite
// 内存管理
1. strong
2. assign
3. copy
4. weak
5. retain
6. unsafe_unretained
// 原子性
1. nonatomic
2. atomic

接下来逐个介绍一下,每个关键词的作用:

存取器方法

  1. getter=getterName
  2. setter=setterName:

指定获取属ios系统性对象的名字为 g数组词多音字组词etterName,如果你没有使用 getter 指定getterName ,系统默认直接使用 propertyName 访问即可。通常来说,只有所指属性需要我们指定 isPropertyName 对应的 Bool 值时,才使用指定 getterName ,一般ios系统直接用 PropertyName 即可。setter=setterName: 则是用来指定设置属性所使用的的 setter 方法,即设置属性值时使用 setterName: 方法,此处 setterName 是一个方法名,因此要以”:”结尾,ios15.4正式版具体示例如下:

// 指定getter访问名为`isHappy`
@property (nonatomic, assign, getter=isHappy) BOOL happy;
// 使用系统默认getter/setter方法
@property (nonatomic, assign) NSInteger age;
// 指定setter方法名为`setNickName:`
@property (nonatomic, copy, setter=setNickName:) NSString *name;

读写权限

  1. readwrite
  2. readonly
  • readwrite:表示自动生成对应的 gettersetter 方法,即可读可写权限, readwrit效率e是编译器的默认选项。
  • readonly:表示只生成 getter线程的几种状态不需要效率符号生成 setter ,即只可读,不可以修改。

内存管理ios越狱

  1. strong // 强引用,引用ios鲁多多app计数+1
  2. assign // assign是指针赋值,不对引用计数操作,对象销毁后不会自动置为nil
  3. copy // copy出一个新对象,引用计数为1
  4. weak // 弱引数组的定义用,不对引用计数操作,对象销毁时自动置为nil
  5. retain // 强引用,对象引用计数+1
  6. unsafe_unreta线程是什么意思ined // 弱引用,不对引效率公平市场经济用计数操作,对象销毁时不会自动置为nil
  • strong

表示强引用关系,即修饰对象的引用计数会+1数组指针,通常用来修饰对象类型,可变集合及可变字符串类型。当对象引用计数为0,即不被任何对象持有,且此对象不再显示在列表中时,效率公平市场经济对象就会从内存中释放。

  • assign

对象不进行 retain 操作,即不改变对象引用计数。通常用来修饰基本数据类型( NSIn数组和链表的区别teger, CGFloat, Bool, NSTimeInterval 等),内存在栈上由系统自动回收。

assign 也可以用来修饰 NSObje数组c语言ct 类型对象,因线程池面试题assiios越狱gn 不会改变修饰数组的定义对象的线程数越多越好吗引用计数,所以当修饰对象的引用计数为0,对象销毁的时候,对象指针不会被自动清空。而此时对象指针指向的地址已被销毁,这时再访问该属性会产生野指针错误:EXC_BAD_ACCESS,因此 assign 通常用来修饰基本数据类型。

  • copy

当调用修饰对象的 setter 方法时,会建立一个引用计数为 1 的新对象,即对象会在内数组存里拷贝一份副本,两个指针指向不同的内存地址。一般用于修饰字符串( NSString )和集合类( NSArray , NSDict代码规范ionary )的不可变变量ios鲁多多appBlock 也是用 copy 修饰。

针对 copy ,这里又牵涉到了深 copy 和浅 copy 的问题,这里做一下简单介绍,后续会有文章专门探讨这个问题:

浅 copy :是对指针的 copy ,指针指向的内容是同一个地址数组,对象的引用计数+1;

深 copy :是对内容线程池面试题的 copy ,会开辟新的内存效率的拼音空间,将内容重新线程是什么意思 copy 一份;

  • 非集合对象的 copy 与 mutableCopy

不可变对象:copyios15 操作为浅 copy,mutableCopy 操作为深 copy。

可变对象:copy 操作为深 copy,mutableCopy 操作也为深 copy。

  • 集合类对象的 copy 与 mutableCopy

不可变对象:copy 操作为浅 copy ,mutabl数组排序eCopy 操作为深copy。

可变对象:copy 操作为深 copy ,可变对象的 mutableCopy 也为深 copy 。

注意:当使用 copy 修饰的属性赋值时,copy 出来的是一份不可变ios系统对象。因此当对象是一个可变对象时,切记不要使用 copy 进行修饰。如果这时使用 copy 修饰,当使用 copy 出来的对象调用可变对象所特有的方法时,会因为找不到对应的方法而 CraiOSsh。

  • weak

表示弱引用关系ios鲁多多app,修饰对象的引用计数不会增加,当修饰对象被销毁的时候,对象指针会自动置为 nil,防止出现野指针。weak 也用来修饰 delegate ,避免循环引用。另外 weak 只能用来修饰对象类型,且是在 AR线程池面试题C 下新引入的修饰词,MRC 下相当于使用 assign

weak的底层实现原理

weak 的底层实现是基于 Runtime 底层维护的 SideTables 的 hash 数组,里面存储的是一个 SideTable 的数据结构:

struct SideTable {
    spinlock_t slock; // 确保原子性操作的锁,虽然名字还叫 spinlock_t ,其实本质已经是 mutex_t,具体可见 objc-os 源码( `using spinlock_t = mutex_tt<LOCKDEBUG>;` )
    RefcountMap refcnts; // 用来存储对象引用计数的 hash 表
    weak_table_t weak_table // 存储对象 weak 引用指针的 hash 表
    //...
};
  • weak 功能实现核心的数据结构 weak_table_t:
/**
 * 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; // 数组中元素的个数
    uintptr_t mask;        // 计数辅助量
    uintptr_t max_hash_displacement; // hash 元素最大偏移值
};
  • weak_entry_t也是一个hash结构:
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        // 动态数组
        struct {
            weak_referrer_t *referrers; // 弱引用该对象的对象指针的hash数组
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        // 定长数组,最大值为4,苹果考虑到一半弱引用的指针个数不会超过这个数,因此为了提升运行效率,一次分配一整块的连续内存空间
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    // 判断当前是动态数组,还是定长数组
    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }
    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

这里重点说一下 weak_en线程是什么意思try_t 定长数组动态数组的切换,首先会将原来定长数组中的内容转移到动态数组中,然后再在动态数组中插入新的元素。

而对于动态数组中元素个数大于或等于总空间的 3/4 时,会对动态数组进行总空间 * 2 的扩容

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }

每次动态数组扩容线程池的七个参数,都会将原先数组中的内容重新插入到新的数组中。

当对象的引用计数线程数越多越好吗为 0 时,底数组词多音字组词层会调用 _objc_rootDealloc线程 方法对对象进行释放,而在 _objc_rootDealloc 方法里面会调用 rootDealloc 方法,如果对数组去重方法象有被 weak 引用,则会进入 object_dispo线程数越多越好吗se , 之后会在 objc_des数组词tructInstance 函数里面调用 obj->clearDeallocating(); 根据对象地址获取所有 weak 指针地址数组,遍历数组找到对应的值,将其值为 nil ,然后将 entryweak 表中移除,最后从引用计数表中删除以废弃对象的地址为键值的记录。

备注: 此处省略了 weak 底层实现的很多细节,具体详细实现,后续会单独发文介效率绍。

  • retain 是在 MRC 下常用的修饰词:

ARC 下效率已不再使用 retain ,而是使用 strong 代替。retainstrong 类似,用来修饰线程的几种状态对象类型,强引用对象,其修饰对象线程池面试题的引用计数会 +1,不会对对象分配新的内存空间。

  • unsafe_unretained 同 weak 类似:

unsafe_unretained 不会对对象的引用计数效率符号 +1,只能用来修饰对象类型,修饰的对象在被销毁时,其指针不会自动清空,指向的仍然是已销毁的对象,这时再调用该指针会产生野指ios15:EXC_BAD_ACCESS 错误。


原子性

atomic 原子性:系统会自动给生成的 getter/setter 方法进行加锁操作;

nonatomic 非原子性:系统不会给自动ios越狱生成的 getter/setter 方法进行加锁操作;

设置属性函数 reallySetProperty(...) 的原子性非原效率公式子性实现如下:

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

获取属性函数 ob线程和进程的区别是什么jc_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);

由此可见,对属性对象的加锁操作仅限于对象的 getter/setter 操作,如果线程安全getter/setter 以外的操作,该加锁并没有意义。因此 a线程池的七个参数tomic 的原子性,仅能保障对象的 getter/setter线程安全,并数组的定义不能保障多线程下对对象的其效率符号他操作安全。如一个线程在 getter/setter 操作,另数组公式一个线程进行 release 操作,可能会导致 crash。此种场景的线程安全,还需要由开发者自己进行处理。


拓展知识

  • Category 中添加属性 @property

Category 中添加 @property,只会生成 setter/getter 方法的声明数组去重方法,并不会有具体的代码实现。这iOS是因为 Category 在运行期对象的内存布局已经确定,此时如果添加实例变量就会破坏对象的内存布局,这将会是灾难性的。因此 Categoryios15 无法添加实例变量。

那如何给 Categ线程池面试题ory 实现类似实例ios15.4正式版变量功能呢?简单列举两种方式,此处暂时不做具体详解,后续会有文章单独介绍:

  1. 使用临时全局变量代替成员变量,并在属性的 setter/getter 函数中进行存取值操作;
  2. 通过 Runtime 添加关联线程池对象实现成员变量,并在属性的 se数组和链表的区别tter/getter 函数中进行存取值操作,其关键调用有两个:
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) // 设置关联对象值调用
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) // 获取关联对象值调用
  • Protocol 中添加属性 @property

protocol 中添加属性 @pr数组c语言operty,其实就是声明线程是什么意思该属性的 setter/getter 方法,在实现该 protocol 时,并没有生成对应的成员变量,此时有动态实现和自动实现两种方式:

  1. 动态实现,需要在实现类中添加 protoios系统col 中声明属性对应的 setter/getter线程池的七个参数法,并声明一个私有成员变量用来进行存取操作ios14.4.1更新了什么备注数组词 实现类如果没有实现对应的 setter/getter 方法,在调用 protocol 属性的 setter/getter 方法时会因为方法找不到而 Crash
  2. 自动实现,可以通过 @synthesize propertyName; 告诉编译器数组的定义自动添加对应的 setter/getter 方法,并生成对应的成员变量;
  • @synthesize 作用

@synthesize 的作用是告诉编译器,自动创建数组属性的 setter/getter 方法,同时生成成员变量,并且可以给属性指定别名。如:

@synthesize name = nickName;

  • @线程池dynamic 作用

@dyna效率mic 作用是告诉编译器,无需自动创建属性的 setter线程同步/getter 方法,这个时候就需ios15.4正式版推送时间要我们手动实现相应的 setter/getter,否则,在使用到相应属性的 setter/getter 方法时,会因找不到方法而 Crash

  • null 相关的一些关键词
  1. nullable : 可以为空
  2. nonnull : 不可以为空
  3. null_unspecified : 未知类型
  4. null_resettable : get不能为空,set可以线程是什么意思为空
  5. __nullable : 可以为 Null数组排序 或 nil
  6. __nonn线程是什么意思ull : 不可以为数组
  • Swift下的 unowned 与 weak 区别:
  1. Swift下 weak 的使用同 Objective-C 下 weak 的使用相同。
  2. unowne线程同步d 无主引用标记对象,即使它的原引用已经被释放,它仍然会保持对已释放对象的引用,效率的英文是什么它不是 Optional ,也不会前端代码规范被置为 nios14.4.1更新了什么il 。当我们试图访问这样的 unowned 引用时,程序会发生错误。而 weak 在引用的对象被释放后,标记为 weak 的对象将会自动置为 nilios鲁多多app

Define a capture in a closure asios鲁多多app a效率高的成语n un线程安全owned reference when the closu效率re and the instance it captures will always refer to each other, and will always be deallocated at the same time.

Conversely, define a capt数组词ure as a weak reference when the captured referenc数组指针e may become nil at some point in the future. Weak references are always of an oiOSptional type, and automatically become nil wh线程池的七个参数en the instance they reference is deallocated. This enables you to check foios15.4正式版推送时间r their existence within the closure’s body.

NOTE:

If the captured reference will never线程 become n线程池面试题il, it should always be captured as an unowned reference, rather than a weak reference.

根据苹果官方文档的建议,如果捕获的引用永远不会变为 nil ,我们应该使用 unowned,否则应线程的几种状态该使用 weak


总结

@property 延展相关的技术点有很多,如:copy 相关的 NSCopying 协议,weak 底层详细的实现原理,如何保障对象的多线程安代码规范七大原则全。还ios下载有很多技术点跟 Runtime效率公平市场经济Runloop 有关,后续文章会陆续介绍。

知识点数组去重方法完整说下来数组词就是一整套系统的协同运转,各个环线程池的七个参数节紧密相扣,最终才成为我们现在看到的样子。本文及ios15.4正式版以后的文章都会尽可能的收缩一下单篇文章线程同步探讨的范围,以期能够让话题更加紧密。

参考资料:

The Objective-C Programming Language


关于技术组

iOS效率 技术组主要用来学习、分享日常开发中使用到的技术,一起保持学习,保持进步。文章仓库在这里:github.com/minhechen/i… 微数组指针线程池面试题公众号:iOS技术ios15.4正式版组,欢迎联系交流,感谢阅读。

iOS 属性 @property 详细探究

发表评论

提供最优质的资源集合

立即查看 了解详情