Skip to content

Added automatic PFObject subclass registration. #967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,12 @@

@interface PFObjectSubclassingController : NSObject

///--------------------------------------
#pragma mark - Init
///--------------------------------------

//TODO: (nlutsenko, richardross) Make it not terrible aka don't have singletons.
+ (instancetype)defaultController;
+ (void)clearDefaultController;

///--------------------------------------
#pragma mark - Registration
///--------------------------------------

- (void)scanForUnregisteredSubclasses:(BOOL)shouldSubscribe;

- (Class<PFSubclassing>)subclassForParseClassName:(NSString *)parseClassName;
- (void)registerSubclass:(Class<PFSubclassing>)kls;
- (void)unregisterSubclass:(Class<PFSubclassing>)kls;
Expand Down
100 changes: 89 additions & 11 deletions Parse/Internal/Object/Subclassing/PFObjectSubclassingController.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#import "PFAssert.h"
#import "PFMacros.h"
#import "PFObject.h"
#import "PFObject+Subclass.h"
#import "PFObjectSubclassInfo.h"
#import "PFPropertyInfo_Private.h"
#import "PFPropertyInfo_Runtime.h"
Expand Down Expand Up @@ -80,10 +82,9 @@ @implementation PFObjectSubclassingController {
dispatch_queue_t _registeredSubclassesAccessQueue;
NSMutableDictionary *_registeredSubclasses;
NSMutableDictionary *_unregisteredSubclasses;
id<NSObject> _bundleLoadedSubscriptionToken;
}

static PFObjectSubclassingController *defaultController_;

///--------------------------------------
#pragma mark - Init
///--------------------------------------
Expand All @@ -99,15 +100,10 @@ - (instancetype)init {
return self;
}

+ (instancetype)defaultController {
if (!defaultController_) {
defaultController_ = [[PFObjectSubclassingController alloc] init];
}
return defaultController_;
}

+ (void)clearDefaultController {
defaultController_ = nil;
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:_bundleLoadedSubscriptionToken
name:NSBundleDidLoadNotification
object:nil];
}

///--------------------------------------
Expand All @@ -122,6 +118,33 @@ + (void)clearDefaultController {
return result;
}

- (void)scanForUnregisteredSubclasses:(BOOL)shouldSubscribe {
// NOTE: Potential race-condition here - if another thread dynamically loads a bundle, we may end up accidentally
// Skipping a bundle. Not entirely sure of the best solution to that here.
if (shouldSubscribe && _bundleLoadedSubscriptionToken == nil) {
@weakify(self);
_bundleLoadedSubscriptionToken = [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
@strongify(self);
[self _registerSubclassesInBundle:note.object];
}];
}
NSArray *bundles = [[NSBundle allFrameworks] arrayByAddingObjectsFromArray:[NSBundle allBundles]];
for (NSBundle *bundle in bundles) {
// Skip bundles that aren't loaded yet.
if (!bundle.loaded || !bundle.executablePath) {
continue;
}
// Filter out any system bundles
if ([bundle.bundlePath hasPrefix:@"/System/"] || [bundle.bundlePath hasPrefix:@"/Library/"]) {
continue;
}
[self _registerSubclassesInBundle:bundle];
}
}

- (void)registerSubclass:(Class<PFSubclassing>)kls {
pf_sync_with_throw(_registeredSubclassesAccessQueue, ^{
[self _rawRegisterSubclass:kls];
Expand Down Expand Up @@ -315,4 +338,59 @@ - (void)_rawRegisterSubclass:(Class)kls {
_registeredSubclasses[[kls parseClassName]] = subclassInfo;
}

- (void)_registerSubclassesInBundle:(NSBundle *)bundle {
PFConsistencyAssert(bundle.loaded, @"Cannot register subclasses in a bundle that hasn't been loaded!");
dispatch_sync(_registeredSubclassesAccessQueue, ^{
Class pfObjectClass = [PFObject class];

// There are two different paths that we will need to check for the bundle, depending on the platform.
// - First, we need to check the raw executable path fom the bundle.
// This should be valid for most frameworks on macOS, and iOS/watchOS/tvOS simulators.
// - Second, we need to check the symlink resolved path - including /private/var on iOS.
// This should be valid for iOS, watchOS, and tvOS devices.
// In case there are other platforms that require checking multiple paths that we add support for,
// just use a simple array here.
char potentialPaths[2][PATH_MAX] = { };

strncpy(potentialPaths[0], bundle.executablePath.UTF8String, PATH_MAX);
realpath(potentialPaths[0], potentialPaths[1]);

const char **classNames = NULL;
unsigned bundleClassCount = 0;

for (int i = 0; i < sizeof(potentialPaths) / sizeof(*potentialPaths); i++) {
classNames = objc_copyClassNamesForImage(potentialPaths[i], &bundleClassCount);
if (bundleClassCount) {
break;
}

free(classNames);
classNames = NULL;
}

for (unsigned i = 0; i < bundleClassCount; i++) {
Class bundleClass = objc_getClass(classNames[i]);
// For obvious reasons, don't register the PFObject class.
if (bundleClass == pfObjectClass) {
continue;
}
// NOTE: Cannot use isSubclassOfClass here. Some classes may be part of a system bundle (even
// though we attempt to filter those out) that may be an internal class which doesn't inherit from NSObject.
// Scary, I know!
for (Class kls = bundleClass; kls != nil; kls = class_getSuperclass(kls)) {
if (kls == pfObjectClass) {
// Do class_conformsToProtocol as late in the checking as possible, as its SUUUPER slow.
// Behind the scenes this is a strcmp (lolwut?)
if (class_conformsToProtocol(bundleClass, @protocol(PFSubclassing)) &&
!class_conformsToProtocol(bundleClass, @protocol(PFSubclassingSkipAutomaticRegistration))) {
[self _rawRegisterSubclass:bundleClass];
}
break;
}
}
}
free(classNames);
});
}

@end
8 changes: 8 additions & 0 deletions Parse/Internal/PFCoreDataProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ NS_ASSUME_NONNULL_BEGIN

@end

@class PFObjectSubclassingController;

@protocol PFObjectSubclassingControllerProvider <NSObject>

@property (null_resettable, nonatomic, strong) PFObjectSubclassingController *objectSubclassingController;

@end

@class PFObjectBatchController;

@protocol PFObjectBatchController <NSObject>
Expand Down
1 change: 1 addition & 0 deletions Parse/Internal/PFCoreManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ PFPersistenceControllerProvider>
<PFLocationManagerProvider,
PFDefaultACLControllerProvider,
PFObjectControllerProvider,
PFObjectSubclassingControllerProvider,
PFObjectBatchController,
PFObjectFilePersistenceControllerProvider,
PFPinningObjectStoreProvider,
Expand Down
23 changes: 23 additions & 0 deletions Parse/Internal/PFCoreManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ @implementation PFCoreManager
@synthesize cloudCodeController = _cloudCodeController;
@synthesize configController = _configController;
@synthesize objectController = _objectController;
@synthesize objectSubclassingController = _objectSubclassingController;
@synthesize objectBatchController = _objectBatchController;
@synthesize objectFilePersistenceController = _objectFilePersistenceController;
@synthesize objectLocalIdStore = _objectLocalIdStore;
Expand Down Expand Up @@ -235,6 +236,28 @@ - (void)setObjectController:(PFObjectController *)controller {
});
}

///--------------------------------------
#pragma mark - ObjectSubclassingController
///--------------------------------------

- (PFObjectSubclassingController *)objectSubclassingController {
__block PFObjectSubclassingController *controller = nil;
dispatch_sync(_controllerAccessQueue, ^{
if (!_objectSubclassingController) {
_objectSubclassingController = [[PFObjectSubclassingController alloc] init];
[_objectSubclassingController scanForUnregisteredSubclasses:YES];
}
controller = _objectSubclassingController;
});
return controller;
}

- (void)setObjectSubclassingController:(PFObjectSubclassingController *)objectSubclassingController {
dispatch_sync(_controllerAccessQueue, ^{
_objectSubclassingController = objectSubclassingController;
});
}

///--------------------------------------
#pragma mark - ObjectBatchController
///--------------------------------------
Expand Down
11 changes: 11 additions & 0 deletions Parse/PFObject+Subclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,15 @@ NS_ASSUME_NONNULL_BEGIN

@end

/*!
This protocol exists ONLY so that, if you absolutely need it, you can perform manual subclass registration
via `[Subclass registerSubclass]`. Note that any calls to `registerSubclass` must happen after parse has been
initialized already. This should only ever be needed in the scenario where you may be dynamically creation new
Objective-C classes for parse objects, or you are doing conditional subclass registration (e.g. only register class A
if config setting 'foo' is defined, otherwise register B).
*/
@protocol PFSubclassingSkipAutomaticRegistration <PFSubclassing>

@end

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion Parse/PFObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -2454,7 +2454,7 @@ + (PFCurrentUserController *)currentUserController {
}

+ (PFObjectSubclassingController *)subclassingController {
return [PFObjectSubclassingController defaultController];
return [Parse _currentManager].coreManager.objectSubclassingController;
}

@end
Expand Down
16 changes: 0 additions & 16 deletions Parse/Parse.m
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,6 @@ + (void)initializeWithConfiguration:(ParseClientConfiguration *)configuration {

currentParseManager_ = manager;

PFObjectSubclassingController *subclassingController = [PFObjectSubclassingController defaultController];
// Register built-in subclasses of PFObject so they get used.
// We're forced to register subclasses directly this way, in order to prevent a deadlock.
// If we ever switch to bundle scanning, this code can go away.
[subclassingController registerSubclass:[PFUser class]];
[subclassingController registerSubclass:[PFSession class]];
[subclassingController registerSubclass:[PFRole class]];
[subclassingController registerSubclass:[PFPin class]];
[subclassingController registerSubclass:[PFEventuallyPin class]];
#if !TARGET_OS_WATCH && !TARGET_OS_TV
[subclassingController registerSubclass:[PFInstallation class]];
#endif
#if TARGET_OS_IOS || TARGET_OS_TV
[subclassingController registerSubclass:[PFProduct class]];
#endif

#if TARGET_OS_IOS
[PFNetworkActivityIndicatorManager sharedManager].enabled = YES;
#endif
Expand Down
1 change: 0 additions & 1 deletion Tests/Other/TestCases/UnitTestCase/PFUnitTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ - (void)setUp {
- (void)tearDown {
[[Parse _currentManager] clearEventuallyQueue];
[Parse _clearCurrentManager];
[PFObjectSubclassingController clearDefaultController];

[super tearDown];
}
Expand Down
6 changes: 2 additions & 4 deletions Tests/Unit/ACLTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
#import "PFMacros.h"
#import "PFObjectPrivate.h"
#import "PFRole.h"
#import "PFTestCase.h"
#import "PFUnitTestCase.h"
#import "PFUserPrivate.h"

@interface ACLTests : PFTestCase
@interface ACLTests : PFUnitTestCase

@end

Expand Down Expand Up @@ -210,8 +210,6 @@ - (void)testUnsharedCopy {


- (void)testACLRequiresObjectId {
[PFUser registerSubclass];

PFACL *acl = [PFACL ACL];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
Expand Down
16 changes: 0 additions & 16 deletions Tests/Unit/ObjectSubclassPropertiesTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,6 @@ @interface ObjectSubclassPropertiesTests : PFUnitTestCase

@implementation ObjectSubclassPropertiesTests

///--------------------------------------
#pragma mark - XCTestCase
///--------------------------------------

- (void)setUp {
[super setUp];

[PFTestObject registerSubclass];
}

- (void)tearDown {
[PFObject unregisterSubclass:[PFTestObject class]];

[super tearDown];
}

///--------------------------------------
#pragma mark - Tests
///--------------------------------------
Expand Down
31 changes: 4 additions & 27 deletions Tests/Unit/ObjectSubclassTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#pragma mark - Helpers
///--------------------------------------

@interface TheFlash : PFObject<PFSubclassing> {
@interface TheFlash : PFObject<PFSubclassingSkipAutomaticRegistration> {
NSString *flashName;
}

Expand Down Expand Up @@ -59,7 +59,7 @@ + (NSString *)parseClassName {

@end

@interface ClassWithDirtyingConstructor : PFObject<PFSubclassing>
@interface ClassWithDirtyingConstructor : PFObject<PFSubclassingSkipAutomaticRegistration>
@end

@implementation ClassWithDirtyingConstructor
Expand All @@ -85,7 +85,7 @@ @interface UtilityClass : PFObject
@implementation UtilityClass
@end

@interface DescendantOfUtility : UtilityClass<PFSubclassing>
@interface DescendantOfUtility : UtilityClass<PFSubclassingSkipAutomaticRegistration>
@end

@implementation DescendantOfUtility
Expand All @@ -94,7 +94,7 @@ + (NSString *)parseClassName {
}
@end

@interface StateClass : PFObject<PFSubclassing>
@interface StateClass : PFObject<PFSubclassing, PFSubclassingSkipAutomaticRegistration>

@property (nonatomic, copy) NSString *state;

Expand All @@ -120,17 +120,6 @@ @interface ObjectSubclassTests : PFUnitTestCase

@implementation ObjectSubclassTests

///--------------------------------------
#pragma mark - XCTestCase
///--------------------------------------

- (void)tearDown {
[PFObject unregisterSubclass:[TheFlash class]];
[PFObject unregisterSubclass:[BarryAllen class]];

[super tearDown];
}

///--------------------------------------
#pragma mark - Tests
///--------------------------------------
Expand Down Expand Up @@ -173,18 +162,6 @@ - (void)testSubclassesCanInheritUtilityClassesWithoutParseClassName {
[DescendantOfUtility registerSubclass];
}

- (void)testSubclassRegistrationBeforeInitializingParse {
[[Parse _currentManager] clearEventuallyQueue];
[Parse _clearCurrentManager];

[TheFlash registerSubclass];

[Parse setApplicationId:@"a" clientKey:@"b"];

PFObject *theFlash = [PFObject objectWithClassName:@"Person"];
PFAssertIsKindOfClass(theFlash, [TheFlash class]);
}

- (void)testStateIsSubclassable {
[StateClass registerSubclass];
StateClass *stateClass = [StateClass object];
Expand Down
Loading