您的位置:首页 > 文旅 > 美景 > 【iOS】OC关键字总结及底层原理(上)

【iOS】OC关键字总结及底层原理(上)

2024/12/23 16:31:26 来源:https://blog.csdn.net/XY_Mckevince/article/details/140849133  浏览:    关键词:【iOS】OC关键字总结及底层原理(上)

目录

  • 线程安全相关的关键字
    • 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 之前的 isaisa_t newisa; // 记录 objc_object 修改后的 isaoldisa = LoadExclusive(&isa().bits); // 似乎是原子性操作,读取 &isa.bits。(&为取地址)if (variant == RRVariant::FastOrMsgSend) {// 这些检查仅对objc_retain()有意义// 它们在这里,以便我们避免重新加载isaif (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 loopif (oldisa.getDecodedClass(false)->isMetaClass()) {ClearExclusive(&isa().bits);return (id)this;}}do {transcribeToSideTable = false; // 默认不需要迁移引用计数到SideTable// 赋值给 newisa(第一次进来时 &isa.bits, oldisa.bits, newisa.bits 三者是完全相同的)newisa = oldisa;// 如果不是优化的isa,直接操作散列表+1if (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++ overflowedif (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 loopif (oldisa.getDecodedClass(false)->isMetaClass()) {ClearExclusive(&isa().bits);return false;}}retry:do {newisa = oldisa;// 判断是否为nonpointerif (slowpath(!newisa.nonpointer)) {ClearExclusive(&isa().bits);// 不是则直接操作散列表-1return 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 overridesuintptr_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了,则走到underflowgoto 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 decrementnewisa = 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 thereif (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 toonewisa.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);// 将指向当前对象的弱指针置为nilobj->clearDeallocating();}return obj;
}
  • 如果有自定义的C++析构函数,则调用C++析构函数
  • 如果有关联对象,则移除关联对象并将其自身的Association Manager的map中移除
  • 调用clearDeallocating函数清除对象的相关引用

clearDeallocating

// clearDeallocating
inline void 
objc_object::clearDeallocating()
{// 判断是否为nonpointerif (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));// 找到对象对应的SideTableSideTable& 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();// 拿到isaisa_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里的散列表refcntsASSERT(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()
{// 获取对象对应的 SideTableSideTable& 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)提供了copymutableCopy两个拷贝方法

  • copy:不可变拷贝,产生不可变副本
  • mutableCopy:可变拷贝,产生可变副本

看以下示例:

// NSString* str1 = [NSString stringWithFormat: @"test"]; // Tagged Pointer
NSString* str1 = [NSString stringWithFormat: @"testttttttttttttttttttttttt"];
NSMutableString* str2 = [str1 copy];  // 返回NSString
NSMutableString* str3 = [str1 mutableCopy];  // 返回NSMutableStringNSLog(@"%@ %@ %@", [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;
@endFellow* 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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com