GULRuntimeSnapshot.m 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GULRuntimeSnapshot.h"
  15. #import <objc/runtime.h>
  16. #import "GULRuntimeClassDiff.h"
  17. #import "GULRuntimeClassSnapshot.h"
  18. #import "GULRuntimeDiff.h"
  19. @implementation GULRuntimeSnapshot {
  20. /** The set of tracked classes. */
  21. NSSet<Class> *__nullable _classes;
  22. /** The class snapshots for each tracked class. */
  23. NSMutableDictionary<NSString *, GULRuntimeClassSnapshot *> *_classSnapshots;
  24. /** The hash value of this object. */
  25. NSUInteger _runningHash;
  26. }
  27. - (instancetype)init {
  28. return [self initWithClasses:nil];
  29. }
  30. - (instancetype)initWithClasses:(nullable NSSet<Class> *)classes {
  31. self = [super init];
  32. if (self) {
  33. _classSnapshots = [[NSMutableDictionary alloc] init];
  34. _classes = classes;
  35. _runningHash = [_classes hash] ^ [_classSnapshots hash];
  36. }
  37. return self;
  38. }
  39. - (NSUInteger)hash {
  40. return _runningHash;
  41. }
  42. - (BOOL)isEqual:(id)object {
  43. return [self hash] == [object hash];
  44. }
  45. - (NSString *)description {
  46. return [[super description] stringByAppendingFormat:@" Hash: 0x%lX", (unsigned long)[self hash]];
  47. }
  48. - (void)capture {
  49. int numberOfClasses = objc_getClassList(NULL, 0);
  50. Class *classList = (Class *)malloc(numberOfClasses * sizeof(Class));
  51. numberOfClasses = objc_getClassList(classList, numberOfClasses);
  52. // If we should track specific classes, then there's no need to figure out all ObjC classes.
  53. if (_classes) {
  54. for (Class aClass in _classes) {
  55. NSString *classString = NSStringFromClass(aClass);
  56. GULRuntimeClassSnapshot *classSnapshot =
  57. [[GULRuntimeClassSnapshot alloc] initWithClass:aClass];
  58. _classSnapshots[classString] = classSnapshot;
  59. [classSnapshot capture];
  60. _runningHash ^= [classSnapshot hash];
  61. }
  62. } else {
  63. for (int i = 0; i < numberOfClasses; i++) {
  64. Class aClass = classList[i];
  65. NSString *classString = NSStringFromClass(aClass);
  66. GULRuntimeClassSnapshot *classSnapshot =
  67. [[GULRuntimeClassSnapshot alloc] initWithClass:aClass];
  68. _classSnapshots[classString] = classSnapshot;
  69. [classSnapshot capture];
  70. _runningHash ^= [classSnapshot hash];
  71. }
  72. }
  73. free(classList);
  74. }
  75. - (GULRuntimeDiff *)diff:(GULRuntimeSnapshot *)otherSnapshot {
  76. GULRuntimeDiff *runtimeDiff = [[GULRuntimeDiff alloc] init];
  77. NSSet *setOne = [NSSet setWithArray:[_classSnapshots allKeys]];
  78. NSSet *setTwo = [NSSet setWithArray:[otherSnapshot->_classSnapshots allKeys]];
  79. // All items contained within setOne, but not in setTwo.
  80. NSSet *removedClasses = [setOne
  81. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  82. id _Nullable evaluatedObject,
  83. NSDictionary<NSString *, id> *_Nullable bindings) {
  84. return ![setTwo containsObject:evaluatedObject];
  85. }]];
  86. // All items contained within setTwo, but not in setOne.
  87. NSSet *addedClasses = [setTwo
  88. filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
  89. id _Nullable evaluatedObject,
  90. NSDictionary<NSString *, id> *_Nullable bindings) {
  91. return ![setOne containsObject:evaluatedObject];
  92. }]];
  93. runtimeDiff.removedClasses = removedClasses;
  94. runtimeDiff.addedClasses = addedClasses;
  95. NSMutableSet<GULRuntimeClassDiff *> *classDiffs = [[NSMutableSet alloc] init];
  96. [_classSnapshots
  97. enumerateKeysAndObjectsUsingBlock:^(
  98. NSString *_Nonnull key, GULRuntimeClassSnapshot *_Nonnull obj, BOOL *_Nonnull stop) {
  99. GULRuntimeClassSnapshot *classSnapshot = self->_classSnapshots[key];
  100. GULRuntimeClassSnapshot *otherClassSnapshot = otherSnapshot->_classSnapshots[key];
  101. GULRuntimeClassDiff *classDiff = [classSnapshot diff:otherClassSnapshot];
  102. if ([classDiff hash]) {
  103. NSAssert(![classDiffs containsObject:classDiff],
  104. @"An equivalent class diff has already been stored.");
  105. [classDiffs addObject:classDiff];
  106. }
  107. }];
  108. runtimeDiff.classDiffs = classDiffs;
  109. return runtimeDiff;
  110. }
  111. @end