【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()函数

objectivec 复制代码
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()函数

objectivec 复制代码
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()函数

objectivec 复制代码
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函数实现

objectivec 复制代码
// 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

objectivec 复制代码
// 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中的引用计数数据

    objectivec 复制代码
    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()清空弱引用表 + 散列表

    objectivec 复制代码
    // 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()

objectivec 复制代码
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

objectivec 复制代码
- (void)setData:(NSArray *)data {
    if (_data != data) {
        [_data release];
        _data = [data retain];
    }
}

3. assign关键字

一般用于修饰基本数据类型(NSInteger、BOOL、int、float等),assign修饰属性生成的setter方法实现是直接赋值,所以assign修饰对象或id类型时,不会增加其引用计数

objectivec 复制代码
- (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:可变拷贝,产生可变副本

看以下示例:

objectivec 复制代码
// 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报错

深拷贝和浅拷贝

  • 深拷贝:内容拷贝,另申请内存,产生新的对象
  • 浅拷贝:指针拷贝,返回原对象,没有新的对象

示例:

objectivec 复制代码
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修饰

objectivec 复制代码
@interface Person : NSObject
@property (nonatomic, strong)NSString* text;
@end

看以下代码:

objectivec 复制代码
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修饰结果会怎样呢:

objectivec 复制代码
@property (nonatomic, copy)NSString* text;
// 输出:dddddddddddd33 --- 0x600000ddc480, dddddddddddd --- 0x6000003df060

改变str的值,person.text不受到影响,看地址这两个变量指向的是两个毫无关系的地址

原因分析:

使用copy修饰属性,生成的setter方法等效于以下操作:

objectivec 复制代码
- (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协议

对于自定义类我们来进行拷贝:

objectivec 复制代码
@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协议

objectivec 复制代码
@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实际上是一个默认的方法,表示引用为强引用

objectivec 复制代码
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

相关推荐
蒙娜丽宁11 小时前
掌握Go语言中的映射、常量与指针
开发语言·ios·golang·go
老豆812 小时前
[ios]准备好app后使用xcode发布ios操作
ios·mac·xcode
ss_dda13 小时前
视频编辑软件Adobe Premiere Pro(Pr)2024WIN/MAC下载及安装教程
macos·adobe·photoshop
归辞...15 小时前
「iOS」折叠cell
ios
Flutter鸿蒙梁典典学院15 小时前
Flutter自动打包ios ipa并且上传
flutter·ios·cocoa
AiFlutter15 小时前
Flutter中添加崩溃分析
macos·objective-c·cocoa
资讯全球17 小时前
让Mac更Mac|明基MA系列显示器上市
macos·计算机外设
柯基的小屁墩18 小时前
mac|安装nginx
nginx·macos
ss_dda19 小时前
专业文字处理与协作软件Adobe InCopy(IC)2024WIN/MAC下载及安装教程
macos·adobe
Xminyang19 小时前
Mac设置文件夹的显示方式:独立窗口显示或者标签页显示
macos·文件夹显示方式