FBRetainCycleDetector + MleaksFinder 읽기

9027 단어

FBRetainCycleDetector는 무엇을 합니까?


FBRetain Cycle Detector는 페이스북에서 시작된 것으로 메모리 유출을 일으키는 대상을 검출하는 데 사용되는 고리형 인용 체인이다.

MleakFinder는 무엇을 합니까?


MleakFinder는 메모리에서 메모리 누수가 발생할 수 있음을 감지하는 대상입니다.

왜 메모리 유출이 FBRetain Cycle Detector + MleaksFinder의 조합인지 검사합니까?


FBRetain Cycle Detector는 의심스러운 대상의 인용 구조를 대상으로 트리 검사를 하고 인용도를 그리며 범람의 깊이를 설정할 수 있다. 기본값은 10이고 더 큰 범람 깊이를 설정할 수 있으며 전체 조작은 시간이 많이 걸리기 때문에 자주 호출하지 않고 맞춤형 사용을 해야 한다.MleakFinder는 메모리에서 메모리 유출이 발생할 수 있는 의심스러운 대상을 찾아내는 데 사용되기 때문에 MleakFinder의 조작은 상대적으로 간단하고 성능을 많이 소모하지 않는다.따라서 FBRetainCycleDetector + MleaksFinder의 조합으로 자주 사용된다.

MleaksFinder는 어떻게 작동합니까?


MleaksFinder의 원본을 읽고 그 중에서 비교적 핵심적인 부분을 찾아서 MleaksFinder의 작업 원리를 간단하게 설명한다. MleaksFinder 중 몇 가지 중요한 category는 MleaksFinder work의 입구이고 UINavigation Controller+Memory Leak에 있다.hcategory+load 방법 중 hook
- (void)pushViewController:animated:
- (void)popViewControllerAnimated:popToViewController:animated:
- (void)popToRootViewControllerAnimated:

시스템 방법, 방법에서 VC가 방출되어야 하는지, vc가 pop,dismiss에 방출되어야 하는지 표시합니다.vc가 "droped"로 표시되어 있으며, 방출 지연이 필요한 "check Delay"로고를 표시하고, 다음push 동작에서 UIView Controller + Memory Leak을 검사합니다.h의 + load 방법 중 hook
- (void)viewWillAppear:
- (void)viewDidDisappear:
- (void)dismissViewControllerAnimated:completion:

생명주기 방법은viewWillAppear에 자신의'unDroped', dismissViewControllerAnimated:completion:을 표시할 때 자신의'droped'표시를 표시하고,droped 표시를 가져오면 자신의 메모리 유출 문제를 감지합니다.
한 페이지가 종료되고 사라질 때, 우리는 이 페이지가 풀려야 한다고 생각하기 때문에, 캐시가 필요하면 다음에 사용할 페이지를 검사하지 않을 수 있습니다.
도대체 어떻게 자신을 검출할 것인가. MleaksFinder에서 몇 가지 기초적인 클래스의 자체 검사 방식이 있는데 그것이 바로 UIViewController, UIView, NSObject이다. 이 몇 가지 주요 클래스인데 이 몇 가지 기초 클래스인 MemoryLeak이라는 category는 모두 윌 Dealloc 방법을 실현할 것이다.VC의view Did Disappear에서 VC 자체의will Dealloc 방법([super will Dealloc]), VC의view([self.view will Dealloc])의will Dealloc 방법을 호출합니다.UIView에서도 subviews의 will Dealloc를 동시에 감지합니다.이렇게 하면 페이지의 모델 층, UI 층의 메모리 유출을 검출할 수 있다.UIView, UIView Controller는 모두 NSObject를 계승하고, 최종 호출은 실제로는 NSObject의will Dealloc 방법에 들어간다.NSObject의 willDealloc 방법은 다음과 같습니다.
- (BOOL)willDealloc {
 NSString *className = NSStringFromClass([self class]);
 if ([[NSObject classNamesWhitelist] containsObject:className]) return NO; 
  NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
 if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) return NO; 
  __weak id weakSelf = self;
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    __strong id strongSelf = weakSelf;
   [strongSelf assertNotDealloc];
 });
 return YES;
}

그 중에서 가장 중요한 부분은 다음과 같다.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
   __strong id strongSelf = weakSelf;
   [strongSelf assertNotDealloc]; 
});

이 지연 실행 코드는 매우 영리하다. 정상적인 페이지가 2s를 종료한 후에 메모리가 유출되지 않으면 페이지의 VC와 View가 방출된다. 이때 strongSelf의 인용은 모두 nil이 되고 OC에서 nil에게 메시지를 보낸다([strongSelf assert NotDealloc])는 응답하지 않는다.설명에 응답해 아직 풀리지 않았다면 의심의 대상이 될 것이다.엄중한 심문이 필요하다.
그렇다면 assert Not Dealloc 방법은 어떻게 엄격하게 심문합니까?
이때 MleaksFinder는 책임을 지지 않고 어느 부분에서 메모리 유출이 발생했는지 개발자에게 현재 대상에게 메모리 유출이 있을 수 있음을 알립니다."Retain Cycle"단추를 누르면 MleaksFinder에서 FBRetain Cycle Detector를 호출하여 상세한 문제 검사를 진행합니다.

FBRetainCycleDetector는 어떻게 인용이 고리로 되어 있는지 검사합니까?


FBRetainCycleDetector는 외부에서 전송된 Object와 검색 깊이를 바탕으로 모든 강인용 속성을 깊이 우선적으로 훑어보고 동적 운행과 관련된 강인용 속성을 깊이 있게 훑어보며 이 관련 대상의 주소를 ObjectSet(set)의 집합에 넣고 대상 정보를 ObjectOnPath 집합(set)에 계산하며 대상을 대상 창고stack에 저장하여 대응 고리를 찾기 편리하게 한다.Stack의 길이는 현재 옮겨다니는 깊이를 대표합니다.먼저 전송된 object가 NSObject라면 대상의class를 가져오고 사용
const char *class_getIvarLayout(Class cls);

class의 모든 정의된property의 레이아웃 정보를 가져오고object에 대응하는property의value값을 꺼내서value대상의 주소(숫자)를 ObjectSet에 넣고 대상 바늘을 ObjectOnPath에 넣고 전체 트리 구조에 삽입합니다. 만약에 새 노드를 옮겨다니면 원래의 ObjectSet 주소표에 이미 존재합니다. 이것은 인용 고리를 형성한 것을 의미합니다. 즉, 원래의 트리 구조가 그림으로 연결된 것입니다.이 때 Stack에 기록된 경로에 따라 중복된object와 결합하여 환상도를 구축하여 환상 인용 체인으로 되돌아갈 수 있다.그러나 NSBlock 유형의 이미지를 만났다. 우리가 먼저 알아야 할 것은 NSBlock이 메모리에 어떻게 저장되는지이다. 그래서 FBRetain Cycle Detector는 Clang의 문서를 참고했고 Block의 구조에 대한 정의는 다음과 같다.
struct Block_literal_1 { void *isa; 
// initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags;
  int reserved;
  void (*invoke)(void *, ...);    
  struct Block_descriptor_1 {
     unsigned long int reserved; // NULL unsigned long
     int size; // sizeof(struct Block_literal_1) 
    // optional 
     helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 
     void (*dispose_helper)(void *src); // IFF (1<<25) 
    // required ABI.2010.3.16 
     const char *signature; // IFF (1<<30) 
} *descriptor; 
// imported variables
};

따라서 FBRetain Cycle Detector 라이브러리에서 Block 구조와 일치하는 struct를 BlockLiteral이라고 정의했다. 만나는 Block을 모두 BlockLiteral로 강하게 바꾸면 Block에 대응하는 속성과 방법을 조작할 수 있다. BlockLiteral의 정의는 다음과 같다.
enum { // Flags from BlockLiteral
  BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
  BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
  BLOCK_IS_GLOBAL =         (1 << 28),
  BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
  BLOCK_HAS_SIGNATURE =     (1 << 30),
};

struct BlockDescriptor {
  unsigned long int reserved;                // NULL
  unsigned long int size;
  // optional helper functions
  void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
  void (*dispose_helper)(void *src);         // IFF (1<<25)
  const char *signature;                     // IFF (1<<30)
};

struct BlockLiteral {
  void *isa;  // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
  int flags;
  int reserved;
  void (*invoke)(void *, ...);
  struct BlockDescriptor *descriptor;
  // imported variables
};


Block 대상의 저장 구조를 알았고 Block에 인용을 어디에 기록하는지 알았지만 어떤 대상의 저장이 강인용인지 알 수 있었다. 우리는 C의 구조체에 강약 인용 구분이 존재하지 않는다. 컴파일러는 컴파일러에서 이른바 강인용을 하나의copy 를 통해helper의function는copy작업을하고block에 생성된struct구조disposehelper의function,disposehelper는 struct가 방출될 때 인용된 대상을 방출합니다.다음은 컴파일러가 생성한disposehelper function의 정의, struct로 들어가는 주소Block_object_dispose는 컴파일러의funtion입니다
void __block_dispose_4(struct __block_literal_4 *src) {
     // was _Block_destroy
     _Block_object_dispose(src->existingBlock, BLOCK_FIELD_IS_BLOCK);
}

그래서 FBRetain Cycle Detector는 블랙박스 테스트를 이용하여 기존의 Block을 바탕으로 새로운 Block을 구축했다. 기존의 preference(인용)를 FBBlock Strong Relation Detector로 바꾸었다. FBBlock Strong Relation Detector는 FBRetain Cycle Detector에 정의된 탐지기로release 방법을 다시 썼고realese 메시지를 받았을 때 실제적으로 자신을 표시하는 strong 로고였다.위장된 Block 대상, 즉 BlockLiteral 대상에 대해 기존 Block으로 생성된 디스포스helper function은 구축된 BlockLiteral 위장 대상을 전송하여 리셋 동작을 시뮬레이션하고 리셋 메시지를 받은 대상은 강력한 인용 대상입니다.알고리즘 조작 초도는 다음과 같다(원고도...어색하다...):
검측하다.png
핵심 코드를 감지하려면 다음과 같이 하십시오.
  NSMutableSet *> *retainCycles = [NSMutableSet new];
  FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];

  NSMutableArray *stack = [NSMutableArray new];
  NSMutableSet *objectsOnPath = [NSMutableSet new];

  [stack addObject:wrappedObject];

  while ([stack count] > 0) {
    @autoreleasepool {

      FBNodeEnumerator *top = [stack lastObject];

      if (![objectsOnPath containsObject:top]) {
        if ([_objectSet containsObject:@([top.object objectAddress])]) {
          [stack removeLastObject];
          continue;
        }
        [_objectSet addObject:@([top.object objectAddress])];
      }

      [objectsOnPath addObject:top];

        // 
      FBNodeEnumerator *firstAdjacent = [top nextObject];
      if (firstAdjacent) {
        // 
        BOOL shouldPushToStack = NO;
        // 
        if ([objectsOnPath containsObject:firstAdjacent]) {
         
          NSUInteger index = [stack indexOfObject:firstAdjacent];
          NSInteger length = [stack count] - index;

          if (index == NSNotFound) {
            shouldPushToStack = YES;
          } else {
            // 
            NSRange cycleRange = NSMakeRange(index, length);
            NSMutableArray *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
            [cycle replaceObjectAtIndex:0 withObject:firstAdjacent];
            [retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];
          }
        } else {
          shouldPushToStack = YES;
        }

        if (shouldPushToStack) {
          if ([stack count] < stackDepth) {
            [stack addObject:firstAdjacent];
          }
        }
      } else {
         // 
        [stack removeLastObject];
        [objectsOnPath removeObject:top];
      }
    }
  }
  return retainCycles;


좋은 웹페이지 즐겨찾기