Skip to content

Commit 3d1b367

Browse files
Added automatic PFObject subclass registration.
This will scan all loaded code bundles for classes which inherit from `PFObject`, and register them upon Parse initialization. Still have opt-in support for manual-only registration, though it shouldn't be necessary for most cases.
1 parent f02bd53 commit 3d1b367

File tree

8 files changed

+107
-34
lines changed

8 files changed

+107
-34
lines changed

Parse/Internal/Object/Subclassing/PFObjectSubclassingController.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,12 @@
1414

1515
@interface PFObjectSubclassingController : NSObject
1616

17-
///--------------------------------------
18-
/// @name Init
19-
///--------------------------------------
20-
21-
//TODO: (nlutsenko, richardross) Make it not terrible aka don't have singletons.
22-
+ (instancetype)defaultController;
23-
+ (void)clearDefaultController;
24-
2517
///--------------------------------------
2618
/// @name Registration
2719
///--------------------------------------
2820

21+
- (void)scanForUnregisteredSubclasses:(BOOL)shouldSubscribe;
22+
2923
- (Class<PFSubclassing>)subclassForParseClassName:(NSString *)parseClassName;
3024
- (void)registerSubclass:(Class<PFSubclassing>)kls;
3125
- (void)unregisterSubclass:(Class<PFSubclassing>)kls;

Parse/Internal/Object/Subclassing/PFObjectSubclassingController.m

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#import "PFAssert.h"
1515
#import "PFMacros.h"
1616
#import "PFObject.h"
17+
#import "PFObject+Subclass.h"
1718
#import "PFObjectSubclassInfo.h"
1819
#import "PFPropertyInfo_Private.h"
1920
#import "PFPropertyInfo_Runtime.h"
@@ -91,17 +92,6 @@ - (instancetype)init {
9192
return self;
9293
}
9394

94-
+ (instancetype)defaultController {
95-
if (!defaultController_) {
96-
defaultController_ = [[PFObjectSubclassingController alloc] init];
97-
}
98-
return defaultController_;
99-
}
100-
101-
+ (void)clearDefaultController {
102-
defaultController_ = nil;
103-
}
104-
10595
///--------------------------------------
10696
#pragma mark - Public
10797
///--------------------------------------
@@ -114,6 +104,33 @@ + (void)clearDefaultController {
114104
return result;
115105
}
116106

107+
- (void)scanForUnregisteredSubclasses:(BOOL)shouldSubscribe {
108+
// NOTE: Potential race-condition here - if another thread dynamically loads a bundle, we may end up accidentally
109+
// Skipping a bundle. Not entirely sure of the best solution to that here.
110+
if (shouldSubscribe) {
111+
@weakify(self);
112+
[[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
113+
object:nil
114+
queue:nil
115+
usingBlock:^(NSNotification *note) {
116+
@strongify(self);
117+
[self _registerSubclassesInBundle:note.object];
118+
}];
119+
}
120+
NSArray *bundles = [[NSBundle allFrameworks] arrayByAddingObjectsFromArray:[NSBundle allBundles]];
121+
for (NSBundle *bundle in bundles) {
122+
// Skip bundles that aren't loaded yet.
123+
if (!bundle.loaded || !bundle.executablePath) {
124+
continue;
125+
}
126+
// Filter out any system bundles
127+
if ([[bundle bundlePath] hasPrefix:@"/System/"] || [[bundle bundlePath] hasPrefix:@"/Library/"]) {
128+
continue;
129+
}
130+
[self _registerSubclassesInBundle:bundle];
131+
}
132+
}
133+
117134
- (void)registerSubclass:(Class<PFSubclassing>)kls {
118135
pf_sync_with_throw(_registeredSubclassesAccessQueue, ^{
119136
[self _rawRegisterSubclass:kls];
@@ -311,4 +328,35 @@ - (void)_rawRegisterSubclass:(Class)kls {
311328
_registeredSubclasses[[kls parseClassName]] = subclassInfo;
312329
}
313330

331+
- (void)_registerSubclassesInBundle:(NSBundle *)bundle {
332+
PFConsistencyAssert(bundle.loaded, @"Cannot register subclasses in a bundle that hasn't been loaded!");
333+
pf_sync_with_throw(_registeredSubclassesAccessQueue, ^{
334+
Class pfObjectClass = [PFObject class];
335+
unsigned bundleClassCount = 0;
336+
const char **classNames = objc_copyClassNamesForImage([[bundle executablePath] UTF8String], &bundleClassCount);
337+
for (unsigned i = 0; i < bundleClassCount; i++) {
338+
Class bundleClass = objc_getClass(classNames[i]);
339+
// For obvious reasons, don't register the PFObject class.
340+
if (bundleClass == pfObjectClass) {
341+
continue;
342+
}
343+
// NOTE: (richardross) Cannot use isSubclassOfClass here. Some classes may be part of a system bundle (even
344+
// though we attempt to filter those out) that may be an internal class which doesn't inherit from NSObject.
345+
// Scary, I know!
346+
for (Class kls = bundleClass; kls != nil; kls = class_getSuperclass(kls)) {
347+
if (kls == pfObjectClass) {
348+
// Do class_conformsToProtocol as late in the checking as possible, as its SUUUPER slow.
349+
// Behind the scenes this is a strcmp (lolwut?)
350+
if (class_conformsToProtocol(bundleClass, @protocol(PFSubclassing)) &&
351+
!class_conformsToProtocol(bundleClass, @protocol(PFSubclassingSkipRegistration))) {
352+
[self _rawRegisterSubclass:bundleClass];
353+
}
354+
break;
355+
}
356+
}
357+
}
358+
free(classNames);
359+
});
360+
}
361+
314362
@end

Parse/Internal/PFCoreDataProvider.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ NS_ASSUME_NONNULL_BEGIN
2424

2525
@end
2626

27+
@class PFObjectSubclassingController;
28+
29+
@protocol PFObjectSubclassingControllerProvider <NSObject>
30+
31+
@property (nonatomic, strong) PFObjectSubclassingController *objectSubclassingController;
32+
33+
@end
34+
2735
@class PFObjectBatchController;
2836

2937
@protocol PFObjectBatchController <NSObject>

Parse/Internal/PFCoreManager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ PFInstallationIdentifierStoreProvider>
4040
@interface PFCoreManager : NSObject
4141
<PFLocationManagerProvider,
4242
PFObjectControllerProvider,
43+
PFObjectSubclassingControllerProvider,
4344
PFObjectBatchController,
4445
PFObjectFilePersistenceControllerProvider,
4546
PFPinningObjectStoreProvider,

Parse/Internal/PFCoreManager.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ @implementation PFCoreManager
4848
@synthesize cloudCodeController = _cloudCodeController;
4949
@synthesize configController = _configController;
5050
@synthesize objectController = _objectController;
51+
@synthesize objectSubclassingController = _objectSubclassingController;
5152
@synthesize objectBatchController = _objectBatchController;
5253
@synthesize objectFilePersistenceController = _objectFilePersistenceController;
5354
@synthesize objectLocalIdStore = _objectLocalIdStore;
@@ -217,6 +218,28 @@ - (void)setObjectController:(PFObjectController *)controller {
217218
});
218219
}
219220

221+
///--------------------------------------
222+
#pragma mark - ObjectSubclassingController
223+
///--------------------------------------
224+
225+
- (PFObjectSubclassingController *)objectSubclassingController {
226+
__block PFObjectSubclassingController *controller = nil;
227+
dispatch_sync(_controllerAccessQueue, ^{
228+
if (!_objectSubclassingController) {
229+
_objectSubclassingController = [[PFObjectSubclassingController alloc] init];
230+
[_objectSubclassingController scanForUnregisteredSubclasses:YES];
231+
}
232+
controller = _objectSubclassingController;
233+
});
234+
return controller;
235+
}
236+
237+
- (void)setObjectSubclassingController:(PFObjectSubclassingController *)objectSubclassingController {
238+
dispatch_sync(_controllerAccessQueue, ^{
239+
_objectSubclassingController = objectSubclassingController;
240+
});
241+
}
242+
220243
///--------------------------------------
221244
#pragma mark - ObjectBatchController
222245
///--------------------------------------

Parse/PFObject+Subclass.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,17 @@ PF_ASSUME_NONNULL_BEGIN
127127

128128
@end
129129

130+
/*!
131+
As of Parse 1.8.1, subclasses are automatically registered when parse is initialized.
132+
133+
This protocol exists ONLY so that, if you absolutely need it, you can perform manual subclass registration
134+
via `[PFObject registerSubclass]`. Note that any calls to `registerSubclass` must happen after parse has been
135+
initialized already. This should only ever be needed in the scenario where you may be dynamically creation new
136+
Objective-C classes for parse objects, or you are doing conditional subclass registration (e.g. only register class A
137+
if config setting 'foo' is defined, otherwise register B).
138+
*/
139+
@protocol PFSubclassingSkipAutomaticRegistration <PFSubclassing>
140+
141+
@end
142+
130143
PF_ASSUME_NONNULL_END

Parse/PFObject.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2743,7 +2743,7 @@ + (PFCurrentUserController *)currentUserController {
27432743
}
27442744

27452745
+ (PFObjectSubclassingController *)subclassingController {
2746-
return [PFObjectSubclassingController defaultController];
2746+
return [Parse _currentManager].coreManager.objectSubclassingController;
27472747
}
27482748

27492749
@end

Parse/Parse.m

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,6 @@ + (void)setApplicationId:(NSString *)applicationId clientKey:(NSString *)clientK
6666

6767
shouldEnableLocalDatastore_ = NO;
6868

69-
PFObjectSubclassingController *subclassingController = [PFObjectSubclassingController defaultController];
70-
// Register built-in subclasses of PFObject so they get used.
71-
// We're forced to register subclasses directly this way, in order to prevent a deadlock.
72-
// If we ever switch to bundle scanning, this code can go away.
73-
[subclassingController registerSubclass:[PFUser class]];
74-
[subclassingController registerSubclass:[PFInstallation class]];
75-
[subclassingController registerSubclass:[PFSession class]];
76-
[subclassingController registerSubclass:[PFRole class]];
77-
[subclassingController registerSubclass:[PFPin class]];
78-
[subclassingController registerSubclass:[PFEventuallyPin class]];
79-
#if TARGET_OS_IPHONE
80-
[subclassingController registerSubclass:[PFProduct class]];
81-
#endif
82-
8369
[currentParseManager_ preloadDiskObjectsToMemoryAsync];
8470

8571
[[self parseModulesCollection] parseDidInitializeWithApplicationId:applicationId clientKey:clientKey];

0 commit comments

Comments
 (0)