Skip to content

Commit fef157a

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 ce4dfe9 commit fef157a

17 files changed

+120
-114
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
@@ -2710,7 +2710,7 @@ + (PFCurrentUserController *)currentUserController {
27102710
}
27112711

27122712
+ (PFObjectSubclassingController *)subclassingController {
2713-
return [PFObjectSubclassingController defaultController];
2713+
return [Parse _currentManager].coreManager.objectSubclassingController;
27142714
}
27152715

27162716
@end

Parse/Parse.m

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

6868
shouldEnableLocalDatastore_ = NO;
6969

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

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

Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.m

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ - (void)setUp {
4242
- (void)tearDown {
4343
[[Parse _currentManager] clearEventuallyQueue];
4444
[Parse _clearCurrentManager];
45-
[PFObjectSubclassingController clearDefaultController];
4645

4746
[super tearDown];
4847
}

Tests/Unit/ACLUnitTests.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,6 @@ - (void)testDefaultACL {
218218
}
219219

220220
- (void)testACLRequiresObjectId {
221-
[PFUser registerSubclass];
222-
223221
PFACL *acl = [PFACL ACL];
224222
#pragma clang diagnostic push
225223
#pragma clang diagnostic ignored "-Wnonnull"

0 commit comments

Comments
 (0)