【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395625.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式初学-C语言-十九

指针的引入 为函数修改实参提供支持为动态内存管理提供支持为动态数据及结构提供支持为内存访问提供另一种途径 指针的概述 内存地址&#xff1a; 系统为了内存管理的方便将内存划分为一个个内存单元&#xff08;一个内存单元占一个字节&#xff09;&#xff0c;并为每一个…

用Vue和Axios将数据库数据显示在前端页面

在本次实例中Vue只用在了前端部分&#xff0c;Axios用于向后端请求数据&#xff0c;我们这里要用到Ajax技术来访问后端数据。 HTML&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name&quo…

全新博客X主题/简约WordPress主题模板/主题巴巴/免授权版源码+自适应设计

源码简介&#xff1a; 博客X这款超酷的Wordpress主题&#xff0c;是主题巴巴团队打造的设计杰作。想象一下&#xff0c;你的博客首页能展示那些炫酷的幻灯片置顶文章、还有各种精心策划的专题列表&#xff0c;这些内容模块的设计简直吸睛了&#xff0c;能让来访的用户眼前一亮…

数据结构和算法|递归算法那些事(递归算法的时间复杂度、尾递归优化、斐波那契数列)

对于文章的第一部分&#xff0c;递归算法的时间复杂度&#xff0c;来自于代码随想录文章:通过一道面试题目&#xff0c;讲一讲递归算法的时间复杂度&#xff01; 对于第二节尾递归优化来自于B站&#xff1a;尾递归优化&#xff1a;你的递归调用是如何被优化的&#xff1f; 文章…

XML(可扩展标记语言)

QDomDocument doc;QDomElement ss doc.createElement("root");//创建标签 //ss标签添加到文档对象doc.appendChild(ss);//doc.save()auto hero doc.createElement("hero");ss.appendChild(hero);hero.setAttribute("id",10086);//为hero添加属…

MySQL——数据表的基本操作(一)创建数据表

数据库创建成功后,就需要创建数据表。所谓创建数据表指的是在已存在的数据库中建立新表。需要注意的是&#xff0c;在操作数据表之前&#xff0c;应该使用 “ USE 数据库名 ” 指定操作是在哪个数据库中进行&#xff0c;否则会抛出 “ No database selected ” 错误。创建数据表…

Tomcat 使用和配置文件(详解)

一.tomcat 介绍 1. tomcat 概述 自从JSP发布之后&#xff0c;推出了各式各样的JSP引擎。Apache Group在完成GNUJSP1.0的开发以后&#xff0c;开始考虑在SUN的JSWDK基础上开发一个可以直接提供Web服务的JSP服务器&#xff0c;当然同时也支持 Servlet&#xff0c;这样Tomcat就诞…

[自学记录09*]关于模糊效果降采样优化性能的小实验

一、降采样在模糊中的优化 这两天接手了几个高度定制化的模糊&#xff0c;包括不限于放射和旋转状的径向模糊&#xff0c;移轴模糊&#xff0c;景深的散景模糊等等&#xff0c;这些效果在游戏中非常常见。 其实模糊的原理都差不多&#xff0c;无非就是对UV偏移后重新采样再求…

《Python爬虫逆向实战》绕过debugger的方法汇总

禁用断点 打开控制台&#xff0c;点击右边的禁用断点按钮。 点击之后再刷新下&#xff0c;就会发现debugger失效了。 注&#xff1a;这种方法有个 弊端&#xff0c;就是我们在代码中下的断点也都将失效。 Add script to ignore list 在代码文件中任意位置右键&#xff0c;然…

51单片机—串口

一、 串口基本认知 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方 式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简 单&a…

【C++ 面试 - 基础题】每日 3 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

【网络安全】玲珑安全第四期

鉴于玲珑安全漏洞挖掘前三期课程取得的优异成绩和获得的强烈反响,我们决定启动玲珑安全第四期漏洞挖掘培训计划。 文章目录 往期学员收获基础学员报喜(部分)课程反馈第四期课程课程内容免费课程往期学员收获 第一期课程总结及学员收获:->点我查看第一期学员收获<- …

性能测试工具LoadRunner

前言&#x1f440;~ 上一章我们介绍了性能测试的一些基本概念&#xff0c;重要的是性能测试的各项指标&#xff0c;今天我们使用性能测试工具LoadRunner简单的完成一次性能测试 性能测试Load Runner LoadRunner是什么&#xff1f; LoadRunner安装 LoadRunner脚本录制 1.录…

算法板子:质数——判定质数、分解质因数、筛质数

目录 一、判定质数 1. 代码 二、分解质因数 1. 质因数的概念 2. 代码 三、筛质数——获取1~n中所有质数的个数 1. 合数的概念 2. 代码 一、判定质数 1. 代码 #include <iostream> using namespace std;bool is_prime(int x) {// 1不是质数, 需要特判if (x 1) …

QT键盘和鼠标事件

这些事件都在QWidget 中的保护成员方法中 都是虚函数在头文件中声明了 需要类外重现实现 如果头文件中声明 类外无实现就会报错 void Widget::keyPressEvent(QKeyEvent *event) {switch (event->key()) {//获取按键case Qt::Key_W://按键wqDebug()<<"按下w"…

开源免费前端地图开发组件xdh-map

xdh-map是一个基于Openlayers的地图应用Vue组件&#xff0c;具有多方面的功能和特点。以下是对xdh-map的详细介绍&#xff1a; 一、功能与特性 内置多种地图瓦片&#xff1a;xdh-map内置了百度、高德、天地图等地图瓦片&#xff0c;使得开发者可以方便地在应用中集成多种地图…

【Material-UI】Checkbox 组件中的 Label Placement 设置详解

文章目录 一、Checkbox 组件简介1. 组件概述2. labelPlacement 属性 二、labelPlacement 属性的使用方法三、各标签位置的效果与应用场景1. Top&#xff08;顶部&#xff09;2. Start&#xff08;左侧&#xff09;3. Bottom&#xff08;底部&#xff09;4. End&#xff08;右侧…

大模型算力基础设施技术趋势、关键挑战与发展路径

文章目录 前言一、大模型技术发展趋势1.1 大语言模型1.2 多模态模型1.3 长序列模型1.4 混合专家模型二、大模型算力基础设施发展问题与挑战2.1 可用算力规模亟需算力利用效率提升2.2 集群性能提升依赖跨尺度、多层次互联三、大模型算力基础设施高质量发展路径总结前言 从大模型…

使用 `grep` 命令的常用方式

使用 grep 命令的常用方式 grep 是一个强大的命令行工具&#xff0c;用于在文件中搜索文本。无论是程序员、系统管理员还是普通用户&#xff0c;都可以通过 grep 快速定位需要的信息。本文将介绍 grep 命令的一些常用方式&#xff0c;并给出相应示例的执行结果。 示例文本 在…

C语言求平方和倒数

文章目录 1. 代码实现float类型数据double类型数据使用 double 类型的调整 2. 魔数与位级别操作浮点数表示位级别魔数操作 3. 牛顿迭代4. 复杂代码具体解释具体解释&#xff1a;目的&#xff1a;举例&#xff1a; 5.感谢 平方和倒数 广泛用于计算机图形学中&#xff0c;尤其是在…