0、主要解决以下几个问题
- 1、
atomic究竟是对copy、weak、retain、assign、strong中哪个修饰字进行了"安全"赋值 - 2、为什么
copy修饰了可变的对象,如NSMutableArray、NSMutableDictionary、NSMutableString,最终都会变成不可变对象? - 3、
atomic究竟使用了哪个线程锁来保证线程安全? - 4、为什么网上有不少文章说的是,使用了
atomic其实是编译器是在setter和getter使用了@ 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文件

shell命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 WTPerson.m
3、WTPerson.cpp文件的源代码,找出成员变量的Setter函数的实现

1、strong、assign、weak
- 1、其中被
strong、assign、weak修饰的属性的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、结论就是,
strong、assign、weak修饰的属性,都是通过self,也就是结构体WTPerson在内存中的地址加上属性的偏移量,得到属性的内存地址,然后直接把地址对应的值修改为新值。
- a、
2、copy、retain
1、其中被
copy、retain修饰的属性的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函数,并且参数copy为true, 所以新的数组调用了copyWithZone:nil函数
realyySetProperty -
5、参考
GNU的实现,参考NSArray的copyWithZone的实现,发现调用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、测试atomic、nonatomic开启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、开启汇编调试模式

3、单步进入

4、发现setName内部其实是调用了objc_setProperty_nonatomic函数

5、objc_setProperty_nonatomic内部还是调用reallySetProperty函数,第四个参数是false,表明是被nonatomic修饰的,所以最终不会加锁。当然这个是objc4源码中所得,根据之前的经验,说明objc4源码只有参考意义,具体测试还是得运行时查看

6、打上断点,进入objc_setProperty_nonatomic函数内部

7、在控制台输入si,表明进入这个函数内部

8、继续回车,表示继承执行si命令

9、一直回车到dyld_stub_binder

10、在dyld_stub_binder,最后一行打上断点,并跳过

11、再一次回车进入objc_setProperty_atomic

12、可以发现确实是利用os_unfair_lock进行加锁的

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函数看实现

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


3、测试了一下代码结果
通过以下结果, 得出的结论就是使用
weak修饰字的属性,无论是使用nonatomic、atomic,都是加了锁的
// 代码执行时间
#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


5、跟踪汇编汇编代码查看, 不停地si,进入setObj函数内部

6、再次不停地si、进入objc_storeWeak函数内部

7、翻阅整个汇编流程,发现内部还是调用了os_unfair_lock进行加锁

3、assign
1、测试结果表明,得出的结论就是使用assign修饰字的属性,无论是使用nonatomic、atomic,都不会加锁的
测试代码
// 代码执行时间
#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、
copy、retain、strong,使用atomic会进行加锁处理,并且使用的os_unfair_lock, 使用nonatomic不会进行加锁处理 - 2、
weak无论使用的是nonatomic、atomic都会利用os_unfair_lock进行加锁处理 - 3、
assign无论使用的是nonatomic、atomic,都不会进行加锁处理 - 4、使用
copy关键字,会对对象进行一次copy操作,是深拷贝。如果可变对象,会被转换成不可变对象。










