copy、weak、retain、assign、strong的实现来看nonatomic、atomic

0、主要解决以下几个问题

  • 1、atomic究竟是对copyweakretainassignstrong中哪个修饰字进行了"安全"赋值
  • 2、为什么copy修饰了可变的对象,如NSMutableArrayNSMutableDictionaryNSMutableString,最终都会变成不可变对象?
  • 3、atomic究竟使用了哪个线程锁来保证线程安全?
  • 4、为什么网上有不少文章说的是,使用了atomic其实是编译器是在settergetter使用了@ synchronized?希望有人可以评论指点一下

1、新建WTPerson的类

WTPerson.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface WTPerson : NSObject

@property (nonatomic, strong) NSMutableArray *strongArray;

@property (nonatomic, copy) NSMutableArray *cpyArray;

@property (nonatomic, assign) NSUInteger assignObj;

@property (nonatomic, weak) id idObj;

@property (nonatomic, retain) NSObject *retainObj;

- (void)initVar;

@end

NS_ASSUME_NONNULL_END

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson
- (void)initVar
{
    self.strongArray = [NSMutableArray array];
    self.cpyArray = [NSMutableArray array];
    self.assignObj = 10;
    self.idObj = (id)[[NSObject alloc] init];
    self.retainObj = [[NSObject alloc] init];
}
@end

2、在相对应项目路径中,以下命令编译成对应的WTPerson.cpp文件

WTPerson.cpp

shell命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 WTPerson.m

3、WTPerson.cpp文件的源代码,找出成员变量的Setter函数的实现

image.png
1、strongassignweak
  • 1、其中被strongassignweak修饰的属性的setter方法是一样的
    通过 (char *)self + OBJC_IVAR_$WTPERSON$_XXX = xxx; 来赋值的。以strong为例,设置值是通过 (char *)self + OBJC_IVAR_$_WTPerson$_strongArray;来赋值的
    • a、OBJC_IVAR_$_WTPerson$_strongArray在cpp在这样定义的,返回值是strongArray这个成员变量在结构体WTPerson在内存中地址的偏移位置
      image.png
    • b、__OFFSETOFIVAR__的实现
      image.png
    • c、结论就是,strongassignweak修饰的属性,都是通过 self,也就是结构体WTPerson在内存中的地址加上属性的偏移量,得到属性的内存地址,然后直接把地址对应的值修改为新值。
2、copyretain
  • 1、其中被copyretain修饰的属性的setter方法是一样的, 内部其实调用了objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WTPerson, _xxx), (id)xxx, 0, 1)函数,以案例中copy修饰的cpyArray为例,调用了objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WTPerson, _cpyArray), (id)cpyArray, 0, 1)

  • 2、https://opensource.apple.com/release/macos-10141.html在苹果官方中下载objc4的源码

    objc4

  • 3、搜索找到objc_setProperty函数的实现, 最终调用了reallySetProperty函数

    objc_setProperty

  • 4、realyySetProperty函数,并且参数copytrue, 所以新的数组调用了copyWithZone:nil函数

    realyySetProperty

  • 5、参考GNU的实现,参考NSArraycopyWithZone的实现,发现调用NSArrayClass allocWithZone:方法,而NSArrayClass其实就是NSArray,并且返回一个不可变的数组,并且地址值是全新的。结论就是使用copy修饰之后,可变数组最终变成不可变数组的原因。

    copyWithZone

    NSArrayClass

  • 6、再回到realyySetProperty函数的实现,发现这里判断是否使用atomic修饰字,如果使用aotmic对赋值进行加锁

    image.png

  • 7、点击了spinlock_t的实现之后,发现内部其实是使用os_unfair_lock线程锁

    image.png

  • 8、下载了https://opensource.apple.com/release/os-x-1010.html的objc4的源代码,发现使用的是OSSpinLock的锁

    image.png

    image.png

3、所有修饰字的getter方法的实现都是通过(char *)self + OBJC_IVAR_$_WTPerson$_xxxx) , 如案例中strongArray,就是通过(char *)self + OBJC_IVAR_$_WTPerson$_strongArray),就是通过结构体WTPerson内存地址加上strongArray的偏移量,来获取对应内存地址的值。

4、测试atomicnonatomic开启6条线程,for循环10000次,各自时间是多少

测试代码

- (void)test
{
    TICK;
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.name = @"张三1";
            self.name;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.name = @"张三1";
            self.name;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.name = @"张三1";
            self.name;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.name = @"张三1";
            self.name;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.name = @"张三1";
            self.name;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.name = @"张三1";
            self.name;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        TOCK;
    });
    
    
}

结论:通过以下结果,使用atomic时间确实相对长一点,使用nonatomic时间相对短一点。按照以上文章所写,理论上atomic、strong是不加锁的,为什么使用时间长呢?希望有热心网友评论一下

12.481426 @property (atomic, copy) NSString *name;
11.998869 @property (atomic, copy) NSString *name;
12.355589 @property (atomic, copy) NSString *name;

1.999961 @property (nonatomic, copy) NSString *name;
2.019006 @property (nonatomic, copy) NSString *name;
2.015141 @property (nonatomic, copy) NSString *name;

7.278346  @property (atomic, strong) NSString *name;
6.806363  @property (atomic, strong) NSString *name;
6.928921  @property (atomic, strong) NSString *name;

0.425550 @property (nonatomic, strong) NSString *name;
0.442165 @property (nonatomic, strong) NSString *name;
0.547891 @property (nonatomic, strong) NSString *name;

20190120 更新

1、运行时分析

1、对name进行赋值

测试代码

@property (nonatomic, strong) NSString *name;
- (void)test
{
    self.name = @"张三1";  
}
2、开启汇编调试模式
image.png
3、单步进入
image.png
4、发现setName内部其实是调用了objc_setProperty_nonatomic函数
image.png
5、objc_setProperty_nonatomic内部还是调用reallySetProperty函数,第四个参数是false,表明是被nonatomic修饰的,所以最终不会加锁。当然这个是objc4源码中所得,根据之前的经验,说明objc4源码只有参考意义,具体测试还是得运行时查看
image.png
6、打上断点,进入objc_setProperty_nonatomic函数内部
image.png
7、在控制台输入si,表明进入这个函数内部
image.png
8、继续回车,表示继承执行si命令
image.png
9、一直回车到dyld_stub_binder
image.png
10、在dyld_stub_binder,最后一行打上断点,并跳过
image.png
11、再一次回车进入objc_setProperty_atomic
image.png
12、可以发现确实是利用os_unfair_lock进行加锁的
image.png
13、分别测试了
  • 1、atomic, strong
objc_setProperty_atomic
  • 2、atomic, copy
objc_setProperty_atomic_copy
  • 3、nonatomic, copy
objc_setProperty_nonatomic_copy
  • 4、nonatomic, retain
objc_setProperty_nonatomic
  • 5、atomic, retain
objc_setProperty_atomic
  • 6、nonatomic, weak
objc_storeWeak
  • 7、atomic, weak
objc_storeWeak
  • 8、nonatomic, assign
没有其他方法
  • 9、atomic, assign
没有其他方法

2、weak

1、 、使用了weak修饰词,发现最终调用了objc_storeWeak,内部其实是调用storeWeak函数,没有发现类似atomic的参数,下面进入storeWeak函数看实现
objc_storeWeak

2、发现函数实现,无条件地加了锁。所以猜测使用weak修饰字的属性,无论是使用nonatomicatomic,都是加了锁的

加锁
解锁

3、测试了一下代码结果

通过以下结果, 得出的结论就是使用weak修饰字的属性,无论是使用nonatomicatomic,都是加了锁的

// 代码执行时间
#define TICK   NSDate *startTime = [NSDate date];
#define TOCK   NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])
- (void)test
{
    
    TICK;
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.idObj = nil;
            self.idObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.idObj = nil;
            self.idObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.idObj = nil;
            self.idObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.idObj = nil;
            self.idObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.idObj = nil;
            self.idObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.idObj = nil;
            self.idObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        TOCK;
    });
    
    
}

结果

7.224174 @property (atomic, weak) id idObj;
7.078933 @property (atomic, weak) id idObj;
6.948409 @property (atomic, weak) id idObj;

7.192473 @property (nonatomic, weak) id idObj;
7.071997 @property (nonatomic, weak) id idObj;
7.238427 @property (nonatomic, weak) id idObj;
4、发现SideTable的锁,是spinlock_t, 也就是os_unfair_lock
image.png

image.png
5、跟踪汇编汇编代码查看, 不停地si,进入setObj函数内部
image.png
6、再次不停地si、进入objc_storeWeak函数内部
image.png
7、翻阅整个汇编流程,发现内部还是调用了os_unfair_lock进行加锁
image.png

3、assign

1、测试结果表明,得出的结论就是使用assign修饰字的属性,无论是使用nonatomicatomic,都不会加锁的

测试代码

// 代码执行时间
#define TICK   NSDate *startTime = [NSDate date];
#define TOCK   NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])
- (void)test
{
    
    TICK;
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.assignObj = 10;
            self.assignObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.assignObj = 10;
            self.assignObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.assignObj = 10;
            self.assignObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.assignObj = 10;
            self.assignObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.assignObj = 10;
            self.assignObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        dispatch_group_enter(group);
        for (NSUInteger i = 0; i < 10000000;  i++)
        {
            self.assignObj = 10;
            self.assignObj;
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        TOCK;
    });
    
    
}

结果

1.434344 @property (atomic, assign) NSUInteger assignObj;
1.561307 @property (atomic, assign) NSUInteger assignObj;
1.420304 @property (atomic, assign) NSUInteger assignObj;

1.112815 @property (nonatomic, weak) id idObj;
1.116036 @property (nonatomic, weak) id idObj;
1.458600 @property (nonatomic, weak) id idObj;
2、从运行时分析,汇编看不太懂,但是基本可以看出直接赋值的,具体等我学习汇编,再来更新
运行时

结论

  • 1、copyretainstrong,使用atomic会进行加锁处理,并且使用的os_unfair_lock, 使用nonatomic不会进行加锁处理
  • 2、weak无论使用的是nonatomicatomic都会利用os_unfair_lock进行加锁处理
  • 3、assign无论使用的是nonatomicatomic,都不会进行加锁处理
  • 4、使用copy关键字,会对对象进行一次copy操作,是深拷贝。如果可变对象,会被转换成不可变对象。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容