目录
- 线程安全相关的关键字
- atomic&nonatomic
- 作用域相关的关键字
- static、extern、const&auto
- 读写权限相关和指定方法名的关键字
- 内存管理相关的关键字(或方法)
- 1. 引用计数的存储
- SideTable
- retain方法源码分析
- release方法源码分析
- dealloc方法源码分析
- retainCount方法源码分析
- 2. retain关键字
- 3. assign关键字
- 4. copy关键字
- 深拷贝和浅拷贝
- copy修饰属性
- NSCopying协议
- 容器对象的拷贝
- 5. strong关键字
- 6. weak关键字
- 7. unsafe_unretained关键字
线程安全相关的关键字
atomic&nonatomic
见此文:【iOS】线程同步&读写安全技术(锁、信号量、同步串行队列)
作用域相关的关键字
static、extern、const&auto
见此文:【iOS】static、extern、const、auto关键字以及联合使用
读写权限相关和指定方法名的关键字
见此文:【Objective-C】浅析OC中的属性关键字
内存管理相关的关键字(或方法)
1. 引用计数的存储
SideTable
【iOS】SideTable中有提到,当isa指针的引用计数过大时,就不是extra_rc
来存储了,而是存在SideTable类中的引用计数器中
下面操作引用计数的方法实现会用到SideTable,来分析一下
调用retain
会让OC对象的引用计数+1
,调用release
会让OC对象的引用计数-1
retain方法源码分析
OC中的retain方法会调用objc_retain(),objc_retain()函数调用retain()函数
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
return rootRetain(false, RRVariant::FastOrMsgSend);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
// 如果是Tagged Pointer则直接返回this(Tagged Pointer不参与引用计数管理,它的内存在栈区,由系统处理)
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false; // 临时变量,标记 SideTable 是否加锁
bool transcribeToSideTable = false; // 临时变量,标记是否需要把引用计数迁移到 SideTable 中
isa_t oldisa; // 记录 objc_object 之前的 isa
isa_t newisa; // 记录 objc_object 修改后的 isa
oldisa = LoadExclusive(&isa().bits); // 似乎是原子性操作,读取 &isa.bits。(&为取地址)
if (variant == RRVariant::FastOrMsgSend) {
// 这些检查仅对objc_retain()有意义
// 它们在这里,以便我们避免重新加载isa
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa().bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa().bits);
return (id)this;
}
}
do {
transcribeToSideTable = false; // 默认不需要迁移引用计数到SideTable
// 赋值给 newisa(第一次进来时 &isa.bits, oldisa.bits, newisa.bits 三者是完全相同的)
newisa = oldisa;
// 如果不是优化的isa,直接操作散列表+1
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa().bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
// 不要检查 newisa.fast_rr; 我们已经调用所有 RR 的重载
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa().bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
uintptr_t carry;
// 执行引用计数加1操作
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// // 判断extra_rc是否满了,carry是标识符
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa().bits);
return rootRetain_overflow(tryRetain);
}
// 如果extra_rc满了,则拿出一半存储到side table散列表中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// 复制 retain count 的另一半到 SideTable 中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
总结流程:
- 第1步:若对象为TaggedPointer小对象,无需进行内存管理,直接返回
- 第2步:若对象的isa没有经过优化,即!newisa.nonpointer成立,由于tryRetain=false,直接进入sidetable_retain方法,此方法本质是直接操作散列表,最后让目标对象的引用计数+1
- 第3步:判断对象是否正在释放,若正在释放,则执行dealloc流程,释放弱引用表和引用计数表
- 第4步:若对象的isa经过了优化,则执行newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry),即isa的位域extra_rc+1,且通过变量carry来判断位域extra_rc是否已满,如果位域extra_rc已满则执行newisa.extra_rc = RC_HALF,即将extra_rc满状态的一半拿出来存到extra_rc位域中,然后将另一半存储到散列表中,执行sidetable_addExtraRC_nolock(RC_HALF)函数
release方法源码分析
release方法底层会调用rootRelease()函数
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa().bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa().bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
// 判断nonpointer是不是共用体类型的指针
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa().bits);
return false;
}
}
retry:
do {
newisa = oldisa;
// 判断是否为nonpointer
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa().bits);
// 不是则直接操作散列表-1
return sidetable_release(sideTableLocked, performDealloc);
}
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa().bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
// 进行引用计数-1操作(extra_rc--)
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
// 如果此时extra_rc的值为0了,则走到underflow
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
// 此时extra_rc中值为0,散列表中也是空的,触发析构函数
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
// 判断散列表中是否存储了一半的引用计数
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
ClearExclusive(&isa().bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa().bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
oldisa = LoadExclusive(&isa().bits);
goto retry;
}
// 从SideTable中移除存储的一半引用计数
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
// 进行-1操作,然后存储到extra_rc中
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
newisa.has_sidetable_rc = !emptySideTable;
bool stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);
if (!stored && oldisa.nonpointer) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
uintptr_t overflow;
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa().bits);
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa().bits);
goto retry;
}
// Decrement successful after borrowing from side table.
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 进行析构,发送dealloc消息
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa().isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
this->performDealloc();
}
return true;
}
总结流程:
- 第1步:若对象为TaggedPointer小对象,不需要做内存管理操作,直接返回;
- 第2步:若对象的isa没有经过优化,即!newisa.nonpointer成立,直接进入sidetable_release方法,此方法本质是直接操作散列表,最后让目标对象的引用计数-1;
- 第3步:判断是引用计数是否为0,如果是0则执行dealloc流程
- 第4步:若对象的isa经过优化,则执行newisa.bits = subc(newisa.bits, RC_ONE, 0, &),即对象的isa位域extra_rc-1;且通过变量carry标识对象的isa的extra_rc是否为0, 如果对象的isa的extra_rc=0,则去访问散列表,判断对象在散列表中是否存在引用计数;
- 第5步:如果sidetable的引用计数为0,对象进行dealloc流程
dealloc方法源码分析
dealloc方法底层调用rootDealloc()函数
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa().nonpointer && // 普通的isa
!isa().weakly_referenced && // 弱指针引用
!isa().has_assoc && // 关联对象
#if ISA_HAS_CXX_DTOR_BIT
!isa().has_cxx_dtor && // C++析构函数
#else
!isa().getClass(false)->hasCxxDtor() &&
#endif
!isa().has_sidetable_rc)) //引用计数散列表
{
assert(!sidetable_present());
free(this); // 直接释放
}
else {
object_dispose((id)this);
}
}
- 首先判断对象是否是TaggedPointer,是的就直接返回
- 采用了优化的isa计数方式,且没被weak引用、无关联对象、无自定义的C++析构函数、没用SideTable存储引用计数,则快速释放
- 如果isa的位域值没有满足上述条件,则进一步调用object_dispose
object_dispose函数实现
// object_dispose
id
object_dispose(id obj)
{
if (!obj) return nil;
// 销毁实例而不释放内存
objc_destructInstance(obj);
// 释放内存
free(obj);
return nil;
}
// objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
// 调用c++析构函数,清除成员变量
if (cxx) object_cxxDestruct(obj);
// 删除关联对象
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
// 将指向当前对象的弱指针置为nil
obj->clearDeallocating();
}
return obj;
}
- 如果有自定义的C++析构函数,则调用C++析构函数
- 如果有关联对象,则移除关联对象并将其自身的Association Manager的map中移除
- 调用clearDeallocating函数清除对象的相关引用
clearDeallocating
// clearDeallocating
inline void
objc_object::clearDeallocating()
{
// 判断是否为nonpointer
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
// 如果不是,则直接释放散列表
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
// 如果是,清空弱引用表 + 散列表
clearDeallocating_slow();
}
assert(!sidetable_present());
}
-
对象没有采用优化isa引用计数,调用
sidetable_clearDeallocating()
清理对象存储在SideTable中的引用计数数据void objc_object::sidetable_clearDeallocating() { SideTable& table = SideTables()[this]; // clear any weak table items // clear extra retain count and deallocating bit // (fixme warn or abort if extra retain count == 0 ?) //清除所有弱表项 //清除额外的保留计数和释放位 //(如果额外保留计数==0,则修复警告或中止) table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) { weak_clear_no_lock(&table.weak_table, (id)this); } table.refcnts.erase(it); } table.unlock(); }
-
对象采用了优化isa引用计数,则判断是否有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合这两种情况中一种的,调用
clearDeallocating_slow()
清空弱引用表 + 散列表// clearDeallocating_slow NEVER_INLINE void objc_object::clearDeallocating_slow() { ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); // 找到对象对应的SideTable SideTable& table = SideTables()[this]; table.lock(); if (isa.weakly_referenced) { // 对象被弱引用 // 清空弱引用表 weak_clear_no_lock(&table.weak_table, (id)this); } if (isa.has_sidetable_rc) { // 对象采用了SideTable做引用计数 // 清空引用计数 table.refcnts.erase(this); } table.unlock(); }
总结流程:
retainCount方法源码分析
底层调用到rootRetainCount()
inline uintptr_t
objc_object::rootRetainCount()
{
// 如果是TaggedPointer就返回
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
// 拿到isa
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) { // 查看是否为优化过的指针
uintptr_t rc = bits.extra_rc; // 拿到isa指针里的extra_rc返回
if (bits.has_sidetable_rc) { // 判断has_sidetable_rc的值是否为1,如果为1就要去SideTable里面取
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
size_t
objc_object::sidetable_getExtraRC_nolock()
{
// 通过一个key取出SideTable里的散列表refcnts
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
uintptr_t
objc_object::sidetable_retainCount()
{
// 获取对象对应的 SideTable
SideTable& table = SideTables()[this]
// 初始化引用计数结果为 1,因为每个对象至少会有一个强引用
size_t refcnt_result = 1;
// 加锁,确保对 refcnts 的访问是线程安全的
table.lock();
// 在RefcountMap中查找当前对象
RefcountMap::iterator it = table.refcnts.find(this);
// 如果找到对象的引用计数
if (it != table.refcnts.end()) {
// 更新引用计数结果,包含 SIDE_TABLE_RC_PINNED 的情况
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
// 解锁
table.unlock();
// 返回引用计数结果
return refcnt_result;
}
2. retain关键字
修饰OC对象,setter实现是将指针原来指向的旧对象释放掉,然后指向新对象,同时将同时将新对象的引用计数+1
- (void)setData:(NSArray *)data {
if (_data != data) {
[_data release];
_data = [data retain];
}
}
3. assign关键字
一般用于修饰基本数据类型(NSInteger、BOOL、int、float等),assign
修饰属性生成的setter方法实现是直接赋值,所以assign修饰对象或id类型时,不会增加其引用计数
- (void)setAge:(int)age {
if (_age != age) {;
_age = age;
}
}
- 修饰基本数据类型时,因为基本数据类型分配在栈上,其内存会有系统自动处理,不会造成野指针
- 修饰对象时,当对象释放后,指针的地址仍指向被销毁对象的原地址,成为野指针(悬垂指针),这时候如果继续通过该指针访问原对象的话,就可能导致程序崩溃)
- MRC下,
id delegate
往往是用assign修饰,若用retain,会产生循环引用
4. copy关键字
OC为Foundation框架中的类(NSString、NSArray、NSDictionary、NSSet和NSData)提供了copy
和mutableCopy
两个拷贝方法
- copy:不可变拷贝,产生不可变副本
- mutableCopy:可变拷贝,产生可变副本
看以下示例:
// NSString* str1 = [NSString stringWithFormat: @"test"]; // Tagged Pointer
NSString* str1 = [NSString stringWithFormat: @"testttttttttttttttttttttttt"];
NSMutableString* str2 = [str1 copy]; // 返回NSString
NSMutableString* str3 = [str1 mutableCopy]; // 返回NSMutableString
NSLog(@"%@ %@ %@", [str1 class], [str2 class], [str3 class]);
[str3 appendString: @"123"];
// NSTaggedPointerString没有appendString这个方法。Thread 1: "-[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xa7173f739e6407b8"
// 尝试使用 appendString 改变不可变对象:。Thread 1: "Attempt to mutate immutable object with appendString:"
[str2 appendString: @"123"];
就算给str2声明的是NSMutableString指针,但返回的实际对象却是不可变的NSString,如果非要修改字符串,就会出现上述注释中的Crash报错
深拷贝和浅拷贝
- 深拷贝:内容拷贝,另申请内存,产生新的对象
- 浅拷贝:指针拷贝,返回原对象,没有新的对象
示例:
NSString* str1 = [NSString stringWithFormat: @"testttttttttttttttttttttttt"];
NSString* str2 = [str1 copy]; // 内容相同又无法改变,所以指向最好一样,返回本身
// 对于不可变对象的不可变拷贝,copy等效于retain,只让引用计数+1且返回自己本身
NSMutableString* str3 = [str1 mutableCopy];
NSLog(@"%p %p %p", str1, str2, str3);
NSMutableString* str = [NSMutableString stringWithFormat: @"testttttttttttttttttttttttt"];
NSString* stra = [str copy];
NSMutableString* strb = [str mutableCopy];
NSLog(@"%p %p %p", str, stra, strb);
运行结果:
通过打印发现:
NSString变量str1,与str2的内存地址是一样的,没有产生新对象,是浅拷贝;而与str3的内存地址不一样,产生了新对象,是深拷贝
NSMutableString变量str,与str2、str3的内存地址都不一样,都产生了新对象,是深拷贝
由于对不可变对象的不可变拷贝是浅拷贝,并不是真正地拷贝内存,所以该情况下copy方法等效于retain方法,只会让引用计数+1
从方法实现的角度来看:
不可变对象的不可变拷贝返回的副本与原对象相比,内容相同且无法改变,为节省内存空间,干脆直接返回本身(原对象地址),而可变拷贝副本与原对象类型不同,就要返回新对象
可变对象无论是不可变拷贝,还是可变拷贝,产生的副本与原对象毫无关系,copy副本类型不同,mutableCopy副本修改内容不同,肯定要返回新对象
经验证,NSString、NSArray以及NSDictionary都符合以上分析:
copy修饰属性
现有一个Person类,其中的NSString字符串属性使用strong修饰
@interface Person : NSObject
@property (nonatomic, strong)NSString* text;
@end
看以下代码:
Person* person = [[Person alloc] init];
NSMutableString* str = [NSMutableString stringWithString: @"dddddddddddd"];
person.text = str;
[str appendString: @"33"];
NSLog(@"%@ --- %p, %@ --- %p", str1, str1, person.text, person.text);
// 输出:dddddddddddd33 --- 0x6000029dc480, dddddddddddd33 --- 0x6000029dc480
传入一个可变对象NSMutableString,我们发现虽改变了str的值,但person.text也会受到影响,看地址不难发现原因是它们指向同一块内存空间
如果person.text使用copy修饰结果会怎样呢:
@property (nonatomic, copy)NSString* text;
// 输出:dddddddddddd33 --- 0x600000ddc480, dddddddddddd --- 0x6000003df060
改变str的值,person.text不受到影响,看地址这两个变量指向的是两个毫无关系的地址
原因分析:
使用copy修饰属性,生成的setter方法等效于以下操作:
- (void)setData:(NSString *)str {
if (_str != str) {
[_str release];
// 引用计数+1,不像retain返回本身,而是返回不可变副本
// 如果传进来的是不可变对象,其不可变拷贝是浅拷贝,返回的其实就是本身,跟strong效果一样
_str = [str copy];
}
}
返回传入对象的不可变拷贝,上个示例中传入可变字符串,那其不可变拷贝是深拷贝,生成了一个新的对象,与原对象互不影响,各做各的事情
总结
一般使用copy
修饰NSString,就是为了防止第一个示例中,原字符串遭外部修改
一般用strong
修饰NSMutableString、NSMutableArray、NSMutableDictionary,如果用copy
修饰,生成的setter
会通过不可变拷贝变成不可变对象,而后改变该对象就会报错
除NSString,其他不可变对象无论是用strong还是copy修饰,都是一样的,都是浅拷贝,原内存地址不变,生成了新的指针
NSCopying协议
对于自定义类我们来进行拷贝:
@interface Fellow : NSObject
@property (nonatomic, assign)int weight;
@property (nonatomic, assign)int age;
@end
Fellow* fellow1 = [[Fellow alloc] init];
fellow1.age = 21;
fellow1.weight = 125;
Fellow* fellow2 = [fellow1 copy];
fellow2.age = 20;
NSLog(@"%@ --- %p, %@ --- %p", fellow1, fellow1, fellow2, fellow2);
运行到调用copy方法时就报错了,提示调用者没有实现copyWithZone:
方法:
该方法在NSCopying协议中实现,copy方法底层调用的是该方法,所以自定义类要进行拷贝,需类遵守NSCopying协议
@interface Fellow : NSObject <NSCopying>
...
@end
@implementation Fellow
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Fellow* fellow = [[Fellow allocWithZone: zone] init];
fellow.age = self.age;
fellow.weight = self.weight;
return fellow;
}
- (NSString *)description {
return [NSString stringWithFormat:@"age = %d, weight = %d", self.age, self.weight];
}
// 输出:age = 21, weight = 125 --- 0x6000012e40b0, age = 20, weight = 125 --- 0x6000012e40c0
@end
容器对象的拷贝
使用copy、mutableCopy对集合对象进行的深浅拷贝是针对集合对象本身的,对集合中的对象执行的默认都是浅拷贝,并不是真正的拷贝
拿NSArray举例:
-
copy:仅仅进行了指针拷贝
-
mutableCopy:仅完成了对NSArray对象的深拷贝,而未对其容器内对象进行处理使用(NSArray对象的内存地址不同,但是内部元素的内存地址不变,对内部元素来说属于浅拷贝)
-
双层深拷贝:完成了NSArray对象和NSArray容器内对象的深拷贝
-
完全深拷贝:解决NSArray嵌套NSArray这种情形,因为内层数组也要实现双层深拷贝,需使用解归档
详细方法见:【Objective-C】对深浅拷贝的理解
5. strong关键字
用于修饰一些OC对象类型的数据如:(NSNumber,NSString,NSArray、NSDate、NSDictionary、模型类等),它被一个强指针引用着,是一个强引用。在ARC的环境下等同于retain,这一点区别于weak。它是一我们通常所说的指针拷贝(浅拷贝),内存地址保持不变,只是生成了一个新的指针,新指针和引用对象的指针指向同一个内存地址,没有生成新的对象,只是多了一个指向该对象的指针
__strong
__strong实际上是一个默认的方法,表示引用为强引用
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
ARC下的对象,正常情况下都是__strong修饰的
在ARC模式下,只要一个变量有__strong标识了,就标识拥有了赋值的对象,不管赋值的对象是怎么来的
也可理解为只要见到__strong
标识,编译器就会给那些不是你持有的对象自动加上retain
,并且在变量超出作用域后自动调用了一次release
,这就是强引用的原理
6. weak关键字
用于修饰OC对象类型的数据,修饰的对象在释放后,指针地址会自动被置为nil
,不会产生悬垂指针,这是一种弱引用,不会增加引用计数
_weak
使用__weak
来对变量进行弱引用,被__weak修饰的变量一旦被释放,会自动置为nil
7. unsafe_unretained关键字
unsafe_unretained
关键字修饰属性同__unsafe_unretained
,其作用也是将变量变成弱指针,但是不同于__weak的原因是修饰的变量释放后并不会置为nil
weak 对性能会有一定的消耗,当一个对象 dealloc 时,需要遍历对象的 weak 表,把表里的所有 weak 指针变量值置为 nil,指向对象的 weak 指针越多,性能消耗就越多。所以 unsafe_unretained 比 weak 快。当明确知道对象的生命周期时,选择 unsafe_unretained 会有一些性能提升
比如 A 持有 B 对象,当 A 销毁时 B 也销毁。当 B 存在,A 就一定会存在。而 B 又要调用 A 的接口时,B 就可以存储 A 的 unsafe_unretained 指针。虽然这种性能上的提升是很微小的。但当你很清楚这种情况下,unsafe_unretained 也是安全的,自然可以快一点就是一点。而当情况不确定的时候,应该优先选用 weak