/
SafeObject.m
147 lines (110 loc) · 3.72 KB
/
SafeObject.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SafeObject
//
// SafeObject/SafeObject.m
//
// Copyright (c) 2013 Stanislaw Pankevich
// Released under the MIT license
#import "SafeObject.h"
#import <objc/runtime.h>
@interface SafeObject () {
dispatch_queue_t _isolationQueue;
NSMutableDictionary *_properties;
}
- (NSMutableDictionary *)properties;
@end
static const void * const SafeObjectKey = &SafeObjectKey;
@implementation SafeObject
-(id)init {
self = [super init];
if (self == nil) return nil;
NSString *queueName = [NSString stringWithFormat:@"com.%@.isolationqueue", NSStringFromClass([self class])];
_isolationQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_set_specific(_isolationQueue, SafeObjectKey, (__bridge void *)(_isolationQueue), NULL);
_properties = [NSMutableDictionary new];
return self;
}
- (void)dealloc {
_properties = nil;
_isolationQueue = nil;
}
- (NSMutableDictionary *)properties {
return _properties;
}
#pragma mark
#pragma mark Dynamic properties
static id propertyIMP(id self, SEL _cmd) {
__block id value;
[self readAccess:^(id object) {
value = [[self properties] valueForKey:NSStringFromSelector(_cmd)];
}];
return value;
}
static void setPropertyIMP(id self, SEL _cmd, id aValue) {
id value = [aValue copy];
NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy];
// delete "set" and ":" and lowercase first letter
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
NSString *firstChar = [key substringToIndex:1];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]];
[self writeAccess:^(id object) {
id oldValue = [self valueForKey:key];
if ([oldValue isEqual:value] == NO) {
[self willChangeValueForKey:key];
[[self properties] setValue:value forKey:key];
[self didChangeValueForKey:key];
}
}];
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
NSMutableString *key = [NSStringFromSelector(aSEL) mutableCopy];
if ([key hasPrefix:@"set"] && [key hasSuffix:@":"]) {
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
NSString *firstChar = [key substringToIndex:1];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]];
if ([[[self class] propertyKeys] containsObject:key]) {
class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v@:@");
return YES;
}
} else {
if ([[[self class] propertyKeys] containsObject:key]) {
class_addMethod([self class], aSEL,(IMP)propertyIMP, "@@:");
return YES;
}
}
return [super resolveInstanceMethod:aSEL];
}
#pragma mark
#pragma mark Public API: Transactional access
- (void)readAccess:(void (^)(id))accessBlock {
if (dispatch_get_specific(SafeObjectKey) == (__bridge void *)(_isolationQueue)) {
accessBlock(self);
}
else {
dispatch_sync(_isolationQueue, ^{
accessBlock(self);
});
}
}
- (void)readWriteAccess:(void(^)(id))accessBlock {
if (dispatch_get_specific(SafeObjectKey) == (__bridge void *)(_isolationQueue)) {
accessBlock(self);
}
else {
dispatch_barrier_sync(_isolationQueue, ^{
accessBlock(self);
});
}
}
- (void)writeAccess:(void(^)(id))accessBlock {
if (dispatch_get_specific(SafeObjectKey) == (__bridge void *)(_isolationQueue)) {
accessBlock(self);
}
else {
dispatch_barrier_async(_isolationQueue, ^{
accessBlock(self);
});
}
}
@end