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

【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 之前的 isaisa_t oldisa;// 记录 objc_object 修改后的 isaisa_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 loopif (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 {// 默认不需要转移引用计数到 SideTabletranscribeToSideTable = 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),则返回 nilif (slowpath(newisa.isDeallocating())) {ClearExclusive(&isa.bits);// SideTable 处于加锁状态if (sideTableLocked) {ASSERT(variant == RRVariant::Full);// 进行解锁sidetable_unlock();}// 需要 tryRetainif (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 置为 truesideTableLocked = 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);}// 返回 thisreturn (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)) {// 未优化 isaClearExclusive(&isa.bits);if (sideTableLocked) sidetable_unlock();// 入参是否要执行 Dealloc 函数,如果为 true 则执行 SEL_deallocreturn 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_rcif (slowpath(newisa.has_sidetable_rc)) {if (!handleUnderflow) {// 调用本函数处理下溢ClearExclusive(&isa.bits);return rootRelease_underflow(performDealloc);}// 从 sidetable 中借位引用计数给 extra_rcsize_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);if (borrowed > 0) {// extra_rc 是计算额外的引用计数,0 即表示被引用一次newisa.extra_rc = borrowed - 1;  // redo the original decrement toobool 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 tableif (!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修饰局部变量1static int age = 0;age++;NSLog(@"%d",age);
}
-(void)test2{// static修饰局部变量2static 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.mzph.cn/web/49703.shtml

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

相关文章

文件上传总结

一、原理 通过界面上的上传功能上传了一个可执行的脚本文件&#xff0c;而WEB端的系统并未对其进行检测或者检测的逻辑做的不够好&#xff0c;使得恶意用户可以通过文件中上传的一句话木马获得操控权 二、绕过方法 1>前端绕过 1.删除前端校验函数 checkFile() 2.禁用js…

大数据平台之HBase

HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;是Apache Hadoop生态系统的重要组成部分。它特别适合大规模结构化和半结构化数据的存储和检索&#xff0c;能够处理实时读写和批处理工作负载。以下是对HBase的详细介绍。 1. 核心概念 1.1 表&#x…

打造一篇完美的【数学建模竞赛论文】:从准备到撰写的全面指南

目录 一、赛前准备 1.1 报名与纪律要求 1.2 MD5码上传 1.3 竞赛准备 1.4 时间分配 二、论文格式规范 2.1 摘要 2.2 参考文献 2.3 排版要求 三、建模过程与方法 3.1 问题分析与模型假设 3.2 模型构建与求解 3.3 结果分析与检验 四、论文撰写技巧 4.1 论文结构 4…

Godot入门 07 世界构建2.0

添加基础节点Node&#xff0c;重命名为Coins&#xff0c;整理场景树&#xff0c;拖动Coin到Coins节点下。 添加基础节点Node&#xff0c;重命名为Platforms&#xff0c;整理场景树&#xff0c;拖动Platform到Platforms节点下。 添加游戏背景 设置当前图层名称为Mid 添加图层元…

飞牛爬虫FlyBullSpider 一款简单方便强大的爬虫,限时免费 特别适合小白!用它爬下Boss的2024年7月底Java岗位,分析一下程序员就业市场行情

一、下载安装FlyBullSpider 暂时支持Window,现在只在Win11上做过测试 1 百度 点击百度网盘 下载 链接&#xff1a;https://pan.baidu.com/s/1gSLKYuezaZgd8iqrXhk8Kg 提取码&#xff1a;Fly6 2 csdn https://download.csdn.net/download/fencer911/89584687 二、体验初…

vue3 vxe-table 点击行,不显示选中状态,加上设置isCurrent: true就可以设置选中行的状态。

1、上个图&#xff0c;要实现这样的&#xff1a; Vxe Table v4.6 官方文档 2、使用 row-config.isCurrent 显示高亮行&#xff0c;当前行是唯一的&#xff1b;用户操作点击选项时会触发事件 current-change <template><div><p><vxe-button click"sel…

C++入门基础(超详细) 需:C语言基础

1.C的发展史 大致了解一下 C的起源可以追溯到1979年&#xff0c;当时BjarneStroustrup(本贾尼斯特劳斯特卢普&#xff0c;这个翻译的名字不 同的地方可能有差异)在贝尔实验室从事计算机科学和软件工程的研究工作。面对项目中复杂的软件开 发任务&#xff0c;特别是模拟和操作系…

Linux权限维持篇

目录 SSH后门 &#xff08;1&#xff09;软链接sshd &#xff08;2&#xff09;SSH Key 生成公私钥 创建个authorized_keys文件来保存公钥 通过修改文件时间来隐藏authorized_keys &#xff08;3&#xff09;SSH Keylogger&#xff08;记录日志&#xff09; Linux的PA…

【Go系列】Go的UI框架Fyne

前言 总有人说Go语言是一门后端编程语言。 Go虽然能够很好地处理后端开发&#xff0c;但是者不代表它没有UI库&#xff0c;不能做GUI&#xff0c;我们一起来看看Go怎么来画UI吧。 正文 Go语言由于其简洁的语法、高效的性能和跨平台的编译能力&#xff0c;非常适合用于开发GUI…

MICA:面向复杂嵌入式系统的混合关键性部署框架

背景 在嵌入式场景中&#xff0c;虽然 Linux 已经得到了广泛应用&#xff0c;但并不能覆盖所有需求&#xff0c;例如高实时、高可靠、高安全的场合。这些场合往往是实时操作系统的用武之地。有些应用场景既需要 Linux 的管理能力、丰富的生态&#xff0c;又需要实时操作系统的高…

vue中scoped详解以及样式穿透>>>、/deep/、::v-deep

1、scoped scoped属性用于限制样式仅应用于当前组件。当一个style标签拥有scoped属性时&#xff0c;它的CSS样式就只能作用于当前的组件&#xff0c;通过该属性&#xff0c;可以使得组件之间的样式不互相污染。 原理&#xff1a;当样式中加了scoped属性时候&#xff0c;编译的…

数据库解析一维和二维简易JSON,

项目还在使用Oracle11&#xff0c;不支持后续官方的json解析方式&#xff0c; 在 前年、去年、今年 接连 遇到json解析问题后&#xff08;其实是公司的轮子效率太慢&#xff0c;太复杂&#xff0c;决定自己造个轮子&#xff0c;看看到底为什么慢&#xff0c;是不是真的很复杂&a…

【最新】cudnn安装教程

最近换了新电脑需要重新安装cuda和cudnn&#xff0c;发现现在cudnn的安装比以前方便多了&#xff0c;直接在官网下载exe安装包一键运行即可。安装的时候注意cuda和cudnn的对应关系即可&#xff1a;【最新】cuda和cudnn和显卡驱动的对应关系-CSDN博客 访问cudnn下载链接cuDNN 9…

Git 基础 GitHub【学习笔记】

一、Git 优势 大部分操作在本地完成&#xff0c;不需要联网完整性保证尽可能添加数据而不是删除或修改数据分支操作非常快捷流畅与 Linux 命令全面兼容 二、Git 程序安装 https://git-scm.com 三、Git 结构 #mermaid-svg-9Go6R1leWXWrDCqn {font-family:"trebuchet ms&quo…

运维锅总详解NFS

NFS是什么&#xff1f;如何对NFS进行部署及优化&#xff1f;NFS工作流程是什么&#xff1f;NFS的性能及优缺点是什么&#xff1f;NFS发展历史又是怎样的&#xff1f;希望本文能帮您解答这些疑惑&#xff01; 一、NFS简介 NFS (Network File System) 是由 Sun Microsystems 在…

论文精读(保姆级解析)—— Flash Diffusion

0 前言 今天分析的论文是《Flash Diffusion: Accelerating Any Conditional Diffusion Model for Few Steps Image Generation》。该论文发表在2024年&#xff0c;目前已开源在arxiv上&#xff0c;主要提出了一种高效、快速且多功能的蒸馏方法&#xff0c;用于加速预训练扩散模…

[C++][STL源码剖析] 详解AVL树的实现

目录 1.概念 2.实现 2.1 初始化 2.2 插入 2.2.1 旋转&#xff08;重点&#xff09; 左单旋 右单旋 双旋 2.❗ 双旋后&#xff0c;对平衡因子的处理 2.3 判断测试 完整代码&#xff1a; 拓展&#xff1a;删除 1.概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但…

遇到Websocket就不会测了?别慌,学会这个Jmeter插件轻松解决....

websocket 是一种双向通信协议&#xff0c;在建立连接后&#xff0c;websocket服务端和客户端都能主动向对方发送或者接收数据&#xff0c;而在http协议中&#xff0c;一个request只能有一个response&#xff0c;而且这个response也是被动的&#xff0c;不能主动发起。 websoc…

【研路导航】保研英语面试高分攻略,助你一路过关斩将

面试攻略之 千锤百炼英语口语 写在前面 在保研面试中&#xff0c;英语口语往往是让许多同学感到头疼的一部分。如何在面试中展现出自信和流利的英语表达能力&#xff0c;是我们今天要探讨的主题。以下是一些有效的英语口语练习方法和常见题型解析&#xff0c;帮助你在保研面试…

LoRA:低秩自适应

LoRA:低秩自适应 本章节是对轻松上手微调大语言模型——QLORA篇中提到的LoRA的原理解释。 背后动机 现今模型的参数量变得越来越大&#xff0c;对预训练模型进行全微调变得越来越不可行。为了解决这个问题有了LoRA&#xff08;Low-Rank Adaption&#xff09;的诞生。将可训练…