Skip to content

Added automatic PFObject subclass registration. #5

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

Closed
wants to merge 1 commit into from
Closed
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
83 changes: 72 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 @@ -99,17 +101,6 @@ - (instancetype)init {
return self;
}

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

+ (void)clearDefaultController {
defaultController_ = nil;
}

///--------------------------------------
#pragma mark - Public
///--------------------------------------
Expand All @@ -122,6 +113,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) {
@weakify(self);
[[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 +333,47 @@ - (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];
unsigned bundleClassCount = 0;

// NSBundle's executablePath does not resolve symlinks (sadface). Unfortunately, objc_copyClassNamesForImage()
// Only takes absolute paths, as it does a direct string compare. This causes issues when using a framework
// which uses the `Versions/XXX` format, vs. just having the binary be at the root of the `.framework` bundle.
// However, we cannot use stringByResolvingSymlinksInPath for some reason here. On iOS, it never resolves the first,
// symlink in the path, e.g. /var to /private/var.
// Luckily, the POSIX function `realpath` will expand that symlink, and give us the path we need.
char pathBuf[PATH_MAX + 1] = { 0 };
if (!realpath([[bundle executablePath] UTF8String], pathBuf)) {
return;
}

const char **classNames = objc_copyClassNamesForImage(pathBuf, &bundleClassCount);
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: (richardross) 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 (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
13 changes: 13 additions & 0 deletions Parse/PFObject+Subclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,17 @@ NS_ASSUME_NONNULL_BEGIN

@end

/*!
As of Parse 1.11.0, subclasses are automatically registered when parse is initialized.

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 @@ -2465,7 +2465,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
6 changes: 3 additions & 3 deletions Tests/Unit/ObjectSubclassingControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
#import "PFUnitTestCase.h"
#import "ParseUnitTests-Swift.h"

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

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

@interface PropertySubclass : PFObject<PFSubclassing> {
@interface PropertySubclass : PFObject<PFSubclassingSkipAutomaticRegistration> {
@public
id _ivarProperty;
}
Expand Down
3 changes: 1 addition & 2 deletions Tests/Unit/OfflineQueryLogicUnitTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ @implementation OfflineQueryLogicUnitTests
- (void)setUp {
[super setUp];

[PFUser registerSubclass];
_user = [PFUser user];
}

- (void)tearDown {
[PFObject unregisterSubclass:[PFUser class]];
_user = nil;

[super tearDown];
}
Expand Down
Loading