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"
@@ -80,10 +82,9 @@ @implementation PFObjectSubclassingController {
8082 dispatch_queue_t _registeredSubclassesAccessQueue;
8183 NSMutableDictionary *_registeredSubclasses;
8284 NSMutableDictionary *_unregisteredSubclasses;
85+ id <NSObject > _bundleLoadedSubscriptionToken;
8386}
8487
85- static PFObjectSubclassingController *defaultController_;
86-
8788// /--------------------------------------
8889#pragma mark - Init
8990// /--------------------------------------
@@ -99,15 +100,10 @@ - (instancetype)init {
99100 return self;
100101}
101102
102- + (instancetype )defaultController {
103- if (!defaultController_) {
104- defaultController_ = [[PFObjectSubclassingController alloc ] init ];
105- }
106- return defaultController_;
107- }
108-
109- + (void )clearDefaultController {
110- defaultController_ = nil ;
103+ - (void )dealloc {
104+ [[NSNotificationCenter defaultCenter ] removeObserver: _bundleLoadedSubscriptionToken
105+ name: NSBundleDidLoadNotification
106+ object: nil ];
111107}
112108
113109// /--------------------------------------
@@ -122,6 +118,33 @@ + (void)clearDefaultController {
122118 return result;
123119}
124120
121+ - (void )scanForUnregisteredSubclasses : (BOOL )shouldSubscribe {
122+ // NOTE: Potential race-condition here - if another thread dynamically loads a bundle, we may end up accidentally
123+ // Skipping a bundle. Not entirely sure of the best solution to that here.
124+ if (shouldSubscribe && _bundleLoadedSubscriptionToken == nil ) {
125+ @weakify (self);
126+ _bundleLoadedSubscriptionToken = [[NSNotificationCenter defaultCenter ] addObserverForName: NSBundleDidLoadNotification
127+ object: nil
128+ queue: nil
129+ usingBlock: ^(NSNotification *note) {
130+ @strongify (self);
131+ [self _registerSubclassesInBundle: note.object];
132+ }];
133+ }
134+ NSArray *bundles = [[NSBundle allFrameworks ] arrayByAddingObjectsFromArray: [NSBundle allBundles ]];
135+ for (NSBundle *bundle in bundles) {
136+ // Skip bundles that aren't loaded yet.
137+ if (!bundle.loaded || !bundle.executablePath ) {
138+ continue ;
139+ }
140+ // Filter out any system bundles
141+ if ([bundle.bundlePath hasPrefix: @" /System/" ] || [bundle.bundlePath hasPrefix: @" /Library/" ]) {
142+ continue ;
143+ }
144+ [self _registerSubclassesInBundle: bundle];
145+ }
146+ }
147+
125148- (void )registerSubclass : (Class <PFSubclassing>)kls {
126149 pf_sync_with_throw (_registeredSubclassesAccessQueue, ^{
127150 [self _rawRegisterSubclass: kls];
@@ -315,4 +338,59 @@ - (void)_rawRegisterSubclass:(Class)kls {
315338 _registeredSubclasses[[kls parseClassName ]] = subclassInfo;
316339}
317340
341+ - (void )_registerSubclassesInBundle : (NSBundle *)bundle {
342+ PFConsistencyAssert (bundle.loaded , @" Cannot register subclasses in a bundle that hasn't been loaded!" );
343+ dispatch_sync (_registeredSubclassesAccessQueue, ^{
344+ Class pfObjectClass = [PFObject class ];
345+
346+ // There are two different paths that we will need to check for the bundle, depending on the platform.
347+ // - First, we need to check the raw executable path fom the bundle.
348+ // This should be valid for most frameworks on macOS, and iOS/watchOS/tvOS simulators.
349+ // - Second, we need to check the symlink resolved path - including /private/var on iOS.
350+ // This should be valid for iOS, watchOS, and tvOS devices.
351+ // In case there are other platforms that require checking multiple paths that we add support for,
352+ // just use a simple array here.
353+ char potentialPaths[2 ][PATH_MAX] = { };
354+
355+ strncpy (potentialPaths[0 ], bundle.executablePath .UTF8String , PATH_MAX);
356+ realpath (potentialPaths[0 ], potentialPaths[1 ]);
357+
358+ const char **classNames = NULL ;
359+ unsigned bundleClassCount = 0 ;
360+
361+ for (int i = 0 ; i < sizeof (potentialPaths) / sizeof (*potentialPaths); i++) {
362+ classNames = objc_copyClassNamesForImage (potentialPaths[i], &bundleClassCount);
363+ if (bundleClassCount) {
364+ break ;
365+ }
366+
367+ free (classNames);
368+ classNames = NULL ;
369+ }
370+
371+ for (unsigned i = 0 ; i < bundleClassCount; i++) {
372+ Class bundleClass = objc_getClass (classNames[i]);
373+ // For obvious reasons, don't register the PFObject class.
374+ if (bundleClass == pfObjectClass) {
375+ continue ;
376+ }
377+ // NOTE: Cannot use isSubclassOfClass here. Some classes may be part of a system bundle (even
378+ // though we attempt to filter those out) that may be an internal class which doesn't inherit from NSObject.
379+ // Scary, I know!
380+ for (Class kls = bundleClass; kls != nil ; kls = class_getSuperclass (kls)) {
381+ if (kls == pfObjectClass) {
382+ // Do class_conformsToProtocol as late in the checking as possible, as its SUUUPER slow.
383+ // Behind the scenes this is a strcmp (lolwut?)
384+ if (class_conformsToProtocol (bundleClass, @protocol (PFSubclassing)) &&
385+ !class_conformsToProtocol (bundleClass, @protocol (PFSubclassingSkipAutomaticRegistration))) {
386+ [self _rawRegisterSubclass: bundleClass];
387+ }
388+ break ;
389+ }
390+ }
391+ }
392+ free (classNames);
393+ });
394+ }
395+
318396@end
0 commit comments