「OC」源码学习——属性关键字

「OC」源码学习——属性关键字

属性的声明

@property (nonatomic, strong) NSString *name;

其实属性就是成员变量和自动编写的存取方法

Property 的组成

属性的作用

读写类的关键字

  • readonly,只读只生成相应的 getter 方法,以及带下划线的实例变量;
@property ( readonly ) int age;
  • readwrite,生成 setter 、getter 方法,以及带下划线的实例变量;
@property ( readwrite ) int age; 

原子性操作类关键字

1. atomic (默认)
  • 作用:保证属性访问的原子性

  • 实现原理:

    - (id)object {
        @synchronized(self) {
            return _object;
        }
    }
    
  • 特点:

    • 线程安全但性能较低
    • 不能完全避免线程问题
2. nonatomic
  • 作用:非原子访问,提高性能

  • 特点:

    • 访问速度比 atomic 快 10-20 倍
    • 需要开发者自行处理线程安全
  • 使用场景:

    @property (nonatomic) NSString *name; // 推荐在单线程环境使用
    

内存管理关键字

strong (默认)
  • 作用:创建强引用,增加对象的引用计数

  • 适用类型:对象类型

  • 实现原理:

    - (void)setObject:(id)newObject {
        [newObject retain];    // 保留新对象
        [_object release];     // 释放旧对象
        _object = newObject;   // 赋值
    }
    
  • 使用场景:

    @property (strong) NSString *name; // 推荐用于对象属性
    
weak
  • 作用:创建弱引用,不增加引用计数

  • 适用类型:对象类型

  • 特点:

    • 对象释放时自动置 nil
    • 避免循环引用
  • 实现原理:

    // 运行时自动注册到 weak_table
    objc_storeWeak(&_weakRef, object);
    
  • 使用场景:

    @property (weak) id delegate; // 代理模式
    
copy
  • 作用:创建对象的副本

  • 适用类型:实现了 NSCopying 协议的对象

  • 实现原理:

    - (void)setName:(NSString *)name {
        [_name release];
        _name = [name copy]; // 调用 copy 方法
    }
    
  • 使用场景:

    @property (copy) NSString *uniqueID; // 保护不可变对象
    
assign
  • 作用:直接赋值,不进行内存管理

  • 适用类型:基本数据类型、C 结构体

  • 风险:对象类型使用可能造成野指针

  • 使用场景:

    @property (assign) NSInteger count; // 基本数据类型
    
unsafe_unretained
  • 作用:类似 assign和weak的结合,但用于对象类型,不添加引用计数,比weak性能高些

  • 风险:对象释放后不置 nil

  • 使用场景:

    @property (unsafe_unretained) NSObject *legacyRef; // 兼容旧代码
    

可空性关键字

nullable
  • 作用:表示属性可以为 nil

  • 使用场景:

    @property (nullable) NSString *optionalName;
    
nonnull
  • 作用:表示属性不应为 nil

  • 使用场景:

    @property (nonnull) NSString *requiredID;
    

@property在runtime中的实现

struct property_t {
    const char *name;            // 属性名称
    const char *attributes;      // 属性特性字符串
};

特性字符串格式:"T[类型],[特性1],[特性2],...,V[变量名]"

示例分析:

// 声明
@property (nonatomic, copy) NSString *name;

// 对应的 attributes 字符串:
"T@\"NSString\",C,N,V_name"
代码含义说明
T…类型后面跟着类型编码
Ccopy使用 copy 语义
&retain/strong使用引用计数保留
Nnonatomic非原子性
Rreadonly只读
Wweakweak 引用

设置@property的函数——reallySetProperty

static inline void reallySetProperty(
    id self,           // 目标对象(属性所属的实例)
    SEL _cmd,          // 设置方法的选择器(通常为 setXxx:)
    id newValue,        // 要设置的新值
    ptrdiff_t offset,   // 实例变量在对象内存中的偏移量
    bool atomic,        // 是否原子操作
    bool copy,          // 是否使用 copy 语义
    bool mutableCopy    // 是否使用 mutableCopy 语义
)
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // 1. 处理特殊情况:offset为0表示要设置的是isa指针(即改变对象的类)
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue; // 用于保存旧值,以便后续释放
    // 2. 计算实例变量在对象内存中的位置(slot指针)
    id *slot = (id*) ((char*)self + offset);

    // 3. 根据属性修饰符处理新值(copy/mutableCopy/retain)
    if (copy) {
        // 执行copy操作:调用copyWithZone:方法创建不可变副本
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        // 执行mutableCopy操作:调用mutableCopyWithZone:方法创建可变副本
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        // 如果不是copy/mutableCopy,则进行retain操作(strong修饰)
        // 先检查新旧值是否相同,避免不必要的操作
        if (*slot == newValue) 
            return; // 相同则直接返回
        // 对新值进行retain(增加引用计数)
        newValue = objc_retain(newValue);
    }

    // 4. 根据原子性要求(atomic/nonatomic)执行赋值操作
    if (!atomic) {
        // 非原子操作:直接赋值
        oldValue = *slot; // 保存旧值
        *slot = newValue; // 将新值赋给实例变量
    } else {
        // 原子操作:需要加锁保证线程安全
        // 4.1 获取与当前slot关联的自旋锁
        spinlock_t& slotlock = PropertyLocks[slot];
        // 4.2 加锁
        slotlock.lock();
        // 4.3 在锁保护下赋值
        oldValue = *slot;
        *slot = newValue;
        // 4.4 解锁
        slotlock.unlock();
    }

    // 5. 释放旧值(在retain/copy/mutableCopy之后,旧值不再被引用)
    objc_release(oldValue);
}

Protocol 中的属性

  • 在 Protocol 中使用 @property 只会自动生成 setter 和 getter 方法声明,不会自动生成其实现。
  • 我们在 Protocol 中使用属性的目的,是希望遵守我协议的对象能实现该属性。

Category 中的属性

  • 在 Category 使用 @property 也是只会自动生成 setter 和 getter 方法的声明,不会自动生成其实现。
  • 如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObjectobjc_getAssociatedObject。即关联对象

Atomic对线程安全的作用

看到reallySetProperty之中,对于Atomic之中的处理

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

其实我们可以看到,这个原子锁就是在调用setter/getter方法时,使用自旋锁进行加锁,即使用atomic修饰的作用在于:单个读/写操作的原子性,保证属性值的读取或写入操作是完整的

但是仍然存在一些问题:

1. 复合操作问题

// 线程1
if (self.atomicCounter > 0) {
    self.atomicCounter -= 1; // 非原子操作
}

// 线程2
self.atomicCounter = 100;

问题分析

  • 虽然每个单独的读写是原子的
  • 检查值->修改值 的组合操作不是原子的
  • 线程2可能在检查后修改值

2. 多属性一致性问题

// 账户转账示例
@interface Account : NSObject
@property (atomic) double balanceA;
@property (atomic) double balanceB;
@end

// 线程1:转账操作
account.balanceA -= 100;
account.balanceB += 100;

// 线程2:读取总额
double total = account.balanceA + account.balanceB;

问题分析

  • 每个属性单独是原子的
  • 但两个属性的组合操作不是原子的
  • 可能读到中间状态:balanceA已扣款但balanceB未加款

copy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值