属性关键字
读写权限
- readonly
- readwrite(默认)
原子性
OC中的属性可以修饰成nonatomic和atomic,即原子和非原子属性。atomic属性设计的出发点是保证多线程下使用属性的安全性,
- atomic(默认)
atomic
修饰的属性可以保证赋值和获取值(对成员属性的直接获取和赋值,并不代表操作和访问),线程安全。
假如一个atomic
修饰的的数组,对数组进行赋值和获取可以保证线程安全,如果对数组进行操作,比如给数组添加对象或移除对象元素,则不在atomic
的负责范围内,没办法保证数组的线程安全。
使用atomic修饰的属性并不会线程安全
源码objc4-750.1
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
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;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot]; //自旋锁
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
从使用PropertyLocks
数组中的一个给写操作上锁,在赋完值之后进行解锁操作。
也就是说,atomic
只是在 setter方法 中加锁,当多个线程同时写操作时,要进行排队。A线程对属性进行写操作,B线程不可以对该属性进行写操作,只有等A线程访问结束,B线程才能操作。问题在于,当A线程再想对属性进行读操作,读取的是B线程写的数据,这就破坏了线程安全,。如果再有C线程在A线程读操作前Release这个属性,那么程序就会Crash.
综上,atomic
操作是原子性的,它只保证了属性的setter和getter时的线程安全,并不能保证属性线程安全,atomic
的使用只是更好的降低了线程出错的可能性。
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;
// 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);
}
- nonatomic
atomic 与 nonatomic 的区别如下:
-
atomic
与nonatomic
的本质区别其实也就是在setter
方法上的操作不同,atomic
保证了getter
和setter
存取方法的线程安全,两者都不能保证整个对象是线程安全的。 -
nonatomic
的速度要比atomic
的快。
鉴于以上两点,大部分使用的是nonatomic
这个属性。
那么问题来了,Apple为什么只给setter和getter方法加锁而不直接使用@synchronized加锁呢?
看看 synchronized
操作的源码, 简单的一个注解,其实做了许多事情,每个@synchronized()
代码块,使用了 objc_sync_enter
、objc_sync_exit
和id2data
三个加解锁序列,而这种作法相比使用一个atomic
来修饰,只给setter
、getter
方法加锁的方式 在性能上要慢很多。读写操作一个属性时,通常需要很快来完成,atomic
的方式要适合这项工作。
在必要时,可以使用@synchronized
,但是在保证多线程操作在发生错误的时候,不会发生dead lock。
引用计数
retain/strong(修饰对象的)
assign/unsafe_unretained(基本数据类型或对象)
assign与weak的区别有哪些?
assign
修饰基本数据类型,如int
,BOOL
等assign
修饰对象类型时,不改变其引用计数。-
会产生悬垂指针。
assign
修饰的对象在被释放之后,其assign
指针仍然指向原对象内存地址,这个时候如果通过assign
指针继续访问对象,可能会由于悬垂指针的原因导致内存泄漏,或程序异常。 weak
不改变被修饰对象的引用计数所指对象在被释放之后会自动置为
nil
weak
之修饰对象
copy
- 可变对象的
copy
和mutableCopy
都是深拷贝。 - 不可变对象的
copy
是浅拷贝,mutableCopy
是深拷贝。 -
copy
方法返回的都是不可变对象;
深拷贝与浅拷贝
浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。
- 会增加对象的引用计数
- 没有发生新的内存分配。
深拷贝让目标对象指针和源对象指针指向两片内容相同的内存空间。
- 不会增加对象的引用计数
- 产生了新的内存空间分配
@property(copy) NSMutableArray *array;
如果像上面那样声明一个成员属性会导致什么问题?
如果赋值过来的是
NSMutableArray
,copy之后是NSArray
。-
如果赋值过来的是
NSArray
,copy之后是NSArray
。所以无论被赋值过来的是
NSMutableArray
,还是NSArray
copy的结果都是不可变对象,由于原来的属性被声明为NSMutablArray
就不可避免的有调用方调用其进行元素的添加与移除,此时由于copy的结果是NSArray
,调用其子类方法的添加与移除会造成程序的崩溃。
OC语言笔试题
MRC下如何重写retain
修饰变量的setter方法?
@property(nonatomic, retain) id obj;
- (void)setObj:(id)obj
{
if(_obj != obj) { //防止提早释放了对象
[_obj release];
_obj = [obj retain];
}
}
请简述分类实现原理?
KVO的实现原理是怎样的?
能否为分类添加成员变量?