Skip to content

Commit 36f34bd

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 c2b7b0b commit 36f34bd

17 files changed

+127
-116
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: 71 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"
@@ -98,17 +99,6 @@ - (instancetype)init {
9899
return self;
99100
}
100101

101-
+ (instancetype)defaultController {
102-
if (!defaultController_) {
103-
defaultController_ = [[PFObjectSubclassingController alloc] init];
104-
}
105-
return defaultController_;
106-
}
107-
108-
+ (void)clearDefaultController {
109-
defaultController_ = nil;
110-
}
111-
112102
///--------------------------------------
113103
#pragma mark - Public
114104
///--------------------------------------
@@ -121,6 +111,33 @@ + (void)clearDefaultController {
121111
return result;
122112
}
123113

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

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

124124
@end
125125

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

Parse/PFObject.m

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

24742474
+ (PFObjectSubclassingController *)subclassingController {
2475-
return [PFObjectSubclassingController defaultController];
2475+
return [Parse _currentManager].coreManager.objectSubclassingController;
24762476
}
24772477

24782478
@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: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -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

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)