【iOS】—— retain\release实现原理和属性关键字

news2024/11/24 18:26:33

【iOS】—— retain\release实现原理和属性关键字

    • 1. retain\reelase实现原理
      • 1.1 retain实现原理
      • 1.2 release实现原理
    • 2. 属性关键字
      • 2.1 属性关键字的分类
      • 2.2 内存管理关键字
        • 2.2.1 weak
        • 2.2.2 assgin
        • 2.3.3 strong和copy
      • 2.4 线程安全的关键字
      • 2.5 修饰变量的关键字
        • 2.5.1常量const
        • 2.5.2 static
        • 2.5.3 常量extern
        • 2.5.4 static与const联合使用
        • 2.5.5 extern与const联合使用

1. retain\reelase实现原理

1.1 retain实现原理

首先来看一下retain的源码:

 ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    // 如果是 Tagged Pointer 则直接返回 this (Tagged Pointer 不参与引用计数管理,它的内存在栈区,由系统处理)
    if (slowpath(isTaggedPointer())) return (id)this;
    // 临时变量,标记 SideTable 是否加锁
    bool sideTableLocked = false;
    // 临时变量,标记是否需要把引用计数迁移到 SideTable 中
    bool transcribeToSideTable = false;

    // 记录 objc_object 之前的 isa
    isa_t oldisa;
    // 记录 objc_object 修改后的 isa
    isa_t newisa;
    
    // 似乎是原子性操作,读取 &isa.bits。(&为取地址)
    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.
        // 这些检查仅对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;
        }
    }
    
    // 循环结束的条件是 slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))
    // StoreExclusive 函数,如果 &isa.bits 与 oldisa.bits 的内存内容相同,则返回 true,并把 newisa.bits 复制到 &isa.bits,
    // 否则返回 false,并把 &isa.bits 的内容加载到 oldisa.bits 中。
    // 即 do-while 的循环条件是指,&isa.bits 与 oldisa.bits 内容不同,如果它们内容不同,则一直进行循环,
    // 循环的最终目的就是把 newisa.bits 复制到 &isa.bits 中。
    // return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst,
    //                                          &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED)
    
    // _Bool atomic_compare_exchange_weak( volatile A *obj, C* expected, C desired );
    // 定义于头文件 <stdatomic.h>
    // 原子地比较 obj 所指向对象的内存的内容与 expected 所指向的内存的内容,若它们相等,则以 desired 替换前者(进行读修改写操作)。
    // 否则,将 obj 所指向的实际内存内容加载到 *expected (进行加载操作)。
    
    do {
        // 默认不需要转移引用计数到 SideTable
        transcribeToSideTable = false;
        
        // 赋值给 newisa(第一次进来时 &isa.bits, oldisa.bits, newisa.bits 三者是完全相同的)
        newisa = oldisa;
        
        // 如果 newisa 不是优化的 isa (元类的 isa 是原始的 isa (Class cls))
        if (slowpath(!newisa.nonpointer)) {
            
            // 在 mac、arm64e 下不执行任何操作,只在 arm64 下执行 __builtin_arm_clrex();
            // 在 arm64 平台下,清除对 &isa.bits 的独占访问标记。
            ClearExclusive(&isa.bits);
            
            // 如果需要 tryRetain 则调用 sidetable_tryRetain 函数,并根据结果返回 this 或者 nil。
            // 执行此行之前是不需要在当前函数对 SideTable 加锁的
            // sidetable_tryRetain 返回 false 表示对象已被标记为正在释放,
            // 所以此时再执行 retain 操作是没有意义的,所以返回 nil。
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            // 如果不需要 tryRetain 则调用 sidetable_retain()
            else return sidetable_retain(sideTableLocked);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        // 不要检查 newisa.fast_rr; 我们已经调用所有 RR 的重载。
        // 如果 tryRetain 为真并且 objc_object 被标记为正在释放 (newisa.deallocating),则返回 nil
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            // SideTable 处于加锁状态
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                // 进行解锁
                sidetable_unlock();
            }
            // 需要 tryRetain
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        // 下面就是 isa 为 nonpointer,并且没有被标记为正在释放的对象
        uintptr_t carry;
        // bits extra_rc 自增
        
        // x86_64 平台下:
        // # define RC_ONE (1ULL<<56)
        // uintptr_t extra_rc : 8
        // extra_rc 内容位于 56~64 位
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        // 如果 carry 为 true,表示要处理引用计数溢出的情况
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            // 如果 variant 不为 Full,
            // 则调用 rootRetain_overflow(tryRetain) 它的作用就是把 variant 传为 Full
            // 再次调用 rootRetain 函数,目的就是 extra_rc 发生溢出时,我们一定要处理
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            // 将 retain count 的一半留在 inline,并准备将另一半复制到 SideTable.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            // 整个函数只有这里把 sideTableLocked 置为 true
            sideTableLocked = true;
            // 标记需要把引用计数转移到 SideTable 中
            transcribeToSideTable = true;
            // x86_64 平台下:
            // uintptr_t extra_rc : 8
            // # define RC_HALF  (1ULL<<7) 二进制表示为: 0b 1000,0000
            // extra_rc 总共 8 位,现在把它置为 RC_HALF,表示 extra_rc 溢出
            newisa.extra_rc = RC_HALF;
            // 把 has_sidetable_rc 标记为 true,表示 extra_rc 已经存不下该对象的引用计数,
            // 需要扩张到 SideTable 中
            newisa.has_sidetable_rc = true;
        }
    } 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.
            // 复制 retain count 的另一半到 SideTable 中。
            sidetable_addExtraRC_nolock(RC_HALF);
        }
        // 如果 tryRetain 为 false 并且 sideTableLocked 为 true,则 SideTable 解锁
        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }
    // 返回 this
    return (id)this;
}

在这里插入图片描述

我们根据图来看一下retain步骤:

  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)函数;

1.2 release实现原理

看一下release的源代码:

 ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 未优化 isa
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            // 入参是否要执行 Dealloc 函数,如果为 true 则执行 SEL_dealloc
            return sidetable_release(performDealloc);
        }

        // extra_rc --
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // donot ClearExclusive()
            goto underflow;
        }
        // 更新 isa 值
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
 	// 处理下溢,从 side table 中借位或者释放

    newisa = oldisa;
    // 如果使用了 sidetable_rc
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
        	// 调用本函数处理下溢
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // 从 sidetable 中借位引用计数给 extra_rc
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        if (borrowed > 0) {
			// extra_rc 是计算额外的引用计数,0 即表示被引用一次
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
                                                
            // 保存失败,恢复现场,重试                                    
            if (!stored) {
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

			// 如果还是保存失败,则还回 side table
            if (!stored) {
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }
    // 没有使用 sidetable_rc ,或者 sidetable_rc 计数 == 0 的就直接释放

    // 如果已经是释放中,抛个过度释放错误
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    // 更新 isa 状态
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

	// 执行 SEL_dealloc 事件
    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

在这里插入图片描述
我们根据图来看一下release步骤:

  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流程。

2. 属性关键字

属性关键字是用来修饰属性的关键字,保证程序的正常执行。

2.1 属性关键字的分类

  • 内存管理有关的关键字:weak, assgin, strong, retain, copy
  • 线程安全的关键字:monatomicatomic
  • 访问权限的关键字:readonlyreadwrite
  • 修饰变量的关键字:conststaticextern

2.2 内存管理关键字

2.2.1 weak

weak将常用来修饰OC对象数据类型,修饰的对象释放之后,指针会自动置nil,这是弱引用的表现。

**在ARC的环境下,为了避免循环引用,delegate往往是用的是weak修饰。在MRC下使用assgin修饰。**当某个对象不再拥有strong类型的指向的时候对象就会被释放,即使还有weak类型的指针指向它,weak指针也会被清除。

2.2.2 assgin

assgin常用于非指针变量,用于修饰基础数据类型和C的数据类型,用于基本数据类型进行复制操作。

asssgin不会修改引用计数,也可以用来修饰对象一般不建议如此,因为assgin修饰的对象被释放之后指针的地址还存着,成为了一个没有指向的野指针(垂悬指针)。

assgin修饰的基本类型都是基本数据类型,基本数据类型分配在栈上,栈上的变量是由系统自动管理,不会造成野指针以及MRC状态下的循环引用。

eg:当对象A通过retain持有了B,B的delegate对象是A,如果都是强引用则导致互相持有无法正确的释放,造成循环引用。

weak和assgin的区别:

  • 修饰的对象不同:weak修饰OC对象类型的数据,assgin修饰的基本数据类型。
  • 引用计数:两者都不会增加引用计数。
  • 释放后结果不同:weak修饰的对象释放之后指针自动为nil。assgin修饰的对象释放之后指针仍然存在,成为野指针。
  • 修饰delegate:MRC下assgin,ARC下weak,两者都是为了避免循环引用。
2.3.3 strong和copy

strong是常用的修饰符,主要用来修饰OC对象类型的数据(NSNumber,NSString,NSArray、NSDate、NSDictionary、模型类等)。 strong是强引用,在ARC下等于retain,这一点区别于weak。

strong就是指针拷贝(浅拷贝),内存地址不变,只是产生新的指针,新的指针和引用对象的指针指向同一个内存地址,没有生成新的对象只是多了一个指针。

**注意:**由于使用的是一个内存地址,当该内存地址存储的内容发生变更的时候导致属性也跟着变更。

同样用于修饰OC对象类型的数据,同时在MRC时期用来修饰block,因为MRC时期block要从栈区copy到堆区。现在的ARC系统自动给我们做了这个操作。也就是现在使用strong或者copy修饰block都可以。

copy和strong相同点在于都是属于强引用,引用计数+1,但是copy修饰的对象是内存拷贝,在引用的时候会生成新的内存地址和指针,和引用对象完全没有相同点,因此它不会因为引用属性的变更而改变。

copy关键字和strong的区别:

  • **copy:**内存拷贝-深拷贝,内存地址不同,指针地址也不同。

  • **strong:**指针拷贝-浅拷贝,内存地址不变,指针地址不同。

声明两个copy属性,两个strong属性,分别为可变和不可变类型:

@property (nonatomic, strong) NSString *Strstrong;
@property (nonatomic, copy) NSString *Strcopy;
@property (nonatomic, strong) NSMutableString *MutableStrongstr;
@property (nonatomic, copy) NSMutableString *MutableCopystr;

1. 不可变对象对属性进行赋值,查看两者的区别

 - (void)TestModel {
    //不可变对象对属性赋值
    NSString *otherString = @"我是谁";
    
    self.Strcopy = otherString;
    self.Strstrong = otherString;
    self.MutableCopystr = otherString;
    self.MutableStrongstr = otherString;  
    // 内容
    NSLog(@"原字符串=>%@\n normal:copy=>%@=====strong=>%@\nMutable:copy=>%@=====strong=>%@", otherString, _Strcopy, _Strstrong, _MutableCopystr, _MutableStrongstr);
    // 内存地址
    NSLog(@"原字符串=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p", otherString, _Strcopy, _Strstrong, _MutableCopystr, _MutableStrongstr);
    // 指针地址
    NSLog(@"原字符串=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p", &otherString, &_Strcopy, &_Strstrong, &_MutableCopystr, &_MutableStrongstr); 
    
}

在这里插入图片描述
由上面可以看出,strong修饰的对象,在引用一个对象的时候,内存地址都是一样的,只有指针地址不同,copy修饰的对象也是如此。
为什么呢?不是说copy修饰的对象是生成一个新的内存地址嘛?这里为什么内存地址还是原来的呢?用不可变对象对属性进行赋值,无论是strong还是copy,都是一样的,原内存地址不变,生成了新的指针地址。

2. 可变对象对属性进行赋值,查看strong和copy的区别

 - (void)testModel {
    //可变对象对属性赋值
    NSMutableString * OriginalMutableStr = [NSMutableString stringWithFormat:@"我已经开始测试了"];

    self.Strcopy = OriginalMutableStr;
    self.Strstrong = OriginalMutableStr;
    self.MutableCopystr = OriginalMutableStr;
    self.MutableStrongstr = OriginalMutableStr;
    [OriginalMutableStr appendFormat:@"改变了"];
    // 内容
    NSLog(@"原字符串=>%@\n normal:copy=>%@=====strong=>%@\nMutable:copy=>%@=====strong=>%@",OriginalMutableStr,_Strcopy,_Strstrong,_MutableCopystr,_MutableStrongstr);
    // 内存地址
    NSLog(@"原字符串=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",OriginalMutableStr,_Strcopy,_Strstrong,_MutableCopystr,_MutableStrongstr);
    // 指针地址
    NSLog(@"原字符串=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",&OriginalMutableStr,&_Strcopy,&_Strstrong,&_MutableCopystr,&_MutableStrongstr);
}

在这里插入图片描述
在上面的结果可以看出,strong修饰的属性内存地址依然没有改变,但是copy修饰的属性内存值产生了变化。
由此得出结论:对可变对象赋值 strong 是原地址不变,引用计数+1(浅拷贝)。 copy是生成一个新的地址和对象生成一个新指针指向新的内存地址(深拷贝)。

3. 此时改变OriginalMutableStr的值

 [OriginalMutableStr appendFormat:@"改变了"];

在这里插入图片描述
结论:

  1. strong修饰的属性,跟着进行改变。
  2. 由于OriginalMutableStr是可变类型,是在原有内存上进行修改,指针地址和内存地址都没有改变,由于strong修饰的属性虽然指针地址不同,但是指针指向的是原内存地址。
  3. 不同于strong,copy修饰的类型不仅指针地址不同,而且指向的内存地址也和OriginalMutableStr不一样,所以不会跟着 OriginalMutableStr的改变而改变。

注意的是:使用self.Strcopy 和 _Strcopy 来赋值也是两个不一样的结果,因为后者没有调用 set 方法,而 copy 和 strong 之所以会产生差别就是因为在 set 方法中,copy修饰的属性: 调用了 _Strcopy = [Strcopy copy] 方法

4. 深浅拷贝

1)深浅拷贝的区别?

浅拷贝:对内存地址的复制,两个指针指向同一个地址,增加被拷贝对象的引用计数,没有发生新的内存分配。

深拷贝:目标对象指针和原对象指针,指向两片内存空间。(不会增加被拷贝对象的引用计数,产生新的内存,出现两块内存。

总结区别:

  • 浅拷贝增加引用计数,不产生新的内存。
  • 深拷贝不增加引用计数,会新分配内存。

2)copy关键字影响了对象的可变和不可变属性吗?

  • 可变对象(mutable)copy和mutableCopy都是深拷贝
  • 不可变对象(immutable)的copy是浅拷贝,mutableCopy是深拷贝
  • copy方法返回的都是不可变对象,若被拷贝对象是可变对象,返回的也是不可变对象。
    在这里插入图片描述
    3)NSMutableArray用copy修饰会出现什么问题?

**出现调用可变方法不可控问题,会导致程序崩溃。**给Mutable 被声明为copy修饰的属性赋值, 过程描述如下:

如果赋值过来的是NSMutableArray对象,会对可变对象进行copy操作,拷贝结果是不可变的,那么copy后就是NSArray
如果赋值过来的是NSArray对象, 会对不可变对象进行copy操作,拷贝结果仍是不可变的,那么copy之后仍是NSArray。
所以不论赋值过来的是什么对象,只要对NSMutableArray进行copy操作,返回的对象都是不可变的。
原来属性声明的是NSMutableArray,可能会调用了add或者remove方法,拷贝后的结果是不可变对象,所以一旦调用这些方法就会程序崩溃(crash)。

4)说说strong和weak的区别是?

  • strong表示指向并拥有该对象,修饰的对象引用计数+1,只要引用计数不为0,就不会被销毁,
  • weak表示指向但是不拥有该对象,修饰的对象引用计数不会增加。无需手动该对象会自行在内存中销毁。

5)weak属性修饰的变量,如何实现在变量没有强引用后自动置为 nil ?

runtime维护了一个weak_table _t弱引用表,用于存储某个对象的所有weak指针。weak是一个哈希表key是所指对象的的地址,value是weak指针的地址的数组。在回收对象的时候,根据对象的地址将所有的weak指针地址的数组便利,把其中的数据值为nil。

2.4 线程安全的关键字

  • nonatomic关键字

​ nonatomic:非原子操作,不加锁,线程执行快,但是多个线程同时访问同一属性会出现崩溃。

  • atomic关键字

​ atomic原子操作:加锁,保证setter和getter存取方法的线程安全(仅仅对setter和getter方法加锁)。因为线程加锁,别的线程访问当前属性的时候会先执行完属性当前的操作。

⚠️注意:atomic只针对属性的 getter/setter 方法进行加锁,所以安全只是针对getter/setter方法来说,并不是整个线程安全,因为一个属性并不只有 setter/getter 方法,例:(如果一个线程正在getter 或者 setter时,有另外一个线程同时对该属性进行release操作,如果release先完成,会造成crash)

2.5 修饰变量的关键字

2.5.1常量const

常量修饰符,表示不可变,可以用来修饰右边的基本变量和指针变量(放在谁的前面修饰谁(基本数据变量p,指针变量*p))。

**const 类型 * 变量名a:可以改变指针的指向,不能改变指针指向的内容。 **

const放在 * 号的前面约束参数,表示*a只读。只能修改地址a,不能通过a修改访问的内存空间

int x = 12;
int new_x = 21;
const int *px = &x; 
px = &new_x; // 改变指针px的指向,使其指向变量y


**类型 * const 变量名:可以改变指针指向的内容,不能改变指针的指向。 **

const放后面约束参数,表示a只读,不能修改a的地址,只能修改a访问的值,不能修改参数的地址

int y = 12;
int new_y = 21;
int * const py = &y;
(*py) = new_y; // 改变px指向的变量x的值

const和define的区别:

使用宏和常量所占的内存差别不大,宏定义的是常量,常量都放在常量区,只会生成一份内存。

缺点:

  • 编译时刻:宏是预编译,const是编译阶段。
  • 导致使用宏定义过多的话,随着工程越来越大,编译速度会越来越慢
    宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。

优点:

  • 宏可以定义一些函数,方法。const不能。
2.5.2 static

定义所修饰的对象只能在当前文件访问,不能通过extern来引用

  1. static修饰全局变量:只能在本文件中访问,修改全局变量的作用域,生命周期不会改。避免重复定义全局变量(单例模式)
  2. static修饰局部变量:
  • 有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用的时候该变量已经有值。这时就应该指定该局部变量为静态变量,用关键字 static 进行声明。
  • 延长局部变量的生命周期(没有改变变量的作用域,只在当前作用域有用),程序结束才会销毁。

注意:当在对象A里这么写static int i = 10; 当A销毁掉之后 这个i还存在当再次alloc init一个A的对象之后 在新对象里 依然可以拿到i = 90,除非杀死程序 再次进入才能得到i = 0。

局部变量只会生成一份内存,只会初始化一次。把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在

- (void)test{
    // static修饰局部变量1
    static int age = 0;
    age++;
    NSLog(@"%d",age);
}
-(void)test2{
    // static修饰局部变量2
    static int age = 0;
    age++;
    NSLog(@"%d",age);
}

[self test];
[self test2];
[self test];
[self test2];
[self test];
[self test2];

打印 1 1 2 2 3 3


由此可见 变量生命周期延长了,作用域没有变。

2.5.3 常量extern

只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量。

查找优先级: 先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。

#import "JMProxy.h"
@implementation JMProxy
int ageJMProxy = 20;
@end

@implementation TableViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    extern int ageJMProxy;
    NSLog(@"%d",ageJMProxy);
}
@end
 

⚠️ extern不能用于定义变量。

2.5.4 static与const联合使用

声明一个静态的全局只读常量。开发中声明的全局变量,有些不希望外界改动,只允许读取。

iOS中staic和const常用使用场景,是用来代替宏,把一个经常使用的字符串常量,定义成静态全局只读变量.

 // 开发中经常拿到key修改值,因此用const修饰key,表示key只读,不允许修改。
static  NSString * const key = @"name";

// 如果 const修饰 *key1,表示*key1只读,key1还是能改变。

static  NSString const *key1 = @"name";

2.5.5 extern与const联合使用

在多个文件中经常使用的同一个字符串常量,可以使用extern与const组合
extern与const组合:只需要定义一份全局变量,多个文件共享

@interface Person : NSObject
extern NSString * const nameKey = @"name"; 
@end

#import "ViewController.h"
@interface ViewController ()

@end
NSString * const nameKey; // 必须用xonst才能访问到 extern与const组合组合修饰的全局变量 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

北京率先建设AI原生城市,力争明年推出百个优秀行业大模型产品

7月26日&#xff0c;《北京市推动“人工智能”行动计划&#xff08;2024-2025年&#xff09;》&#xff08;简称《行动计划》&#xff09;正式向社会发布&#xff0c;新京报记者在北京市发展和改革委员会举行的新闻发布会上获悉&#xff0c;北京将率先建设AI原生城市&#xff0…

基于JSP的班级同学录网站

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPB/S架构 工具&#xff1a;Eclipse、Mysql 系统展示 首页 管理员功能界面 用户功能界面 论坛管…

ubuntu上部署vue项目到ngixn中+SpringBoot项目+postgresql数据库

文章目录 前提1.Ubuntu上安装ngix2.部署Vue项目2.1上传vue项目2.2.配置 3.Ubuntu上安装Postgres4.部署springboot项目 前提 记一次在ubuntu部署前端vue和后端springboot项目&#xff0c;以及数据库postgresql的安装以及启动、停止等常用的命令。 1.Ubuntu上安装ngix 1、检查…

探索 Python 的色彩世界:Colorama 库深度解析

文章目录 &#x1f308; 探索 Python 的色彩世界&#xff1a;Colorama 库深度解析背景&#xff1a;为何选择 Colorama&#xff1f;Colorama 是什么&#xff1f;如何安装 Colorama&#xff1f;简单库函数使用方法场景应用示例常见问题及解决方案总结 &#x1f308; 探索 Python …

Gartner发布2024年零信任网络技术成熟度曲线:20项零信任相关的前沿和趋势性技术

大多数组织都制定了零信任信息安全策略&#xff0c;而网络是零信任实施领域的顶级技术。此技术成熟度曲线可以帮助安全和风险管理领导者确定合适的技术&#xff0c;以将零信任原则嵌入其网络中。 战略规划假设 到 2026 年&#xff0c;15% 的企业将在企业拥有的局域网上用 ZTNA …

HarmonyOS 质量、测试、上架速浏

1.应用质量要求&#xff1a; 1. 应用体验质量建议: 功能数据完备 功能完备 数据完备 基础体验要求 基础约束 兼容性 稳定性 性能 功耗 安全…

Yolov5-v7.0使用CBAM注意力机制记录

Yolov5-v7.0使用CBAM注意力机制记录 一、CBAM实现代码 在model/common.py文件中加入如下代码&#xff1a; #############CBAM注意力机制############## class ChannelAttention(nn.Module):def __init__(self, in_planes, ratio16):super(ChannelAttention, self).__init__(…

【Windows】激活补丁被误删,怎么办?如何关闭Windows11安全中心中的“病毒和威胁保护”!

按下“win&#xff08;徽标键&#xff09;i”快捷键&#xff0c;选择隐私与安全性-Windows安全中心。 选择防火墙和网络保护-域保护。 将开关闭&#xff0c;专业网络和公用网络防火墙也同样关闭&#xff0c;如下图所示&#xff1a; 关闭防火墙后&#xff0c;左边菜单…

改进向量搜索-使用PostgresML和LlamaIndex重新排名

改进向量搜索-使用PostgresML和LlamaIndex重新排名 搜索和重新排名&#xff1a;提高结果相关性 搜索系统通常采用两种主要方法&#xff1a;关键字和语义。关键字搜索将精确的查询词与索引数据库内容匹配&#xff0c;而语义搜索使用 NLP 和机器学习来理解查询上下文和意图。许多…

【踩坑系列-Docker】基于Alibaba Cloud Linux3基础镜像安装Nginx

Author&#xff1a;赵志乾 Date&#xff1a;2024-07-26 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 1. 问题描述 使用Alibaba Cloud Linux3作为基础镜像&#xff0c;在其上安装Nginx&#xff0c;对应的Dockerfile内容如下&#xff1a; …

使用 From File 模块加载数据

目录 检查模型 创建时间和信号数据 加载 timeseries 数据 加载数组数据 加载总线数据 此示例说明如何使用 From File 模块从 MAT 文件加载仿真输入数据,包括如何创建和格式化输入数据。可以通过编程方式创建您加载的数据,加载从另一个仿真中记录的数据,或加载从…

栈和队列<数据结构 C版>

目录 栈&#xff08;Stack&#xff09; 栈的结构体 初始化 销毁 入栈 判空 出栈 取栈顶元素 获取栈个数 测试&#xff1a; 队列&#xff08;Queue&#xff09; 队列的结构体 单个结点 队列 初始化 销毁 入队列&#xff0c;队尾 判空 出队列&#xff0c;队头 …

【YashanDB知识库】开源调度框架Quartz写入Boolean值到YashanDB报错

问题现象 Quartz 是一个广泛应用于企业级应用中的开源作业调度框架&#xff0c;它主要用于在Java环境中管理和执行任务。 为了任务调度&#xff0c;Quartz的数据模型中使用了大量的布尔值记录任务、流程的各种状态&#xff0c;如&#xff1a; Quartz使用JDBC写入任务状态代码…

【资料分享】2024第三届钉钉杯大学生大数据挑战赛B题思路解析+双语言代码

2024钉钉杯大学生大数据挑战赛&#xff0c;B题解题思路和双语言代码分享&#xff0c;资料预览&#xff1a;

制作excel模板,用于管理后台批量导入船舶数据

文章目录 引言I 数据有效性:基于WPS在Excel中设置下拉框选择序列内容II 数据处理:基于easyexcel工具实现导入数据的持久化2.1 自定义枚举转换器2.2 ExcelDataConvertExceptionIII 序列格式化: 基于Sublime Text 文本编辑器进行批量字符操作引言 需求: excel数据导入模板制…

【MySQL进阶之路 | 高级篇】表级锁之S锁,X锁,意向锁

1. 从数据操作的粒度划分&#xff1a;表级锁&#xff0c;页级锁&#xff0c;行锁 为了尽可能提高数据库的并发度&#xff0c;每次锁定的数据范围越小越好&#xff0c;理论上每次只锁定当前操作的数据的方案会得到最大的并发度&#xff0c;但是管理锁是很耗资源的事情&#xff…

前端开发:HTML与CSS

文章目录 前言1.1、CS架构和BS架构1.2、网页构成 HTML1.web开发1.1、最简单的web应用程序1.2、HTTP协议1.2.1 、简介1.2.2、 http协议特性1.3.3、http请求协议与响应协议 2.HTML概述3.HTML标准结构4.标签的语法5.基本标签6.超链接标签6.1、超链接基本使用6.2、锚点 7.img标签8.…

【网络安全的神秘世界】文件包含漏洞

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 一、概述 文件包含&#xff1a;重复使用的函数写在文件里&#xff0c;需要使用某个函数时直接调用此文件&#xff0c;而无需再…

【学习日记】函数调用 和 全局变量 如何实现 位置无关码

问题来源 在 I.MX6ull 的启动流程中&#xff0c;u-boot会将自身从内存一开始的位置拷贝到其他位置&#xff0c;以便给linux留出内存空间&#xff0c;防止 u-boot被覆盖如果代码中包含直接引用其链接时地址的指令&#xff0c;那么当代码被移动到新的地址时&#xff0c;这些引用…

聊聊RNNLSTM

RNN 用于解决输入数据为&#xff0c;序列到序列(时间序列)数据&#xff0c;不能在传统的前馈神经网络(FNN)很好应用的问题。时间序列数据是指在不同时间点上收集到的数据&#xff0c;这类数据反映了某一事物、现象等随时间的变化状态或程度&#xff0c;即输入内容的上下文关联…