Skip to content

Commit 11111a0

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 2f70f59 commit 11111a0

17 files changed

+132
-121
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-
#pragma mark - 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
#pragma mark - 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: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#import "PFAssert.h"
1818
#import "PFMacros.h"
19+
#import "PFObject.h"
20+
#import "PFObject+Subclass.h"
1921
#import "PFObjectSubclassInfo.h"
2022
#import "PFPropertyInfo_Private.h"
2123
#import "PFPropertyInfo_Runtime.h"
@@ -99,17 +101,6 @@ - (instancetype)init {
99101
return self;
100102
}
101103

102-
+ (instancetype)defaultController {
103-
if (!defaultController_) {
104-
defaultController_ = [[PFObjectSubclassingController alloc] init];
105-
}
106-
return defaultController_;
107-
}
108-
109-
+ (void)clearDefaultController {
110-
defaultController_ = nil;
111-
}
112-
113104
///--------------------------------------
114105
#pragma mark - Public
115106
///--------------------------------------
@@ -122,6 +113,33 @@ + (void)clearDefaultController {
122113
return result;
123114
}
124115

116+
- (void)scanForUnregisteredSubclasses:(BOOL)shouldSubscribe {
117+
// NOTE: Potential race-condition here - if another thread dynamically loads a bundle, we may end up accidentally
118+
// Skipping a bundle. Not entirely sure of the best solution to that here.
119+
if (shouldSubscribe) {
120+
@weakify(self);
121+
[[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
122+
object:nil
123+
queue:nil
124+
usingBlock:^(NSNotification *note) {
125+
@strongify(self);
126+
[self _registerSubclassesInBundle:note.object];
127+
}];
128+
}
129+
NSArray *bundles = [[NSBundle allFrameworks] arrayByAddingObjectsFromArray:[NSBundle allBundles]];
130+
for (NSBundle *bundle in bundles) {
131+
// Skip bundles that aren't loaded yet.
132+
if (!bundle.loaded || !bundle.executablePath) {
133+
continue;
134+
}
135+
// Filter out any system bundles
136+
if ([[bundle bundlePath] hasPrefix:@"/System/"] || [[bundle bundlePath] hasPrefix:@"/Library/"]) {
137+
continue;
138+
}
139+
[self _registerSubclassesInBundle:bundle];
140+
}
141+
}
142+
125143
- (void)registerSubclass:(Class<PFSubclassing>)kls {
126144
pf_sync_with_throw(_registeredSubclassesAccessQueue, ^{
127145
[self _rawRegisterSubclass:kls];
@@ -315,4 +333,47 @@ - (void)_rawRegisterSubclass:(Class)kls {
315333
_registeredSubclasses[[kls parseClassName]] = subclassInfo;
316334
}
317335

336+
- (void)_registerSubclassesInBundle:(NSBundle *)bundle {
337+
PFConsistencyAssert(bundle.loaded, @"Cannot register subclasses in a bundle that hasn't been loaded!");
338+
dispatch_sync(_registeredSubclassesAccessQueue, ^{
339+
Class pfObjectClass = [PFObject class];
340+
unsigned bundleClassCount = 0;
341+
342+
// NSBundle's executablePath does not resolve symlinks (sadface). Unfortunately, objc_copyClassNamesForImage()
343+
// Only takes absolute paths, as it does a direct string compare. This causes issues when using a framework
344+
// which uses the `Versions/XXX` format, vs. just having the binary be at the root of the `.framework` bundle.
345+
// However, we cannot use stringByResolvingSymlinksInPath for some reason here. On iOS, it never resolves the first,
346+
// symlink in the path, e.g. /var to /private/var.
347+
// Luckily, the POSIX function `realpath` will expand that symlink, and give us the path we need.
348+
char pathBuf[PATH_MAX + 1] = { 0 };
349+
if (!realpath([[bundle executablePath] UTF8String], pathBuf)) {
350+
return;
351+
}
352+
353+
const char **classNames = objc_copyClassNamesForImage(pathBuf, &bundleClassCount);
354+
for (unsigned i = 0; i < bundleClassCount; i++) {
355+
Class bundleClass = objc_getClass(classNames[i]);
356+
// For obvious reasons, don't register the PFObject class.
357+
if (bundleClass == pfObjectClass) {
358+
continue;
359+
}
360+
// NOTE: (richardross) Cannot use isSubclassOfClass here. Some classes may be part of a system bundle (even
361+
// though we attempt to filter those out) that may be an internal class which doesn't inherit from NSObject.
362+
// Scary, I know!
363+
for (Class kls = bundleClass; kls != nil; kls = class_getSuperclass(kls)) {
364+
if (kls == pfObjectClass) {
365+
// Do class_conformsToProtocol as late in the checking as possible, as its SUUUPER slow.
366+
// Behind the scenes this is a strcmp (lolwut?)
367+
if (class_conformsToProtocol(bundleClass, @protocol(PFSubclassing)) &&
368+
!class_conformsToProtocol(bundleClass, @protocol(PFSubclassingSkipAutomaticRegistration))) {
369+
[self _rawRegisterSubclass:bundleClass];
370+
}
371+
break;
372+
}
373+
}
374+
}
375+
free(classNames);
376+
});
377+
}
378+
318379
@end

Parse/Internal/PFCoreDataProvider.h

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

3737
@end
3838

39+
@class PFObjectSubclassingController;
40+
41+
@protocol PFObjectSubclassingControllerProvider <NSObject>
42+
43+
@property (nonatomic, strong) PFObjectSubclassingController *objectSubclassingController;
44+
45+
@end
46+
3947
@class PFObjectBatchController;
4048

4149
@protocol PFObjectBatchController <NSObject>

Parse/Internal/PFCoreManager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ PFPersistenceControllerProvider>
4242
<PFLocationManagerProvider,
4343
PFDefaultACLControllerProvider,
4444
PFObjectControllerProvider,
45+
PFObjectSubclassingControllerProvider,
4546
PFObjectBatchController,
4647
PFObjectFilePersistenceControllerProvider,
4748
PFPinningObjectStoreProvider,

Parse/Internal/PFCoreManager.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ @implementation PFCoreManager
5353
@synthesize cloudCodeController = _cloudCodeController;
5454
@synthesize configController = _configController;
5555
@synthesize objectController = _objectController;
56+
@synthesize objectSubclassingController = _objectSubclassingController;
5657
@synthesize objectBatchController = _objectBatchController;
5758
@synthesize objectFilePersistenceController = _objectFilePersistenceController;
5859
@synthesize objectLocalIdStore = _objectLocalIdStore;
@@ -235,6 +236,28 @@ - (void)setObjectController:(PFObjectController *)controller {
235236
});
236237
}
237238

239+
///--------------------------------------
240+
#pragma mark - ObjectSubclassingController
241+
///--------------------------------------
242+
243+
- (PFObjectSubclassingController *)objectSubclassingController {
244+
__block PFObjectSubclassingController *controller = nil;
245+
dispatch_sync(_controllerAccessQueue, ^{
246+
if (!_objectSubclassingController) {
247+
_objectSubclassingController = [[PFObjectSubclassingController alloc] init];
248+
[_objectSubclassingController scanForUnregisteredSubclasses:YES];
249+
}
250+
controller = _objectSubclassingController;
251+
});
252+
return controller;
253+
}
254+
255+
- (void)setObjectSubclassingController:(PFObjectSubclassingController *)objectSubclassingController {
256+
dispatch_sync(_controllerAccessQueue, ^{
257+
_objectSubclassingController = objectSubclassingController;
258+
});
259+
}
260+
238261
///--------------------------------------
239262
#pragma mark - ObjectBatchController
240263
///--------------------------------------

Parse/PFObject+Subclass.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,17 @@ NS_ASSUME_NONNULL_BEGIN
132132

133133
@end
134134

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

Parse/PFObject.m

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

24672467
+ (PFObjectSubclassingController *)subclassingController {
2468-
return [PFObjectSubclassingController defaultController];
2468+
return [Parse _currentManager].coreManager.objectSubclassingController;
24692469
}
24702470

24712471
@end

Parse/Parse.m

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,6 @@ + (void)initializeWithConfiguration:(ParseClientConfiguration *)configuration {
8080

8181
currentParseManager_ = manager;
8282

83-
PFObjectSubclassingController *subclassingController = [PFObjectSubclassingController defaultController];
84-
// Register built-in subclasses of PFObject so they get used.
85-
// We're forced to register subclasses directly this way, in order to prevent a deadlock.
86-
// If we ever switch to bundle scanning, this code can go away.
87-
[subclassingController registerSubclass:[PFUser class]];
88-
[subclassingController registerSubclass:[PFSession class]];
89-
[subclassingController registerSubclass:[PFRole class]];
90-
[subclassingController registerSubclass:[PFPin class]];
91-
[subclassingController registerSubclass:[PFEventuallyPin class]];
92-
#if !TARGET_OS_WATCH && !TARGET_OS_TV
93-
[subclassingController registerSubclass:[PFInstallation class]];
94-
#endif
95-
#if TARGET_OS_IOS || TARGET_OS_TV
96-
[subclassingController registerSubclass:[PFProduct class]];
97-
#endif
98-
9983
#if TARGET_OS_IOS
10084
[PFNetworkActivityIndicatorManager sharedManager].enabled = YES;
10185
#endif

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/ACLTests.m

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
#import "PFMacros.h"
1414
#import "PFObjectPrivate.h"
1515
#import "PFRole.h"
16-
#import "PFTestCase.h"
16+
#import "PFUnitTestCase.h"
1717
#import "PFUserPrivate.h"
1818

19-
@interface ACLTests : PFTestCase
19+
@interface ACLTests : PFUnitTestCase
2020

2121
@end
2222

@@ -210,8 +210,6 @@ - (void)testUnsharedCopy {
210210

211211

212212
- (void)testACLRequiresObjectId {
213-
[PFUser registerSubclass];
214-
215213
PFACL *acl = [PFACL ACL];
216214
#pragma clang diagnostic push
217215
#pragma clang diagnostic ignored "-Wnonnull"

Tests/Unit/ObjectSubclassPropertiesTests.m

Lines changed: 0 additions & 16 deletions
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

Lines changed: 4 additions & 27 deletions
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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
#import "PFUnitTestCase.h"
1717
#import "ParseUnitTests-Swift.h"
1818

19-
@interface TestSubclass : PFObject<PFSubclassing>
19+
@interface TestSubclass : PFObject<PFSubclassingSkipAutomaticRegistration>
2020
@end
2121

22-
@interface NotSubclass : PFObject<PFSubclassing>
22+
@interface NotSubclass : PFObject<PFSubclassingSkipAutomaticRegistration>
2323
@end
2424

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

Tests/Unit/OfflineQueryLogicUnitTests.m

Lines changed: 1 addition & 2 deletions
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
}

0 commit comments

Comments
 (0)