iOS—从源码分析retain、release、retainCount实现

news/2024/7/20 21:58:14 标签: ios, objective-c

文章目录

    • isa简单了解
        • isa的bits成员变量
        • isa的cls成员变量
        • 总结
    • sideTables、sideTable 简单了解
    • retainCount实现
    • retain 实现
    • release实现
    • retain、release过程大总结:
    • objc_autoreleaseReturnValue实现
    • objc_retainAutoreleasedReturnValue实现

内容会陆续补充

isa简单了解

每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。

  • 这里说一下struct和union的区别
  1. 两者都可以包含多个不同类型的数据,如int、double、Class等。
  2. 在struct中各成员有各自的内存空间,一个struct变量的内存总长度大于等于各成员内存长度之和;而在union中,各成员共享一段内存空间,一个union变量的内存总长度等于各成员中内存最长的那个成员的内存长度。
  3. 对struct中的成员进行赋值,不会影响其他成员的值;对union中的成员赋值时,每次只能给一个成员赋值,同时其它成员的值也就不存在了。

isa的数据结构其实是isa_t,是一个共用体(union修饰),意味着共用内存。
结构如下
在这里插入图片描述

isa的bits成员变量

isa的bits成员变量类型是uintptr_t,它实质上是个unsigned long,在64位架构下bits长度为64位,也就是8字节其中每一位的存储使用了位域,即ISA_BITFIELD。
在这里插入图片描述

  • 位域或位段
    一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
    优点:
    节省储存空间;
    可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。
    缺点:
    其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位域在本质上是不可移植的。

宏ISA_DITDIELD
关于这个宏定义在不同架构下的表示部分如下,这里只截取了部分,大概看一下就可,下面会详细介绍
在这里插入图片描述
在64位下,isa其实还是占8个字节内存,共64位个二进制位

  • 由于共用体特性, cls , bits 以及 struct 都是 8 字节内存 , 也就是说他们在内存中是完全重叠的。
  • 实际上在 runtime 中,任何对 struct 的操作和获取某些值,如 extra_rc,实际上都是通过对 bits 做位运算实现的。
  • bits 和 struct 的关系可以看做 : bits 向外提供了操作 struct 的接口,而 struct 本身则说明了 bits 中各个二进制位的定义。

以获取有无关联对象来举例 :

可以直接使用 isa.has_assoc , 也就是点语法直接访问 bits 中第二个二进制位中的数据 . ( arm 64 架构中 )
关于ISA_BITFIELD中每一个字段所存储的内容,我们以arm64为例

#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19

在这里插入图片描述
对于extra_rc存储的是引用计数-1的理解,它存储的是对象自身以外的引用计数,但随着源码的更新,创建对象时对isa的初始化中,对extra_rc初始化为1,所以extra_rc存储的内容直接就是对象的引用计数,这里我们不必纠结到底减不减1,只需要知道它存储的是对象的引用计数。

对于在不同的架构下,存储的数据没有变,只是占据位不同

isa的cls成员变量

在这里插入图片描述
在这里插入图片描述
由源码可知,cls是Class类型,而Class其实是指向objc_class结构体的指针变量,即cls就是指向objc_class结构体的指针变量。

总结

对于上面对isa结构的分析,我们可以知道isa其实分为两种
cls或bits
也就是

  • 纯指针,指向内存地址
  • NON_POINTER_ISA,除了内存地址,还存有一些其他信息

参考,所以isa的结构大致内容如下

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
         uintptr_t has_assoc         : 1;//->是否包含关联对象
         uintptr_t has_cxx_dtor      : 1;//->是否设置了析构函数,如果没有,释放对象更快
         uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
         uintptr_t magic             : 6;//->固定值,用于判断是否完成初始化
         uintptr_t weakly_referenced : 1;//->对象是否被弱引用
         uintptr_t deallocating      : 1;//->对象是否正在销毁
         uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
        uintptr_t extra_rc          : 19;  //->存储引用计数
    };
};


sideTables、sideTable 简单了解

sideTablesSideTables() 方法返回的是一个 StripedMap& 类型的引用:在这里插入图片描述

// StripedMap<T> is a map of void* -> T, sized appropriately 
// for cache-friendly lock striping. 
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];
    ...
}

StripedMap 是一个模板类, 内部维护一个大小为 StripeCount 的数组, 数组的成员为结构体 PaddedT, PaddedT 结构体只有一个成员 value, value 的类型是 T.
StripedMap< SideTable >, 说明类内的结构体的成员类型为SideTable,根据源码中的注释可知StripedMap是一个void* -> T的映射。哈希值(value,即元素sideTable)可根据对象的地址(key)得到。

在这里插入图片描述


sideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;    //存引用计数的哈希表表
    weak_table_t weak_table;  //用于存储对象弱引用的哈希表
}
 

RefcountMap是一个hash map,其key是obj的DisguisedPtr<objc_object>。value,则是obj对象的引用计数,同时,这个map还有个加强版功能,当引用计数为0时,会自动将对象数据清除。

参考
参考


//
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;


下面在retainCount中会有从sidetables、sideTable中获取value的步骤

retainCount实现


- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}

_objc_rootRetainCount(id obj)
{
    ASSERT(obj);

    return obj->rootRetainCount();
}


objc_object::rootRetainCount()
{
    //标记指针 直接返回
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    
    //nonpointer 为1 表示优化过的isa指针
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        
        //has_sidetable_rc 为1说明 extra_rc溢出,存放在sidetable中
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    //未优化的isa指针
    sidetable_unlock();
    return sidetable_retainCount();
}

//优化过的isa,且extra溢出,要执行的方法
size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
    
    //SideTables中通过this指针(key)获取sideTable
    SideTable& table = SideTables()[this];
	
	//在sidetable中的refcnts(存储引用计数的哈希表)通过this(key)获取对象的引用计数(value)
	
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
	//返回引用计数
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}



//未优化的isa
uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

retain 实现

objc_retain(id obj)
{
    if (obj->isTaggedPointerOrNil()) return obj;
    return obj->retain();
}

objc_object::retain()
{
    ASSERT(!isTaggedPointer());

    return rootRetain(false, RRVariant::FastOrMsgSend);
}


objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    //如果是标签指针 直接返回
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);

    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // 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()) {
                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;
        newisa = oldisa;
        
        //未优化的isa部分
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        
        //对象正在被释放的处理
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        
        //extra_rc未溢出 extra_rc++
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        //printf("------------------%d\n", newisa.extra_rc);

        //newisa.extra_rc++ 溢出
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                
                //重新调rootRetain,handleOverflow 为 true (处理溢出)
                return rootRetain_overflow(tryRetain);
            }
            
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            // 保留一半引用计数
            // 准备将另一半复制到 side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            
            //has_sidetable_rc设置为true,表示使用了sidetable来计算引用次数
            newisa.has_sidetable_rc = true;
        }
        //  更新 isa 值
    } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    
    
    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            // 将另一半复制到 side table side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}




  • TaggedPointer:值存在指针内,直接返回。
  • !newisa.nonpointer:未优化的 isa ,使用sidetable_retain()。
  • newisa.nonpointer:已优化的 isa , 分 extra_rc 溢出和未溢出的两种情况。
    未溢出时,isa.extra_rc++。
    溢出时,将 isa.extra_rc 中一半值转移至sidetable中,将isa.has_sidetable_rc设置为true,表示使用了sidetable来存引用次数。

release实现

- (oneway void)release {
    _objc_rootRelease(self);
}

_objc_rootRelease(id obj)
{
    ASSERT(obj);

    obj->rootRelease();
}

objc_object::rootRelease()
{
    return rootRelease(true, RRVariant::Fast);
}

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;
        }
    }

    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;
        
        //没优化过的指针
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            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;
        // extra_rc--
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));

    if (slowpath(newisa.isDeallocating()))
        goto deallocate;

    if (variant == RRVariant::Full) {
        if (slowpath(sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!sideTableLocked);
    }
    return false;

 underflow:
    //处理下溢,从 side table 中借位或者释放
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    //has_sidetable_rc设置为1,则使用了sideTable
    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;
        }

        // Try to remove some retain counts from the side table.
        
        // 从 sidetable 中借位引用计数给 extra_rc
        auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
        
        // we'll clear the side table if no refcounts remain there
        //sideTable没有引用计数 就清空
        bool emptySideTable = borrow.remaining == 0;

        if (borrow.borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            bool didTransitionToDeallocating = false;
            // extra_rc 是计算额外的引用计数,0 即表示被引用一次?????
            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();
                    }
                }
            }

            //如果还是保存失败,则还回 side table
            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.
        }
    }

deallocate:
    // Really deallocate.

    ASSERT(newisa.isDeallocating());
    ASSERT(isa.isDeallocating());

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}


逻辑大概如下:

  • 是标记指针,直接返回
  • 是未优化过的isa,执行sidetable_release
  • 优化过的指针:
    extra_rc未下溢,extra_rc–
    extra_rc下溢:从 sidetable 中借位给 extra_rc 达到半满,如果无法借位则说明引用计数归零需要进行释放。其中借位时可能保存失败会不断重试

retain、release过程大总结:

  • 标记指针,直接返回
  • 对于没有优化过的isa
    retain就调用sidetable_retain,对sideTable中存储的引用计数进行操作。
    release就调用sidetable_release,对sideTable中存储的引用计数进行操作。
  • 对于优化过的isa
    retain,如果extra_rc未溢出,ectra_rc++;溢出,将isa.has_sidetable_rc设置为true,表示sideTable存储引用计数,将extra_rc一半值移到sideTable中。
    release,如果extra_rc为下溢,extra_rc–;下溢,从 sidetable 中借位给 extra_rc ,如果无法借位则说明引用计数归零需要进行释放。

retain实现逻辑图
在这里插入图片描述


release实现逻辑图
在这里插入图片描述


objc_autoreleaseReturnValue实现

//
enum ReturnDisposition : bool {
    ReturnAtPlus0 = false, ReturnAtPlus1 = true
};


id 
objc_autoreleaseReturnValue(id obj)
{
    //优化 ,returnAtPlus1表示true
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
    
    //不优化
    return objc_autorelease(obj);
}


static ALWAYS_INLINE bool 
prepareOptimizedReturn(ReturnDisposition disposition)
{
    ASSERT(getReturnDisposition() == ReturnAtPlus0);

    //检查调用方的方法列表,如果紧跟着执行retain,将不注册到autoreleasePool中
    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        //设置标记
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}


//ReturnAtPlus0 = false, ReturnAtPlus1 = true,
//优化流程将一个标志位存储在 TLS (Thread Local Storage) 中
//设置标志位
static ALWAYS_INLINE void 
setReturnDisposition(ReturnDisposition disposition)
{
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

代码逻辑:

  • 检查调用方方法列表是否紧跟着retain
  • 紧跟着retain,设置TLS中的标志位为true,返回对象
  • 没有retain,执行objc_autorelease,放入自动释放池

objc_retainAutoreleasedReturnValue实现


id
objc_retainAutoreleasedReturnValue(id obj)
{
    
    // 如果 TLS 中标记表示使用了优化程序,则直接返回
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
    return objc_retain(obj);
}


static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    //取出标记
    ReturnDisposition disposition = getReturnDisposition();
    
    //重置标志位 置为false
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

//获取标记的函数
static ALWAYS_INLINE ReturnDisposition 
getReturnDisposition()
{
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}


//ReturnAtPlus0 = false, ReturnAtPlus1 = true,
//优化流程将一个标志位存储在 TLS (Thread Local Storage) 中
static ALWAYS_INLINE void 
setReturnDisposition(ReturnDisposition disposition)
{
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}


代码逻辑:

  • 取出TLS 中标记
  • 重置标志位,置为false
  • 根据取出的标记判断是否执行优化
    执行优化,直接返回对象
    不执行优化,引用计数+1


http://www.niftyadmin.cn/n/1617806.html

相关文章

iOS—对象的本质(学习笔记)

只是小小记录一下&#xff0c;内容不完整 OC底层实现是c\c&#xff0c;oc的面向对象都是基于c\c实现NSObject c 底层结构 struct NSObject_IMPL {//CLass 指针&#xff0c;64位占8个字节__unsafe_unretained Class isa; };//Class typedef struct objc_class *Classoc中的定义…

iOS—[self class]和[super class]

先上一段代码 Person类继承NSObject Student类继承Person //Person类 继承自NSObject #import <Foundation/Foundation.h>interface Person : NSObjectend#import "Person.h"implementation Personend//Student类 继承自Person #import "Person.h"N…

iOS—属性关键字

文章目录property/synthesize/dynamic系统默认关键字原子性读写权限引用计数assignunsafe_unretainedweakstrongcopy一些问题property/synthesize/dynamic property 我们经常会看到 属性 getter setter 成员变量 但实际上property会生成setter、getter的方法声明synthesize会…

iOS—strong、weak的实现以及dealloc的调用流程

文章目录__strong修饰符ARC在编译器、运行期做了哪些工作&#xff1f;__weak修饰符__weak在使用过程中调用的函数objc_initWeakstoreWeakSideTableweak_table_t 结构weak_entry_t 结构weak_register_no_lock方法&#xff0c;添加弱引用weak_entry_for_referent &#xff0c;获取…

iOS—KVO用法及本质

文章目录KVOKVO 的使用KVO本质总结一些问题KVO KVO全称KeyValueObserving&#xff0c;键值监听&#xff0c;是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变&#xff0c;并在改变时接受到事件。 KVO 的使用 三个方法 注册观察者 addObserver:(nonnull…

iOS—KVC本质

文章目录KVC一些API集合运算符setValue:forKey: 原理&#xff0c;赋值过程ValueForKey&#xff1a;原理&#xff0c;获值过程一些问题未完未完未完 后续补充KVC KVC全称是Key-Value-Coding&#xff0c;键值编码&#xff0c;可以通过一个key来访问某个属性 一些API 常见的API…

iOS—GCD详解

文章目录多线程编程进程线程线程与进程的关系多线程编程多线程编程的问题GCD什么是GCDGCD任务和队列GCD的使用步骤队列的创建队列的获取任务的创建方法任务与队列不同组合方式的区别同步执行 并发队列异步执行 并发队列同步执行 串行队列异步执行 串行队列同步执行 主队列…

iOS—pthread、NSThread简单了解

文章目录pthreadpthread的使用pthread其他相关方法NSThread创建、启动线程线程相关用法线程状态控制方法线程之间的通信NSThread线程安全pthread pthread是一套通用的多线程API&#xff0c;可以在Unix/Linux/Windows等系统跨平台使用&#xff0c;使用C语言编写&#xff0c;需要…