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



    线程安全相关的关键字

    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)提供了copymutableCopy两个拷贝方法

    • 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

  • 相关阅读:
    Ubuntu Pycharm Anaconda 管理&切换环境
    JWT的原理及实际使用
    day 32 文件上传&二次渲染&.htaccess&变异免杀
    Linux安装RabbitMQ教程(文件下载地址+安装命令+ 端口开放 + 用户创建 +配置文件模板+端口修改)
    二叉树的创建与遍历
    代码研发规范考试
    推荐系统-排序层-2018:ESMM【多任务学习模型】【多任务学习(multi-task learning,简写MTL)】【阿里】
    客服呼叫中心的变革之路:从传统到智能的跨越
    可燃气体报警器检定检验:江门工业安全新保障的实践与思考
    [CISCN2019 华北赛区 Day1 Web2]ikun-1|python反序列化
  • 原文地址:https://blog.csdn.net/XY_Mckevince/article/details/140849133