ios中通过@property可以自动生成setter和getter方法,使用及其方便,这里就介绍属性操作那点事,以及多线程操作过程存在的隐患过程,了解其可以提高代码质量
@property的getter和setter方法介绍
查看objc的getter和setter需要用到objc源码来查看,这里面会贴出代码介绍
getter
getter方法获取属性内容源码如下所示,其通过
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
//非原子性属性操作,直接通过对象+偏移量获取到对应的属性内容
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
//原子性操作,需要用到spinlock_t自旋锁,与setter通用一个锁PropertyLocks[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);
}
setter
setter要比getter方法要复杂不少,其中的atomic、nonatomic、retain、copy等都在这里处理,如下所示
可以发现,都是调用了reallySetProperty的方法,唯一区别就是原子性与是否copy,且方法调用非线程安全
//默认的属性的setProperty方法,默认为atomic,且根据情况能copy就copy
oid objc_setProperty(id self, SEL _cmd, ptrdiff_t offset,
id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
//atomic的retain操作
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
//nonatomic的retain操作
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
//atomic的copy操作
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}
//nonatomic的copy操作
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
reallySetProperty方法介绍了setter方法的主要逻辑,且其并非线程安全,如下所示
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
1.
//初始化旧值
id oldValue;
id *slot = (id*) ((char*)self + offset);
2.
//根据copy属性copy内容
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
//新值和旧值是一个直接结束
if (*slot == newValue) return;
//对新值进行retain操作
newValue = objc_retain(newValue);
}
3.
//非原子性操作nonatomic直接取出旧值,附上新值
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
//原子性操作,与setter公用一个锁PropertyLocks[slot],以保证读写操作的原子性
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
4.
//对旧值进行release
objc_release(oldValue);
}
void
objc_release(id obj)
{
// 如果是taggedPointer直接结束
if (obj->isTaggedPointerOrNil()) return;
//否则直接release
return obj->release();
}
多线程操作属性问题
上面的内容看着没有什么问题,可是由于不是线程安全,下面加入多线程 -- 案例代码
为了方便描述多线程操作过程中的问题,上面将reallySetProperty方法执行操作大致分为了4步
下面介绍问题出现的步骤:
多线程意味着同时会有多个线程执行同一个函数,操作同一个对象
多线程操作过程中,可能会同时执行1、2、3步骤,即同时指向了同一个老对象oldValue,赋值新对象newValue给slot,因此此过程一定几率会出现新值赋值 *slot = newValue; 执行了多次,新值不知道赋予是哪一个后执行,因此不知道是哪一个
至此,最多出现数据赋值更新问题,然而进行到objc_release方法释放时,旧值oldValue指向的地址仍然是同一个地址老对象oldValue,此时多个线程同时执行完毕到步骤4,调用objc_release(oldValue)对同一个oldValue,多次释放,因此这个oldValue会被多次释放,从而导致野指针访问,出现崩溃
最后
至此,介绍了属性的读写操作、原子性操作以及多线程操作中出现的问题,理解了就又是充满希望的一天