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