16
16
17
17
#import " PFAssert.h"
18
18
#import " PFMacros.h"
19
+ #import " PFObject.h"
20
+ #import " PFObject+Subclass.h"
19
21
#import " PFObjectSubclassInfo.h"
20
22
#import " PFPropertyInfo_Private.h"
21
23
#import " PFPropertyInfo_Runtime.h"
@@ -80,10 +82,9 @@ @implementation PFObjectSubclassingController {
80
82
dispatch_queue_t _registeredSubclassesAccessQueue;
81
83
NSMutableDictionary *_registeredSubclasses;
82
84
NSMutableDictionary *_unregisteredSubclasses;
85
+ id <NSObject > _bundleLoadedSubscriptionToken;
83
86
}
84
87
85
- static PFObjectSubclassingController *defaultController_;
86
-
87
88
// /--------------------------------------
88
89
#pragma mark - Init
89
90
// /--------------------------------------
@@ -99,15 +100,10 @@ - (instancetype)init {
99
100
return self;
100
101
}
101
102
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 ];
111
107
}
112
108
113
109
// /--------------------------------------
@@ -122,6 +118,33 @@ + (void)clearDefaultController {
122
118
return result;
123
119
}
124
120
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
+
125
148
- (void )registerSubclass : (Class <PFSubclassing>)kls {
126
149
pf_sync_with_throw (_registeredSubclassesAccessQueue, ^{
127
150
[self _rawRegisterSubclass: kls];
@@ -315,4 +338,59 @@ - (void)_rawRegisterSubclass:(Class)kls {
315
338
_registeredSubclasses[[kls parseClassName ]] = subclassInfo;
316
339
}
317
340
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 OS X.
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.
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: (richardross) 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
+
318
396
@end
0 commit comments