欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > iOS——strong和copy的底层实现

iOS——strong和copy的底层实现

2024/10/24 14:20:43 来源:https://blog.csdn.net/m0_73348697/article/details/141940577  浏览:    关键词:iOS——strong和copy的底层实现

copy和strong的区别

有如下代码:

#import "Person.h"@interface Person ()@property (nonatomic, strong) NSString *strStrong;
@property (nonatomic, copy) NSString *strCopy;@end@implementation Person- (void) go {NSMutableString *newStr = [NSMutableString stringWithString:@"newString"];self.strStrong = newStr;self.strCopy = newStr;[newStr setString:@"changString"];NSLog(@"newStr:%p %@", newStr, newStr);NSLog(@"strStrong:%p %@", self.strStrong, self.strStrong);NSLog(@"strCopy:%p %@", self.strCopy, self.strCopy);}@end

打印出的结果是:
请添加图片描述

可以看出来使用copy修饰的strCopy的值没有改变。
根据前面的学习我们知道:copy修饰的变量,对象地址不一致了,指针指向了一个新的内存区域(相当于深拷贝),导致新值(newString)修改时不会影响。
那么copy和strong这种区别的实现究竟是在哪里,下面我们一步一步解析:

属性使用点语法和_属性名的区别的原理

我们根据之前学的知识可知:一个属性使用self.的赋值是调用它的setter方法,而使用_属性名是直接赋值。下面我们使用clang分析两个语法的cpp:

//self.strStrong = newStr;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setStrStrong:"), (NSString *)newStr);
//_strStrong = newStr;
(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_strStrong)) = newStr;

第一段代码是使用这个函数指针向对象 self 发送消息 setStrStrong:,并传递 newStr 作为参数。
而第二段是self + OBJC_IVAR_...(属性偏移值) = strongStr的内存地址,然后在内存中进行替换。

属性的setter方法的底层

实际上strong和copy的区别在于它们setter方法的底层逻辑不同,我们先来看strStrong的setter方法:

static void _I_Person_setStrStrong_(Person * self, SEL _cmd, NSString *strStrong) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_strStrong)) = strStrong; }

这里是通过指针偏移后,将变量指针指向新的地址。

而strCopy的setter方法:

static void _I_Person_setStrCopy_(Person * self, SEL _cmd, NSString *strCopy) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _strCopy), (id)strCopy, 0, 1); }

这里的(id)strCopy就是我们要给strCopy赋的新值newStr。因为_I_Person_setStrCopy_(Person * self, SEL _cmd, NSString *strCopy)函数传入的参数中的NSString *strCopy就是newStr。

与strStrong不同的是,strCopy的setter方法中多了一个objc_setProperty,它们出现这样的区别的代码就在这里。

objc_setProperty

objc_setProperty 函数是在 Objective-C 中用于实现属性设置操作的一个函数。它负责处理属性的内存管理策略,如 copy、retain(对应于 strong)、nonatomic、atomic 等。

/*self: 调用该方法的对象。
_cmd: 方法的选择子(selector),即当前方法的名字。
offset: 属性在对象中的偏移量。
newValue: 要设置的新值。
atomic: 是否为原子操作。
shouldCopy: 是否需要复制新值。*/
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{//#define MUTABLE_COPY 2bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);bool mutableCopy = (shouldCopy == MUTABLE_COPY);reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

为什么copy修饰的变量set方法是调用objc_setProperty函数,而strong修饰却没有呢?因为:

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}

strong 修饰符要求在设置属性时,对传入的对象增加一个引用计数,以确保对象在属性持有期间不会被释放。这个操作比较简单,可以直接通过 objc_storeStrong 函数来实现,因此不需要调用 objc_setProperty。编译器生成的 set 方法会直接使用 objc_storeStrong 来处理 strong 修饰的属性。

objc_setProperty_nonatomic_copy

接下来在objc4中搜索objc_setProperty_nonatomic_copy可以看到它的源码:

void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

可以看到实际上它里面调用了一个reallySetProperty方法:

reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{//如果offset为0,说明这是在设置对象的类(如isa指针),直接调用object_setClass来设置类,并返回。if (offset == 0) {object_setClass(self, newValue);return;}id oldValue;//slot指向属性在对象中的存储位置。id *slot = (id*) ((char*)self + offset);if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {//检查 newValue 是否与当前值相同,如果相同则返回;如果不同,调用 objc_retain 来保留新值。if (*slot == newValue) return;newValue = objc_retain(newValue);}if (!atomic) {oldValue = *slot;*slot = newValue;} else {spinlock_t& slotlock = PropertyLocks.get()[slot];slotlock.lock();oldValue = *slot;*slot = newValue;        slotlock.unlock();}objc_release(oldValue);
}

根据这段代码可知,使用copy时,底层会调用copyWithZone;而使用mutableCopy时,底层会调用mutableCopyWithZone;两个都不是时,会增加引用计数,确保对象被正确持有。
这里根据我们上面的例子可知,我们的newStr是NSMutableString类型的。而且根据上面的各个方法的结果可知,copy为1,mutableCopy为0。因此会进入newValue = [newValue copyWithZone:nil]; 这一行,在这一行中,调用了newValue(NSMutableString)的copyWithZone,但是在NSMutableString中并没有找到copyWithZone的方法,向上找到了父类中的copyWithZone方法。
我们通过GUNstep找到copyWithZone和mutableCopyWithZone的具体实现:

- (id) copyWithZone: (NSZone*)zone
{/** 默认实现不应简单地保留(retain)...字符串可能已经在初始化时设置了 freeWhenDone==NO 并且不拥有其字符数据... * 因此创建它的代码在处理完原始字符串后可能会销毁该内存... * 这样会导致副本指向无效的数据指针。 所以我们总是完全复制。*/return [[NSStringClass allocWithZone: zone] initWithString: self];
}- (id) mutableCopyWithZone: (NSZone*)zone
{return [[GSMutableStringClass allocWithZone: zone] initWithString: self];
}

我们再查看allocWithZone 的内部:
请添加图片描述

NSAllocateObject方法

我们发现在allocWithZone中调用了NSAllocateObject方法

inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{id	new;#ifdef OBJC_CAP_ARCif ((new = class_createInstance(aClass, extraBytes)) != nil){AADD(aClass, new);}
#elseint	size;NSCAssert((!class_isMetaClass(aClass)), @"Bad class for new object");size = class_getInstanceSize(aClass) + extraBytes + sizeof(struct obj_layout);//如果 zone 为 0,则使用默认的内存分配区域。if (zone == 0){zone = NSDefaultMallocZone();}//计算分配对象所需的大小。new = NSZoneMalloc(zone, size);//分配内存。if (new != nil){memset (new, 0, size);new = (id)&((obj)new)[1];// 将新的内存空间设置为aClass的类型object_setClass(new, aClass);AADD(aClass, new);}/* Don't bother doing this in a thread-safe way, because the cost of locking* will be a lot more than the cost of doing the same call in two threads.* The returned selector will persist and the runtime will ensure that both* calls return the same selector, so we don't need to bother doing it* ourselves.*///初始化内存,设置类,并进行附加操作。if (0 == cxx_construct){cxx_construct = sel_registerName(".cxx_construct");cxx_destruct = sel_registerName(".cxx_destruct");}callCXXConstructors(aClass, new);
#endifreturn new;
}

到这就可以得出结论了,NSMutablString的Copy协议是创建了新的内存空间,进行了内容拷贝,通俗可以理解为进行了深拷贝。

依次再使用别的拷贝模式的深浅拷贝关系:

请添加图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com