iOS 内存管理机制与原理
内存分区
内存一般分为五大区:栈区、堆区、常量区、全局区、代码区。如图
1.栈区
是由编译器自动分配并释放的,主要用来存储局部变量、函数的参数等,是一块连续的内存区域,遵循先进后出(FILO)原则。一般在运行时分配。它的分配由高地址空间向低地址空间分配。
优点:因为栈是由编译器自动分配并释放的,不会产生内存碎片,所以快速高效。
缺点:栈的内存大小有限制,数据不灵活。
例如:下图,创建两个变量,存放在栈区,地址是递减4。
2.堆区
堆区是由程序员手动管理。 主要用来存放动态分配的对象类型数据。是不连续的内存区域。在MRC环境下,由程序员手动释放,在ARC环境下,由编译器自动释放。一般在运行时分配。它的分配是从低地址空间向高地址空间分配。
优点:灵活方便,数据适应面广泛。
缺点:需手动管理,速度慢、容易产生内存碎片。
例如:下图,创建两个对象,存放在堆区,地址递增。
3.常量区
常量区存放的就是字符串常量和基本类型常量。在编译时分配,程序结束后回收
4.全局区/静态区
全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在.data
段,未初始化的全局变量和静态变量在相邻的.bss
区域,在编译时分配,程序结束后由系统释放。
5.代码区
代码区是在编译时分配,主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的
内存管理
内存管理方案
1.TaggedPointer
2013 年 9 月苹果推出了首个采用 64 位架构的 A7 双核处理器的手机 iPhone5s,为了改进从 32 位 CPU
迁移到 64 位 的内存浪费和效率问题,在 64 位 环境下,苹果工程师提出了 Tagged Pointer
的概念。采用这一机制,系统会对 NSString
、NSNumber
和 NSDate
等对象进行优化。建议大家看看 WWDC2020 这个视频的介绍。
Tagged Pointer 专门用来存储小对象,例如NSNumber,NSDate等,Tagged Pointer指针的值不再是单纯的地址了,而是真正的值,所以,实际上它也不再是一个对象了,它只是一个披着对象皮的普通变量而已,所以它的内存并不存储在堆区,也不需要malloc和free。这样在读取上有着3倍的效率,创建时候比以前快106倍。
由上图可以看出NSdate是NSTaggedPointer,
此外当字符串的长度为10个以内时,字符串的类型都是NSTaggedPointerString
类型,当超过10个时,字符串的类型才是__NSCFString
NSTaggedPointer标志位
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
上面这个方法我们看到,判断一个对象类型是否为NSTaggedPointerString
类型实际上是讲对象的地址与_OBJC_TAG_MASK
进行按位与操作,结果在跟_OBJC_TAG_MASK
进行对比,我们在看下_OBJC_TAG_MASK
的定义:
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
我们都知道一个对象地址为64位二进制,它表明如果64位数据中,最高位是1的话,则表明当前是一个tagged pointer
类型。
那么我们在看下上面打印出的地址,所有NSTaggedPointerString
地址都是0xd
开头,d转换为二进制1110
,根据上面的结论,我们看到首位为1表示为NSTaggedPointerString
类型。在这里得到验证。
注意
:TaggedPointer
类型在iOS和MacOS中标志位是不同的iOS为最高位而MacOS为最低位
对象类型
正常情况下一个对象的类型,是通过这个对象的ISA指针来判断的,那么对于NSTaggedPointer
类型我们如何通过地址判断对应数据是什么类型的呢?
在objc4-723之前,我们可以通过与判断TaggedPointer
标志位一样根据地址来判断,而类型的标志位就是对象地址的61-63位,比如对象地址为0xa
开头,那么转换成二进制位1010
,那么去掉最高位标志位后,剩余为010
,即10进制中的2。
接着我们看下runtime源码objc-internal.h中有关于标志位的定义如下:
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{// 60-bit payloadsOBJC_TAG_NSAtom = 0, OBJC_TAG_1 = 1, OBJC_TAG_NSString = 2, OBJC_TAG_NSNumber = 3, OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate = 6,// 60-bit reservedOBJC_TAG_RESERVED_7 = 7, // 52-bit payloadsOBJC_TAG_Photos_1 = 8,OBJC_TAG_Photos_2 = 9,OBJC_TAG_Photos_3 = 10,OBJC_TAG_Photos_4 = 11,OBJC_TAG_XPC_1 = 12,OBJC_TAG_XPC_2 = 13,OBJC_TAG_XPC_3 = 14,OBJC_TAG_XPC_4 = 15,OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, OBJC_TAG_First52BitPayload = 8, OBJC_TAG_Last52BitPayload = 263, OBJC_TAG_RESERVED_264 = 264
};
#if __has_feature(objc_fixed_enum) && !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
objc4-750之后
// Returns a pointer to the class's storage in the tagged class arrays.
// Assumes the tag is a valid basic tag.
static Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator>> _OBJC_TAG_INDEX_SHIFT)& _OBJC_TAG_INDEX_MASK);uintptr_t obfuscatedTag = tag ^ tagObfuscator;// Array index in objc_tag_classes includes the tagged bit itself
#if SUPPORT_MSB_TAGGED_POINTERS 高位优先return &objc_tag_classes[0x8 | obfuscatedTag];
#elsereturn &objc_tag_classes[(obfuscatedTag << 1) | 1];
#endif
}
classSlotForBasicTagIndex() 函数的主要功能就是根据指定索引 tag 从数组objc_tag_classes中获取类指针,而下标的计算方法发是根据外部传递的索引tag。比如字符串 tag = 2。当然这并不是简单的从数组中获取某条数据。
获取TaggedPointer的值
objc4-750之后源码:
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr)
{// assert(_objc_isTaggedPointer(ptr));uintptr_t value = _objc_decodeTaggedPointer(ptr);uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;if (basicTag == _OBJC_TAG_INDEX_MASK) {return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;} else {return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;}
}static inline intptr_t
_objc_getTaggedPointerSignedValue(const void * _Nullable ptr)
{// assert(_objc_isTaggedPointer(ptr));uintptr_t value = _objc_decodeTaggedPointer(ptr);uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;if (basicTag == _OBJC_TAG_INDEX_MASK) {return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;} else {return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;}
}
实例代码和结果
NSString *str1 = [NSString stringWithFormat:@"1"];NSString *str11 = [NSString stringWithFormat:@"11"];NSString *str2 = [NSString stringWithFormat:@"2"];NSString *str22 = [NSString stringWithFormat:@"22"];// 0x31 1 0x32 1uintptr_t value1 = objc_getTaggedPointerValue((__bridge void *)str1);uintptr_t value2 = objc_getTaggedPointerValue((__bridge void *)str2);uintptr_t value11 = objc_getTaggedPointerValue((__bridge void *)str11);uintptr_t value22 = objc_getTaggedPointerValue((__bridge void *)str22);// 以16进制形式输出NSLog(@"%lx", value1);NSLog(@"%lx", value11);NSLog(@"%lx", value2);NSLog(@"%lx", value22);
TaggedPointer[89535:3033433] 311 TaggedPointer[89535:3033433] 31312 TaggedPointer[89535:3033433] 321 TaggedPointer[89535:3033433] 32322
TaggedPoint对象是一个特殊的对象,不会涉及到引用计数retain
、release
等内存操作。对象的值就存在指针中,不过值通过了一份加密。
2.NONPOINTER_ISA
上面我们说了,对于一个对象的存储,苹果做了优化,那么对于ISA指针呢?
对象的isa指针,用来表明对象所属的类类型。
union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits;
#if defined(ISA_BITFIELD)struct {ISA_BITFIELD; // defined in isa.h};
#endif
};
结合下图
从图中可以看出,我们所谓的isa指针,最后实际上落脚于isa_t的联合类型。那么何为联合类型呢? 联合类型是C语言中的一种类型,是一种n选1的关系,联合的作用在于,用更少的空间,表示了更多的可能的类型,虽然这些类型是不能够共存的
。比如isa_t 中包含有cls
,bits
, struct
三个变量,它们的内存空间是重叠的。在实际使用时,仅能够使用它们中的一种,你把它当做cls,就不能当bits访问,你把它当bits,就不能用cls来访问。
对于isa_t
联合类型,主要包含了两个构造函数isa_t()
,isa_t(uintptr_t value)
和三个变量cls
,bits
,struct
,而uintptr_t
的定义为typedef unsigned long
。
当isa_t作为Class cls使用时,这符合了我们之前一贯的认知:isa是一个指向对象所属Class类型的指针。然而,仅让一个64位的指针表示一个类型,显然不划算。
因此,绝大多数情况下,苹果采用了优化的isa策略
,即,isa_t
类型并不等同而Class cls
, 而是struct
。
下面我们先来看下struct的结构体
// ISA_BITFIELD定义如下
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \uintptr_t has_sidetable_rc : 1; \uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
注意:
成员后面的:表明了该成员占用几个bit
而每个成员的意义如下表
nonopointer:表示是否对isa指针开启了指针优化,
0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。
has_assoc:关联对象标志位,0:没有,1:存在
has_cxx_dtor:该对象是否有c++或者Objc的析构器,如果有析构器函数,则需要做析构逻辑,如果没有,则可以更快的释放。
shiftcls:存储指针的值,开启指针优化的情况下,再arm64架构中有33位用来存储类指针,
magic:用于调试器判断当前对象是真的对象还是未初始化的空间。
weakly_referenced:标致对象是否指向或者曾经指向一个ARC的若变量,没有弱引用的对象可以更快的释放。
deallcating:标志对象是否正在释放内存
has_sidetable_rc:当前对象引用计数大于10的时,则需要借用变量存储进位
extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9,如果用于计数大于10则需要使用下面的has_sidetable_rc。
3.散列表、引用计数表
Sidetable主要包含spinlock,引用计数(存放extra_rc接收的另一半引用计数),弱引用表。
truct SideTable {spinlock_t slock;// 存放从extra_rc接收的那一半引用计数RefcountMap refcnts;// 弱引用表weak_table_t weak_table;SideTable() {memset(&weak_table, 0, sizeof(weak_table));}~SideTable() {_objc_fatal("Do not delete SideTable.");}void lock() { slock.lock(); }void unlock() { slock.unlock(); }void forceReset() { slock.forceReset(); }// Address-ordered lock discipline for a pair of side tables.template<HaveOld, HaveNew>static void lockTwo(SideTable *lock1, SideTable *lock2);template<HaveOld, HaveNew>static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
1.spinlock_t 加锁
spinlock_t 不是自旋锁,在底层代码查找的过程中,我们可以发现他是一把os_unfair_lock锁,在使用sidetable的时候,频繁的读取需要加锁,一张表无疑影响了效率,因此,我们采用stripedMap来分散压力,且stripedMap的数量是根据系统来确定的(真机模式下sidetable最多为8张,虚拟机等为64张).
// 上面 SideTables 的实现
static StripedMap<SideTable>& SideTables() {return SideTablesMap.get();
}
2.RefcountMap(引用计数表)
- RefcountMap本身从DenseMap得来
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
存放从extra_rc接收的那一半引用计数
if (variant == RRVariant::Full) {if (slowpath(transcribeToSideTable)) {// Copy the other half of the retain counts to the side table.// 将引用计数一半存在散列表中的方法sidetable_addExtraRC_nolock(RC_HALF);}if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {ASSERT(!transcribeToSideTable);ASSERT(!sideTableLocked);
}
接着,来看一下 sidetable_addExtraRC_nolock 方法:
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{ASSERT(isa.nonpointer);// 获取SideTables,也就是StripeMapSideTable& table = SideTables()[this];size_t& refcntStorage = table.refcnts[this];size_t oldRefcnt = refcntStorage;// isa-side bits should not be set hereASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;uintptr_t carry;size_t newRefcnt = addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);if (carry) {refcntStorage =SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);return true;}else {refcntStorage = newRefcnt;return false;}
}
3.WeakTable(弱引用表)
弱引用底层调用objc_initWeak:
id objc_initWeak(id *location, id newObj)
{if (!newObj) {*location = nil;return nil;}return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
}
storeWeak:
- 添加引用的时候调用storeWeak一共有五个参数,其中3个参数定义在了template模版参数中(HaveOld:weak指针是否指向一个弱引用;HavNew:weak指针是否需要指向一个新的引用;crashIfDeallocating表示被弱引用的对象是否正在析构)。
- weak_unregister_no_lock:清除原来弱引用表中的数据
- weak_register_no_lock:将weak的指针地址添加到对象的弱引用表
enum CrashIfDeallocating {DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};// HaveOld:weak指针是否指向一个弱引用
// HavNew:weak指针是否需要指向一个新的引用
template <HaveOld haveOld, HaveNew haveNew,enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{ASSERT(haveOld || haveNew);if (!haveNew) ASSERT(newObj == nil);Class previouslyInitializedClass = nil;id oldObj;SideTable *oldTable;SideTable *newTable;// Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:if (haveOld) {//如果有拿到旧表oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}if (haveNew) {//如果没有创建新表newTable = &SideTables()[newObj];} else {newTable = nil;}SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);if (haveOld && *location != oldObj) {//如果旧表不存在对应的objSideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);goto retry;}// Prevent a deadlock between the weak reference machinery// and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa.if (haveNew && newObj) {//有新表和新对象Class cls = newObj->getIsa();if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) //如果类没有初始化就重新初始化{SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);class_initialize(cls, (id)newObj);// If this class is finished with +initialize then we're good.// If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself)// then we may proceed but it will appear initializing and // not yet initialized to the check above.// Instead set previouslyInitializedClass to recognize it on retry.previouslyInitializedClass = cls;goto retry;}}// Clean up old value, if any.if (haveOld) {//如果指针曾经指向别的对象,就清除// 清除原来弱引用表中数据weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// Assign new value, if any.if (haveNew) {newObj = (objc_object *)// 将weak的指针地址添加到对象的弱引用表weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);// weak_register_no_lock returns nil if weak store should be rejected// Set is-weakly-referenced bit in refcount table.if (!newObj->isTaggedPointerOrNil()) {// 将对象曾经指向过弱引用的标识置为true,没有弱引用的释放更快newObj->setWeaklyReferenced_nolock();}// Do not set *location anywhere else. That would introduce a race.*location = (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);// This must be called without the locks held, as it can invoke// arbitrary code. In particular, even if _setWeaklyReferenced// is not implemented, resolveInstanceMethod: may be, and may// call back into the weak reference machinery.callSetWeaklyReferenced((id)newObj);return (id)newObj;
}
weak_entry_t:
struct weak_table_t {// 弱引用数组weak_entry_t *weak_entries;// 数组个数size_t num_entries;uintptr_t mask;uintptr_t max_hash_displacement;
};
weak_entries是一个哈希数组,一个对象可以被多个弱引用指针引用,因此,这里用数组的形式表示一个对象的多个弱引用;数组中存储的内容就是弱引用对象的指针地址。当对象的弱引用个数小于等于4时走静态存储(在weak_entry_t初始化的时候一并分配好),大于4走动态存储。
sidetables总结:
- sidetables可以理解为一个全局的hash数组,里面存储了sidetables类型的数据,其中长度为8或者64
- 一个obj(oc对象)对应了一个sideTable,但是一个SideTable,会对应多个obj,因为sidetabels的数量只有8或者64个,所以有很多obj会共用一个sidetable
- 在弱引用表中,key是对象的地址,value是weak指针地址的数组(weak_entry_t)
- weak_unregister_no_lock 和 weak_register_no_lock 中都是对 weak_entry_t 类型的数组进行操作
- _ _weak修饰对象(不会放入自动释放池),会调用objc_loadWeakRetained;使得引用计数加一,但仅是临时变量;被引用的对象不会增加他的引用计数。
ARC&MRC
Object-C提供了两种内存管理机制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),为Objective-C提供了内存的手动和自动管理。
MRC
基本思想:通过手动引用计数来进行对象的内存管理
涉及方法
- alloc/new/copy/mutableCopy:生成对象并自己持有,引用计数+1(从0变为1)
- retain :持有对象,使对象的引用计数加1
- release : 释放对象,使对象的引用计数减1
- retainCount : 获取当前对象的引用计数值
- autorelease : 当前对象会在autoreleasePool结束的时候,调用这个对象的release操作,进行引用计数减1
- dealloc : 在MRC中若调用dealloc,需要显示的调用[super dealloc],来释放父类的相关成员变量
autorelease
autorelease即“自动释放”,是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池来回收统一释放。自动释放池销毁的时候,池子里面所有的对象都会做一次release操作
那么,autorelease释放与简单的release释放有什么区别呢?
调用 autorelease 方法,就会把该对象放到离自己最近的自动释放池中(栈顶的释放池,多重自动释放池嵌套是以栈的形式存取的),即:使对象的持有权转移给了自动释放池(即注册到了自动释放池中),调用方拿到了对象,但这个对象还不被调用方所持有。当自动释放池销毁时,其中的所有的对象都会调用一次release操作。
本质上,区别在于autorelease 方法不会改变调用者的引用计数,它只是改变了对象释放时机,不再让程序员负责释放这个对象,而是交给自动释放池去处理 。
autorelease 方法相当于把调用者注册到 autoreleasepool 中,ARC环境下不能显式地调用 autorelease 方法和显式地创建 NSAutoreleasePool 对象,但可以使用@autoreleasepool { }块代替(并不代表块中所有内容都被注册到了自动释放池中)。
对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
RunLoop和AutoReleasePool是通过线程的方式一一对应的
在非手动添加Autorelease pool下,Autorelease对象是在当前runloop进入休眠等待前被释放的
当一个runloop在不停的循环工作,那么runloop每一次循环必定会经过BeforeWaiting(准备进入休眠):而去BeforeWaiting(准备进入休眠) 时会调用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush()释放旧的池并创建新池,那么这两个方法来销毁要释放的对象,所以我们根本不需要担心Autorelease的内存管理问题。
ARC
内存管理方案
iOS内存管理方案有:
- MRC和ARC
- Tagged Pointer:专门用来处理小对象,例如NSNumber、NSDate、小NSString等
- NONPOINTER_ISA :非指针类型的isa,主要是用来优化64位地址。在 64 位架构下,isa 指针是占 64 比特位的,实际上只有 30 多位就已经够用了,为了提高利用率,剩余的比特位存储了内存管理的相关数据内容。
- nonpointer: 表示是否对 isa 指针开启指针优化
- • 0: 纯 isa 指针
- • 1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等
- SideTables:散列表,在散列表中主要有两个表,分别是引用计数表、弱引用表。通过 SideTables()结构来实现的,SideTables()结构下,有很多 SideTable 的数据结构。 而 sideTable 当中包含了自旋锁,引用计数表,弱引用表。 SideTables()实际上是一个哈希表,通过对象的地址来计算该对象的引用计数在哪个 sideTable 中。
修饰符
当ARC有效时,id类型和对象类型必须附加所有权修饰符,一共有如下四种。
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
__strong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符。
__weak修饰符
弱引用表示并不持有对象,当所引用的对象销毁了,这个变量就自动设为nil。
可以利用__weak修饰符来解决循环引用问题。
__unsafe_unretained修饰符
__unsafe_unretained和__weak很像,唯一区别就是,__unsafe_unretained变量引用的对象再被销毁以后,不会被自动设置为nil,仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe,此时你再尝试通过变量访问这个对象的属性或方法就会crash。一旦对象释放,则会成为悬垂指针,程序崩溃,因此__unnsafe_unretained修饰符的变量一定要在赋值的对象存在的情况下使用。
__autoreleasing修饰符
ARC无效时使用autorelease,在ARC下__autoreleasing的使用:
@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];
}
相关文章:

iOS 内存管理机制与原理
内存分区 内存一般分为五大区:栈区、堆区、常量区、全局区、代码区。如图 1.栈区 是由编译器自动分配并释放的,主要用来存储局部变量、函数的参数等,是一块连续的内存区域,遵循先进后出(FILO)原则。一般在…...

Linux之父:连你自己都懒得解释,那这就是一堆垃圾!
不出意外,Linus又开喷了,这次的激情开麦,源自一部分没有做注释的合并请求:Linux6.3内核收到了一部分合并请求,但这部分合并完全没有注释。 如果你懒得解释为什么存在一个合并,那这个合并从本质上来说就是错…...

二战华为成功上岸,准备了小半年,要个27k应该也算不上很高吧~
先说下我基本情况,本科不是计算机专业,现在是学通信,然后做图像处理,可能面试官看我不是科班出身没有问太多计算机相关的问题,因为第一次找工作,华为的游戏专场又是最早开始的,就投递了…...

全国青少年电子信息智能创新大赛(复赛)python·模拟三卷,含答案解析
目录 一、编程题 答案解析: 文档下载打印: 老子搞不懂了,答案解析,还版权问题,有毛病是不。谁在瞎投诉啊。 全国青少年电子信息智能创新大赛(复赛)python模拟三卷 一、编程题 第一题:描述 输入某学生成绩,若成绩在 85分及以上,输出“A”,若成绩在 60分到 85分之间…...

服务网关选型指南
1、为服务网关选型需要考虑哪些因素? 功能需求:您需要考虑您的服务网关需要提供哪些功能,例如 API 管理、请求转发、负载均衡、安全认证等。您应该选择能够满足您的需求的服务网关。 可扩展性:您的服务网关需要能够扩展以支持未来…...

华为OD机试-查找充电设备组合-2022Q4 A卷-Py/Java/JS
某个充电站,可提供n个充电设备,每个充电设备均有对应的输出功率。任意个充电设备组合的输出功率总和,均构成功率集合P的1个元素。功率集合P的最优元素,表示最接近充电站最大输出功率P_max的元素 输入描述 输入为3行: 第1行为充电设…...

免费好用的oa系统有哪些?盘点这几款!
免费好用的oa系统有哪些?盘点这几款! 办公自动化(OA),英文Office Automation的缩写。它可以通过特定流程或特定环节与日常事务联系在一起,使公文在流转、审批、发布等方面提高效率,实现办公管理…...

光伏发电系统模拟及其发电预测开源python工具pvlib
1. 太阳辐照量模拟 pysolar是一个用于计算太阳位置和辐照量的Python库。它是基于python语言编写的,可以方便地在各种python项目中使用。pysolar主要用于计算太阳的位置、太阳高度角、太阳方位角、日出和日落时间等信息。这些信息可以用于太阳能电池板和太阳能集热器…...

精彩回顾 | 2023工赋Meetup—上海站
2023工赋Meetup—上海站 2023年4月2日下午,在上海数字长宁体验馆举办的“价值驱动的数字化转型技术专场”Meetup圆满落幕,本次活动由工赋开发者社区主办,上海市工业互联网协会指导,长宁区东虹桥发展办公室和积梦智能联合主办。 …...

[oeasy]python0132_[专业选修]utf-8_unicode_transformation_format_8_编码方式
utf-8 回忆上次内容 上次再次输出了大红心♥ 找到了红心对应的编码黑红梅方都对应有编码 原来的编码叫做 ascii️ \u这种新的编码方式叫unicode包括了 中日韩字符集等 各书写系统的字符集 但是有个问题 拜这个字在字节中应该是b"\x62\xdc"两个字节 该如何理解b&qu…...

DNS 的解析过程以及相关问题
文章目录DNS解析过程DNS 为什么用 UDP简单说下怎么实现 DNS 劫持谈谈你对域名缓存的了解DNS解析过程 浏览器首先看看自己浏览器缓存有没有对应的IP记录,同时还要查询一下主机本地文件里面有没有对应的记录,如果有记录就没必要进行后面的步骤了。 浏览器…...

个人情况-单词练习
目录 前言原文兴趣爱好特长专业习惯理想志向情境常用单词性别家庭成员正面性格前言 加油 原文 1.come from… 来自…… I come from Shanghai. 我来自上海/我是上海人。 2.born[bɔrn]adj.出生的 be born into + 家庭 出身……的家庭 George was born into a poor fami…...

python天狗吃月 青少年编程电子学会python编程等级考试一级真题解析2022年9月
目录 python天狗吃月 一、题目要求 编程实现 二、解题思路 1、图形分析...

JAVA做语言国际化
项目场景: 问题描述 提示:这里描述项目中遇到的问题: 例如:数据传输过程中数据不时出现丢失的情况,偶尔会丢失一部分数据 APP 中接收数据代码: Overridepublic void run() {bytes mmInStream.read(buff…...

面试题 16.19. 水域大小
题目链接 面试题 16.19. 水域大小 mid 题目描述 你有一个用于表示一片土地的整数矩阵 land,该矩阵中每个点的值代表对应地点的海拔高度。若值为 0 则表示水域。由垂直、水平或对角连接的水域为池塘。 池塘的大小是指相连接的水域的个数。 编写一个方法来计算矩阵…...

在vscode中切换分支,显示已经删除的远程分支
运行命令:修剪远程分支 git remote prune origin 然后远程的已经删除的分支就不见了。...

森林督查违法图斑内业报告高效制作实践技术
Python已成为最热门的编程语言之一,与arcpy、geopandas等行业软件包相结合,能极大程度地减轻森林督查违法图斑内业报告制作的工作量,显著提升工作效率。为了提升广大从业人员在森林违法图斑内业报告制作等方面的技能,内容主要包括…...

华为OD机试-日志限流-2022Q4 A卷-Py/Java/JS
某软件系统会在运行过程中持续产生日志,系统每天运行N单位时间,运行期间每单位时间产生的日志条数保行在数组 records中。records[i]表示第i单位时间内产生日志条数。 由于系统磁盘空间限制,每天可记录保存的日志总数上限为total条。 如果一天…...

ChatGPT能胜任高级程序员吗?
与开发人员信任的其他软件开发工具不同,AI工具在训练、构建、托管和使用方式等方面都存在一些独特的风险。 自2022年底ChatGPT发布以来,互联网上便充斥着对其几乎相同比例的支持和怀疑的论调。不管你是否喜欢它,AI正在逐步进入你的开发组织。…...

effective c++ item 25-29
item25:自定义swap函数 namespace std{template<typename T>void swap(T& a, T& b){T temp(a); // T要满足拷贝构造和拷贝赋值a b;b temp; } }1、Pimpl 2、自定义swap item26:尽可能延后变量的定义 case 1: temp j; for(int i 0; i < n; …...

MasterCAM实体旋转命令相关几个问题:曲线相交于边缘等
MasterCAM版本:2022 目的:通过旋转画杯子边缘主体 内外环直径分别是:56、60mm 命令:实体 - 旋转 问题: 一、处理实体期间错误parasolid(r) kernel 界面错误PK 错误代码:942-曲线相交于边缘 对应参数&a…...

p标签需要设置宽高吗?不用
Dusk: # 引用补丁,开头必须以 -javaagent: 开头,后面跟着补丁的绝对路径(可根据你实际的位置进行修改),注意路径一定要填写正确,且不能包含中文,否则会导致 IDEA 无法启动-javaagent:D:/ja-netfilter/ja-ne…...

Andorid 事件分发机制案例实操与解析
文章目录为什么要理解Android事件分发机制?滑动冲突类问题我们以什么开始?代码如下:activity xml 代码:Activity代码:item_user.xml代码修改后代码如下:Activity xmlactivity代码item_gift.xml问题出现了An…...

Git 版本控制/项目迭代
一、Git的作用/为什么要进行版本控制? 什么是项目迭代? 搞开发的时候我们不是一次性就做好平台的所有功能,而是先上线一个功能差不多的版本让用户用着,然后不断迭代、修改,上线新的版本,所以一个项目就会…...

智慧农业大数据项目建设方案
智慧农业大数据项目建设方案 目录 项目概述.................................... 6...

【数据结构专栏】动态扩容顺序栈详解
💌 博客内容:顺序栈的原理详解 😀 作 者:陈大大陈 🚀 个人简介:一个正在努力学技术的准前段,专注基础和实战分享 ,欢迎私信! 💖 欢迎大家:这…...

Linux命令·ifconfig
许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使…...

大器晚成我服刘邦,48岁才开始创业
读史使人明智,周末放下手机,静下心来读点人文历史。大器晚成我最佩服刘邦,48岁才开始创业 。在此之前,他是一个出身平凡的农民,早年曾多次失败和受挫。刘邦最后能够战胜项羽,常常让人觉得匪夷所思ÿ…...

AndroidStudio快捷键
动态演示:https://blog.csdn.net/weixin_67276852/article/details/124159843?spm1000.2115.3001.6382&utm_mediumdistribute.pc_feed_v2.none-task-blog-hot-11.pc_personrec&depth_1-utm_sourcedistribute.pc_feed_v2.none-task-blog-hot-11.pc_personre…...

机械硬盘的工作原理
每个磁盘的表面都有高速扫过的记录磁头。 每个磁盘上都覆盖着一层薄薄的微小的磁化金属粒。 数据以一种肉眼无法分辨的形式存在。很多组微小颗粒形成的磁化图案记录形成了数据。每一组,又称之为比特(bit)。 所有微粒都按照自身的磁性排列…...