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文件

976255-da1756f5226a24ea.png
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函数的实现

976255-f9b08e30395186b6.png
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在内存中地址的偏移位置
      976255-c3675ccec4260fae.png
      image.png
    • b、__OFFSETOFIVAR__的实现
      976255-f2c615b077e6658a.png
      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的源码

    976255-211dfdfebbc2882e.png
    objc4

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

    976255-356b9c0d94b36cc2.png
    objc_setProperty

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

    976255-5ece11a1a354207e.png
    realyySetProperty

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

    扫描二维码关注公众号,回复: 5197168 查看本文章
    976255-ddc44f11fa24eb8f.png
    copyWithZone

    976255-26f66272a6bc9989.png
    NSArrayClass

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

    976255-16c99e244f6f8750.png
    image.png

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

    976255-09880adff6012393.png
    image.png

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

    976255-37dba1b87ad28acb.png
    image.png

    976255-7cb5d48f505cae2e.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、开启汇编调试模式
976255-7f9abadfa31a317e.png
image.png
3、单步进入
976255-0dcc2fd211e00814.png
image.png
4、发现setName内部其实是调用了objc_setProperty_nonatomic函数
976255-6c8e09528abb8bef.png
image.png
5、objc_setProperty_nonatomic内部还是调用reallySetProperty函数,第四个参数是false,表明是被nonatomic修饰的,所以最终不会加锁。当然这个是objc4源码中所得,根据之前的经验,说明objc4源码只有参考意义,具体测试还是得运行时查看
976255-afbe553eed5af779.png
image.png
6、打上断点,进入objc_setProperty_nonatomic函数内部
976255-51a156ba707614f7.png
image.png
7、在控制台输入si,表明进入这个函数内部
976255-b4d321f1822da66f.png
image.png
8、继续回车,表示继承执行si命令
976255-849dc4f6fe6e2caf.png
image.png
9、一直回车到dyld_stub_binder
976255-1c6b13fa1df02b5a.png
image.png
10、在dyld_stub_binder,最后一行打上断点,并跳过
976255-158ad763e4378106.png
image.png
11、再一次回车进入objc_setProperty_atomic
976255-88dee5c5642dd6e4.png
image.png
12、可以发现确实是利用os_unfair_lock进行加锁的
976255-530e0782e77e10fd.png
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函数看实现
976255-54c41a5c05523bf6.png
objc_storeWeak

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

976255-e64bbae82ff40dda.png
加锁
976255-c41fa5e004578db1.png
解锁

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
976255-a46bba0c58637cb1.png
image.png

976255-f240f16a38cc4010.png
image.png
5、跟踪汇编汇编代码查看, 不停地si,进入setObj函数内部
976255-f4ea7667a9d12c8e.png
image.png
6、再次不停地si、进入objc_storeWeak函数内部
976255-64e9624db45b79ff.png
image.png
7、翻阅整个汇编流程,发现内部还是调用了os_unfair_lock进行加锁
976255-8a2dd87c16c7f588.png
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、从运行时分析,汇编看不太懂,但是基本可以看出直接赋值的,具体等我学习汇编,再来更新
976255-fc1acfbc262ffd86.png
运行时

结论

  • 1、copyretainstrong,使用atomic会进行加锁处理,并且使用的os_unfair_lock, 使用nonatomic不会进行加锁处理
  • 2、weak无论使用的是nonatomicatomic都会利用os_unfair_lock进行加锁处理
  • 3、assign无论使用的是nonatomicatomic,都不会进行加锁处理
  • 4、使用copy关键字,会对对象进行一次copy操作,是深拷贝。如果可变对象,会被转换成不可变对象。

猜你喜欢

转载自blog.csdn.net/weixin_33670786/article/details/87418165