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

+2-8
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

+64-11
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

+8
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

+1
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

+23
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

+13
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

+1-1
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

-14
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

-1
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

-2
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"

Tests/Unit/ObjectSubclassPropertiesTests.m

-16
Original file line numberDiff line numberDiff line change
@@ -105,22 +105,6 @@ @interface ObjectSubclassPropertiesTests : PFUnitTestCase
105105

106106
@implementation ObjectSubclassPropertiesTests
107107

108-
///--------------------------------------
109-
#pragma mark - XCTestCase
110-
///--------------------------------------
111-
112-
- (void)setUp {
113-
[super setUp];
114-
115-
[PFTestObject registerSubclass];
116-
}
117-
118-
- (void)tearDown {
119-
[PFObject unregisterSubclass:[PFTestObject class]];
120-
121-
[super tearDown];
122-
}
123-
124108
///--------------------------------------
125109
#pragma mark - Tests
126110
///--------------------------------------

Tests/Unit/ObjectSubclassTests.m

+4-27
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#pragma mark - Helpers
1818
///--------------------------------------
1919

20-
@interface TheFlash : PFObject<PFSubclassing> {
20+
@interface TheFlash : PFObject<PFSubclassingSkipAutomaticRegistration> {
2121
NSString *flashName;
2222
}
2323

@@ -59,7 +59,7 @@ + (NSString *)parseClassName {
5959

6060
@end
6161

62-
@interface ClassWithDirtyingConstructor : PFObject<PFSubclassing>
62+
@interface ClassWithDirtyingConstructor : PFObject<PFSubclassingSkipAutomaticRegistration>
6363
@end
6464

6565
@implementation ClassWithDirtyingConstructor
@@ -85,7 +85,7 @@ @interface UtilityClass : PFObject
8585
@implementation UtilityClass
8686
@end
8787

88-
@interface DescendantOfUtility : UtilityClass<PFSubclassing>
88+
@interface DescendantOfUtility : UtilityClass<PFSubclassingSkipAutomaticRegistration>
8989
@end
9090

9191
@implementation DescendantOfUtility
@@ -94,7 +94,7 @@ + (NSString *)parseClassName {
9494
}
9595
@end
9696

97-
@interface StateClass : PFObject<PFSubclassing>
97+
@interface StateClass : PFObject<PFSubclassing, PFSubclassingSkipAutomaticRegistration>
9898

9999
@property (nonatomic, copy) NSString *state;
100100

@@ -120,17 +120,6 @@ @interface ObjectSubclassTests : PFUnitTestCase
120120

121121
@implementation ObjectSubclassTests
122122

123-
///--------------------------------------
124-
#pragma mark - XCTestCase
125-
///--------------------------------------
126-
127-
- (void)tearDown {
128-
[PFObject unregisterSubclass:[TheFlash class]];
129-
[PFObject unregisterSubclass:[BarryAllen class]];
130-
131-
[super tearDown];
132-
}
133-
134123
///--------------------------------------
135124
#pragma mark - Tests
136125
///--------------------------------------
@@ -173,18 +162,6 @@ - (void)testSubclassesCanInheritUtilityClassesWithoutParseClassName {
173162
[DescendantOfUtility registerSubclass];
174163
}
175164

176-
- (void)testSubclassRegistrationBeforeInitializingParse {
177-
[[Parse _currentManager] clearEventuallyQueue];
178-
[Parse _clearCurrentManager];
179-
180-
[TheFlash registerSubclass];
181-
182-
[Parse setApplicationId:@"a" clientKey:@"b"];
183-
184-
PFObject *theFlash = [PFObject objectWithClassName:@"Person"];
185-
PFAssertIsKindOfClass(theFlash, [TheFlash class]);
186-
}
187-
188165
- (void)testStateIsSubclassable {
189166
[StateClass registerSubclass];
190167
StateClass *stateClass = [StateClass object];

Tests/Unit/ObjectSubclassingControllerTests.m

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
#import "PFUnitTestCase.h"
1616
#import "ParseUnitTests-Swift.h"
1717

18-
@interface TestSubclass : PFObject<PFSubclassing>
18+
@interface TestSubclass : PFObject<PFSubclassingSkipAutomaticRegistration>
1919
@end
2020

21-
@interface NotSubclass : PFObject<PFSubclassing>
21+
@interface NotSubclass : PFObject<PFSubclassingSkipAutomaticRegistration>
2222
@end
2323

24-
@interface PropertySubclass : PFObject<PFSubclassing> {
24+
@interface PropertySubclass : PFObject<PFSubclassingSkipAutomaticRegistration> {
2525
@public
2626
id _ivarProperty;
2727
}

Tests/Unit/OfflineQueryLogicUnitTests.m

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ @implementation OfflineQueryLogicUnitTests
2929
- (void)setUp {
3030
[super setUp];
3131

32-
[PFUser registerSubclass];
3332
_user = [PFUser user];
3433
}
3534

3635
- (void)tearDown {
37-
[PFObject unregisterSubclass:[PFUser class]];
36+
_user = nil;
3837

3938
[super tearDown];
4039
}

Tests/Unit/SessionControllerTests.m

-16
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,6 @@ @interface SessionControllerTests : PFUnitTestCase
2525

2626
@implementation SessionControllerTests
2727

28-
///--------------------------------------
29-
#pragma mark - XCTestCase
30-
///--------------------------------------
31-
32-
- (void)setUp {
33-
[super setUp];
34-
35-
[PFSession registerSubclass];
36-
}
37-
38-
- (void)tearDown {
39-
[PFObject unregisterSubclass:[PFSession class]];
40-
41-
[super tearDown];
42-
}
43-
4428
///--------------------------------------
4529
#pragma mark - Helpers
4630
///--------------------------------------

0 commit comments

Comments
 (0)