Skip to content

Commit b8e3172

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 8d38b5f commit b8e3172

File tree

8 files changed

+112
-34
lines changed

8 files changed

+112
-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: 64 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"
@@ -108,17 +109,6 @@ - (instancetype)init {
108109
return self;
109110
}
110111

111-
+ (instancetype)defaultController {
112-
if (!defaultController_) {
113-
defaultController_ = [[PFObjectSubclassingController alloc] init];
114-
}
115-
return defaultController_;
116-
}
117-
118-
+ (void)clearDefaultController {
119-
defaultController_ = nil;
120-
}
121-
122112
///--------------------------------------
123113
#pragma mark - Public
124114
///--------------------------------------
@@ -131,6 +121,33 @@ + (void)clearDefaultController {
131121
return result;
132122
}
133123

124+
- (void)scanForUnregisteredSubclasses:(BOOL)shouldSubscribe {
125+
// NOTE: Potential race-condition here - if another thread dynamically loads a bundle, we may end up accidentally
126+
// Skipping a bundle. Not entirely sure of the best solution to that here.
127+
if (shouldSubscribe) {
128+
@weakify(self);
129+
[[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
130+
object:nil
131+
queue:nil
132+
usingBlock:^(NSNotification *note) {
133+
@strongify(self);
134+
[self _registerSubclassesInBundle:note.object];
135+
}];
136+
}
137+
NSArray *bundles = [[NSBundle allFrameworks] arrayByAddingObjectsFromArray:[NSBundle allBundles]];
138+
for (NSBundle *bundle in bundles) {
139+
// Skip bundles that aren't loaded yet.
140+
if (!bundle.loaded || !bundle.executablePath) {
141+
continue;
142+
}
143+
// Filter out any system bundles
144+
if ([[bundle bundlePath] hasPrefix:@"/System/"] || [[bundle bundlePath] hasPrefix:@"/Library/"]) {
145+
continue;
146+
}
147+
[self _registerSubclassesInBundle:bundle];
148+
}
149+
}
150+
134151
- (void)registerSubclass:(Class<PFSubclassing>)kls {
135152
pf_sync_with_throw(_registeredSubclassesAccessQueue, ^{
136153
[self _rawRegisterSubclass:kls];
@@ -324,4 +341,40 @@ - (void)_rawRegisterSubclass:(Class)kls {
324341
_registeredSubclasses[[kls parseClassName]] = subclassInfo;
325342
}
326343

344+
- (void)_registerSubclassesInBundle:(NSBundle *)bundle {
345+
PFConsistencyAssert(bundle.loaded, @"Cannot register subclasses in a bundle that hasn't been loaded!");
346+
dispatch_sync(_registeredSubclassesAccessQueue, ^{
347+
Class pfObjectClass = [PFObject class];
348+
unsigned bundleClassCount = 0;
349+
350+
// NSBundle's executablePath does not resolve symlinks (sadface). Unfortunately, objc_copyClassNamesForImage()
351+
// Only takes absolute paths, as it does a direct string compare. This causes issues when using a framework
352+
// which uses the `Versions/XXX` format, vs. just having the binary be at the root of the `.framework` bundle.
353+
NSString *realPath = [[bundle executablePath] stringByResolvingSymlinksInPath];
354+
const char **classNames = objc_copyClassNamesForImage([realPath UTF8String], &bundleClassCount);
355+
for (unsigned i = 0; i < bundleClassCount; i++) {
356+
Class bundleClass = objc_getClass(classNames[i]);
357+
// For obvious reasons, don't register the PFObject class.
358+
if (bundleClass == pfObjectClass) {
359+
continue;
360+
}
361+
// NOTE: (richardross) Cannot use isSubclassOfClass here. Some classes may be part of a system bundle (even
362+
// though we attempt to filter those out) that may be an internal class which doesn't inherit from NSObject.
363+
// Scary, I know!
364+
for (Class kls = bundleClass; kls != nil; kls = class_getSuperclass(kls)) {
365+
if (kls == pfObjectClass) {
366+
// Do class_conformsToProtocol as late in the checking as possible, as its SUUUPER slow.
367+
// Behind the scenes this is a strcmp (lolwut?)
368+
if (class_conformsToProtocol(bundleClass, @protocol(PFSubclassing)) &&
369+
!class_conformsToProtocol(bundleClass, @protocol(PFSubclassingSkipAutomaticRegistration))) {
370+
[self _rawRegisterSubclass:bundleClass];
371+
}
372+
break;
373+
}
374+
}
375+
}
376+
free(classNames);
377+
});
378+
}
379+
327380
@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
@@ -122,4 +122,17 @@ PF_ASSUME_NONNULL_BEGIN
122122

123123
@end
124124

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

Parse/PFObject.m

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

27442744
+ (PFObjectSubclassingController *)subclassingController {
2745-
return [PFObjectSubclassingController defaultController];
2745+
return [Parse _currentManager].coreManager.objectSubclassingController;
27462746
}
27472747

27482748
@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)