15, 아이폰 OS는 분석의 기초 - KVO를


KVO는 종종 아이폰 OS에서 사용되는 KVC는 종종 인터뷰를 요청합니다. 오늘은 무엇 KVO를 탐험.

KVO는 무엇입니까?

KVO : 키 관찰 메커니즘, 속성을 관찰 할 수있는 방법을 제공합니다 변경
, KVO는 KeyValueObserving 스탠드 애플에서 제공하는 이벤트 알림 메커니즘입니다. 이 객체가 다른 객체의 특성의 변화를 모니터링 할 수 있으며, 이벤트는 변화의 시점에서 수신된다. 그렇게 KVO의 구현 메커니즘 때문에, 속성은 일반적으로 NSObject의 객체로부터 상속 역할을 담당 할 것입니다 기본 KVO에 의해 지원됩니다.
그리고 KVO 아이폰 OS NSNotificationCenter는에있는 관찰자 모델의 하나의 구현입니다.

차이는 관찰자와 관찰자 사이의 상대적인 관계한다 :

  • KVO 중 하나입니다
  • NSNotificationCenter 일대

오브젝트되기 KVO 비 침습적 모니터링 구현 내부 코드를 수정하지 않고, 모니터.
KVO는 객체의 컬렉션의 변화를 모니터링 할 수있는 개별 특성의 변화를 모니터링 할 수 있습니다. KVC의 mutableArrayValueForKey 기준 : 내부 프록시 객체 변경, 콜백 메소드 KVO이들을 때, 프록시 객체 메소드를 가져옵니다. 있는 NSArray 객체와 NSSet의 컬렉션을 포함합니다.

첫째, KVO의 기본적인 사용법

사용 KVO는 세 단계 (KVO 부작)으로 나누어

  1. 등록 관찰자
  2. 콜백 구현
  3. 제거 관찰자
#import "LJLKVOViewController.h"
#import <objc/runtime.h>
#import "LJLKVOPerson.h"
#import "LJLKVODownloader.h"

static void * personNameContext = &personNameContext;//观察person.name 的 context
static void * personKvoArrayContext = &personKvoArrayContext;//观察person.kvoArray 的 context

@interface LJLKVOViewController ()
@property(nonatomic, strong)LJLKVOPerson * person;
@end

1.1 등록 관찰자는
    관찰자 레지스터의 다음 오브젝트 메소드 호출을 관찰 :

self.person = [[LJLKVOPerson alloc] init];
//注册观察者
[self.person addObserver:self
              forKeyPath:@"name"
                 options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                context:personNameContext];

상황에 맞는 효과 : 빨리 키 관찰을 찾습니다.
같은 이름이 컨텍스트를 구별 할 필요성에 구별이 용이하지 않을 때이 경우 키 패스는 관찰했다.
그러나 또한 이러한 양식을 통해 당신은 속성, 그렇게하는보다 안전하고 효율적으로, 중첩 콜백 다층 결정된다 감소 어떤 수업 관찰되는 알 수 있습니다. 기능은 태그와 유사합니다.
ID가 전무을 채워지면이 곳은 그래서 널 (NULL) 무효 * NULL을 작성하기 때문에 당신은 당신이 NULL 채울 수없는 경우. 엑스 코드는 컴파일 할 때 전무 NULL로 전환하는 데 도움이 있기 때문에 또한, 전무 채울 수 있습니다.

1.2 콜백 구현
등록, 관찰자 알림을받을 수있는 다음과 같은 콜백을 구현해야합니다 :

-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                      context:(void *)context
{
//这里就可以通过注册的时候的context来快速判断出观察的哪个类的哪个属性变化触发的回调
    if (context == personNameContext) {
        NSLog(@"name change : %@",change);
    }else{
        NSLog(@"change - %@",change);
    }

//change - {
//        kind = 1; 这个就是观察的类型,1是set
//        new = "0.100000";
/*
     观察类型枚举 (被观察者变更的类型)
     NSKeyValueChangeSetting = 1,       设置 例如观察的是NSString 修改的时候就是这个类型
     NSKeyValueChangeInsertion = 2,     插入 例如观察的是NSMutableArray 添加数据的时候
     NSKeyValueChangeRemoval = 3,       删除
     NSKeyValueChangeReplacement = 4,   替换
*/
}

1.3, 관찰자를 제거하지
    더 이상은 관찰자를 제거해야합니다, 관찰을 계속하는 경우, 또는 예외가 발생할 수 있습니다. 어떤 숨겨진 충돌을 피하기 위해 좋은 습관을 개발한다.
    당신이 그것을 제거하고 다시 오지 않는 경우 다시 등록합니다. 여러 통화가 발생하는 경우입니다. 시청자는 하나의 실시 예를 사용하는 경우, 또한, 관찰 대상이 해제되지 않을 것이다가 제거되지 않고, 포인터 필드가있을 것, 축소한다.

[self.student removeObserver:self forKeyPath:@"name"];

더 완전한 세 단계는 다음과 같이 물론,이 반드시 필요에 따라, 하나의 실시 예 관찰자에 기록 할 필요가 없습니다.

#import "LGPerson.h"
{
//需要设置为公共的,否者不能在外部访问。要在外部赋值这个实例变量必须加 @public 公有.KVO不能监听实例变量
    @public
    NSString *nikcName;
}
@interface LGStudent : LGPerson
+ (instancetype)shareInstance;
@end

//------------------------------------------------
#import "LGStudent.h"
@implementation LGStudent
static LGStudent* _instance = nil;
+ (instancetype)shareInstance{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:NULL] init] ;
    }) ;
    return _instance ;
}
@end

//------------------------------------------------
#import "LJLKVOViewController.h"
#import "LGStudent.h"

@interface LJLKVOViewController ()
@property (nonatomic, strong) LGStudent *student;
@end

@implementation LJLKVOViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.student = [LGStudent shareInstance];
//1、注册观察者
    [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
//触摸事件修改student.name 触发监听回调
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.student.name = @"hello word";
//    外部访问实例变量 需要设置为公共
    self.student->nikcName = @"ljl";
}
#pragma mark - KVO回调
//2、实现回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"LJLKVOViewController :%@",change);
}

//3、移除观察者
- (void)dealloc{
    [self.student removeObserver:self forKeyPath:@"name"];
}
@end

1.4 형의 속성 세트

    관찰의 컬렉션은 KVC 기반으로해야합니다. 직접 접근하는 것이 더 편리 KVC으로.
    가변 어레이 속성을 추가 LJLKVOPerson.h

@property(nonatomic, strong) NSMutableArray * kvoArray;

KVO 가입하고 kvoArray 수정

//    一定要初始化,否者 kvoArray为nil addObject:的时候就崩溃了
    self.person.kvoArray = [NSMutableArray array];
    [self.person addObserver:self
                  forKeyPath:@"kvoArray"
                     options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                     context:NULL];

    [self.person.kvoArray addObject:@"123"];
//    上面这样这样写是无法触发KVO 回调的,因为addObject:方法是不走setter 的。 而KVO监听是setter方法.需要将上述向数组添加元素的方法修改一下
    [[self.person mutableArrayValueForKey:@"kvoArray"] addObject:@"123"];
//    这样做之后 self.person.kvoArray 指向一个新的数组对象,相当于:
//    NSMutableArray * tmp = [NSMutableArray arrayWithArray:self.person.kvoArray];
//    [tmp addObject:@"123"];
//    self.person.kvoArray = tmp;
//    所以能触发KVO回调

1.5 여러 관련 속성 관찰
    : 같은 다운로드를 시뮬레이션하는 데 사용되는 LJLKVODownloader 클래스 등을,이 세 가지 특성은 totalBytes, completedBytes 및 진행 진보의 비율입니다
    우리는 진행 상황에 대해 우려하는 UI 계층에서, 그러나 진행이 영향을 미치는 두 가지 다른 속성이 적용됩니다 때 두 가지 방법을 다시 LJLKVODownloader 필요합니다 :

#import <Foundation/Foundation.h>

@interface LJLKVODownloader : NSObject
@property(nonatomic, assign) unsigned long long totalBytes;//总字节
@property(nonatomic, assign) unsigned long long completedBytes;//完成字节
@property(nonatomic, copy) NSString * progress;//进度
@end
#import "LJLKVODownloader.h"

@implementation LJLKVODownloader
//返回属性的一组键路径,这些属性的值会影响键控属性的值
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"progress"]) {
        NSArray * dependKeys = @[@"totalBytes",@"completedBytes"];
//        通过从数组中添加对象来设置
        keyPaths = [keyPaths setByAddingObjectsFromArray:dependKeys];
    }
    return keyPaths;
}

- (NSString *)progress{
    if (self.totalBytes == 0 || self.completedBytes == 0) {
        return @"0%";
    }
    double progress = (double)self.completedBytes/(double)self.totalBytes*100;
    if (progress > 100) {
        progress = 100;
    }
    return [NSString stringWithFormat:@"%d%%",(int)ceil(progress)];
}
@end
    LJLKVODownloader * downloader = [[LJLKVODownloader alloc] init];
    downloader.totalBytes = 205;
    [downloader addObserver:self
                 forKeyPath:@"progress"
                    options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                    context:NULL];
    downloader.completedBytes = 64;
    downloader.completedBytes = 168;

모니터 콜백을 결과는 다음과 같다

2020-03-08 00:34:53.547235+0800 filedome[31182:820584] change : {
    kind = 1;
    new = "32%";
    old = "0%";
}
2020-03-08 00:34:53.547581+0800 filedome[31182:820584] change : {
    kind = 1;
    new = "82%";
    old = "32%";
}

 

둘째, 자동 또는 수동 스위치를 관찰

    KVO 그것을 이해하기 전에, 우리는 KVC을 이해할 필요가있다.
    반환 YES, 멤버 변수를 찾아 가면 KVC는 세터 또는 게터에 호출됩니다, 발견되지 않는 경우는, 다음 클래스 메소드 + accessInstanceVariablesDirectly (직접 액세스 인스턴스 변수)를 호출
    KVO 비슷한 메커니즘입니다 세 가지 인터페이스가 KVO에있다, 인터페이스 :

- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;(自动通知观察者)

+ automaticallyNotifiesObserversForKey : 기본 반환 YES, 중산층 동적 이전과 수정이 호출 된 후 속성에 세터, 투기를 다시 만든
 -willChangeValueForKey :
 및 -didChangeValueForKey을 : 유사한 방법 뷰어를 알리는 목적을 달성 할 수 있습니다.
 만약 서브 클래스 오버라이드 (override) + automaticallyNotifiesObserversForKey : 및 NO에 반환, 당신은 자동으로 KVO 알림 메커니즘을 트리거 할 수는 없지만, 우리가 수동으로 -willChangeValueForKey를 호출 할 수 및 -didChangeValueForKey을 다음 KVO 콜백을 트리거 할 수 있습니다.

// 수동 관찰이 있는지 여부를 개방 또는 폐쇄 자동 자동 관찰을 관찰 할 콜백
: // 콜백 IS didChangeValueForKey가 트리거
// 자동 관측은 기본적으로 시스템이 우리를 도울 방법을 한 것입니다 추가됩니다. 당신이 그의 손에 다시 작성하는 경우, 그것은 한 트리거 콜백 메소드 시간을, 즉, 두 번 트리거합니다

#import "LJLKVOPerson.h"
@implementation LJLKVOPerson

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;//自动观察
}

- (void)setName:(NSString *)name{
//    手动观察。不管是打开自动观察还是关闭自动观察都会回调
// 触发回调的是 didChangeValueForKey 
// 自动观察 也就是默认情况下,系统是帮我们添加了will 和 did 方法。如果在自己手动写一遍的话就会触发两次,也就是走一次did 方法就会触发一次回调
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];//这个方法里面触发回调
}
@end

 

셋째, KVO의 원칙의 실현

 KVO 공식 문서는 자동으로 KVO 관찰이 ISA-스위 즐링 키를 달성하기 위해 기술을 사용하는 말

  1. 동적 서브 클래스를 생성 - NSKVONotifying_xxx
  2. 동적 setter 메소드를 추가
  3. 동적으로 클래스 메소드를 추가
  4. 동적으로 할당 해제의 방법을 추가
  5. 수동 관찰을 엽니 다
  6. 우리의 원래의 클래스 NEWVALUE에 메시지
  7. 메시지 - 응답 콜백 메소드

    2.2 원리

검증 :

 self.person = [[LJLKVOPerson alloc] init];
//1、下一行下断点
 [self.person addObserver:self
               forKeyPath:@"name"
                  options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                  context:personNameContext];
//2、下一行下断点
 self.person.name = @"liujilou";

상단은 다음에서 llbd 디버깅을 표시 코드에 브레이크 포인트

//    LLBD调试
//    1位置断点
(lldb) po object_getClassName(self.person)
"LJLKVOPerson"

//    走到2位置断点
(lldb) po object_getClassName(self.person)
"NSKVONotifying_LJLKVOPerson"

II를 확인합니다 :

    LJLKVOPerson * person = [[LJLKVOPerson alloc] init];
    [self printClasses:[LJLKVOPerson class]];
    [person addObserver:self
             forKeyPath:@"name"
                options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
                context:NULL];
    [self printClasses:[LJLKVOPerson class]];
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
    
    // 注册类的总数
    int count = objc_getClassList(NULL, 0);
    // 创建一个数组, 其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 获取所有已注册的类
    Class* classes = (Class*)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i<count; i++) {
//        判断cls 是否等于classes[i] 的父类
        if (cls == class_getSuperclass(classes[i])) {
//            将cls 的所有子类添加进来
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes = %@", mArray);
}
2020-03-07 22:31:31.788378+0800 filedome[29720:765937] classes = (
LJLKVOPerson
                                                                  )
2020-03-07 22:31:31.799907+0800 filedome[29720:765937] classes = (
LJLKVOPerson,
"NSKVONotifying_LJLKVOPerson"
                                                                  )
    

위의 의해 두 검증, 우리는 알 수 있습니다 :

  1. 실제로 중산층을 생성 : NSKVONotifying_LJLKVOPerson
  2. NSKVONotifying_LJLKVOPerson 상속과 LJLKVOPerson
  3. 아이의 self.person뿐만 아니라 중산층의 객체를 가리 킵니다.

서브 클래스, 서브 클래스 동적 연구를 공부하기 시작 : - 방법 - ISA 슈퍼 클래스 cache_t 비트 변수

의 뒷면을 확인하는 프로세스를 계속하자 :

    self.person = [[LJLKVOPerson alloc] init];
    [self printClasses:[LJLKVOPerson class]];
    [self printClassAllMethod:[LJLKVOPerson class]];
    [self.person addObserver:self
             forKeyPath:@"name"
                options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
                context:NULL];
    [self printClasses:[LJLKVOPerson class]];
    [self printClassAllMethod:NSClassFromString(@"NSKVONotifying_LJLKVOPerson")];
#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
    NSLog(@"*********************");
    unsigned int count = 0;
//  需要导入  #import <objc/runtime.h>
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}
    2020-03-07 22:59:18.260869+0800 filedome[30061:779679] classes = (
    LJLKVOPerson
                                                                      )
    2020-03-07 22:59:18.261029+0800 filedome[30061:779679] *********************
    2020-03-07 22:59:18.261129+0800 filedome[30061:779679] kvoArray-0x10b30e410
    2020-03-07 22:59:18.261252+0800 filedome[30061:779679] setKvoArray:-0x10b30e430
    2020-03-07 22:59:18.261348+0800 filedome[30061:779679] .cxx_destruct-0x10b30e470
    2020-03-07 22:59:18.261437+0800 filedome[30061:779679] name-0x10b30e3e0
    2020-03-07 22:59:18.261522+0800 filedome[30061:779679] setName:-0x10b30e340
    2020-03-07 22:59:18.272516+0800 filedome[30061:779679] classes = (
     LJLKVOPerson,
     "NSKVONotifying_LJLKVOPerson"
                                                                      )
    2020-03-07 22:59:18.272853+0800 filedome[30061:779679] *********************
    2020-03-07 22:59:18.273394+0800 filedome[30061:779679] setName:-0x10bfb0b5e
    2020-03-07 22:59:18.273505+0800 filedome[30061:779679] class-0x10bfaf592
    2020-03-07 22:59:18.273672+0800 filedome[30061:779679] dealloc-0x10bfaf336
    2020-03-07 22:59:18.273793+0800 filedome[30061:779679] _isKVOA-0x10bfaf32e

위의 인쇄에서 NSKVONotifying_LJLKVOPerson 부모 LJLKVOPerson를 다시 알고

  1. 에서는 setName (세터)
  2. 수업
  3. 할당 해제
  4. _isKVOA

이러한 방법. 이 곳은 자신의 진짜 방법을 인쇄 할 수 있습니다. 자신의 부모를 인쇄 할 수있는 방법이되지 않습니다, 그것은 다시 작성하는이 방법을 인쇄 증명할 수

리소스를 해제하기 위해 할당 해제의 방법을 다시 작성합니다.

이 개인 방법은 클래스 KVO를 나타내는 데 사용되는 다시 _isKVOA하는 클래스를 주장하는 메커니즘이다.


    세터는 관찰

        시청자의 클래스 인스턴스가 처음 등록되는 경우, 시스템은 다음을 수행한다
    NSKVONotifying_xxx : 1, 동적으로 생성이 클래스의 중간 상속을
    2 중간 클래스 (ISA-원래 물체 점을 수정 ISA ) 스위 즐링
    . 3, 대체 서브 - 클래스 일본어 클래스, 서브 클래스에있어서 여전히 복귀하지
    4 방법 오버라이드 -dealloc
    해당 속성 세터 키 패스 재기록 5
    방법을 추가 6 -_isKVOA

이 클래스 나 메소드 반환 NSKVONotifying_xxx (LJLKVOPerson)는 LJLKVOPerson (). ) (우리에게 작업이나 LJLKVOPerson에게

중산층을 해제 전화를 해제하는 데 필요한 곳에 할당 해제


돌아올 여부를 관찰 ISA 제거?

그런 다음 더 이상 관찰 ISA를 제거 NSKVONotifying_xxx에게 포인트 백 XXX 포인트

확인 :

- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"name"];
}

전에 콘솔을 제거하고 브레이크 포인트 후 것은 알고 인쇄 할 수 있습니다 :

//在dealloc 移除观察者之前打断点
po object_getClassName(self.person)
"NSKVONotifying_LJLKVOPerson"

//移除观察者之后打断点,然后 LLBD
po object_getClassName(self.person)
"LJLKVOPerson"
//可以发现isa 又指向了 LJLKVOPerson

 

5 : 파괴 된 경우에 볼 수있는 아이의 제거 후 동적 중산층?

아니 다음 사용을 용이하게하기 위해, 파괴. 당신이 모든 시간을 파괴 제거하면, 너무 느린 만들 등록.

확인 :

쓰기 등록 연속 LJLKVOViewController의 ViewController의 관찰, 때 제거 관찰 다음의 ViewController 페이지로 돌아갑니다.

의 ViewController에 다음 코드를 추가합니다. 이 NSKVONotifying_LJLKVOPerson 경우 관찰자를 제거한 후 순회 서브 클래스 LJLKVOPerson 클래스를 확인합니다.

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self printClasses:[LJLKVOPerson class]];
}
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
    
    // 注册类的总数
    int count = objc_getClassList(NULL, 0);
    // 创建一个数组, 其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 获取所有已注册的类
    Class* classes = (Class*)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i<count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"LJLKVOViewController:classes = %@", mArray);
}
2020-03-08 01:40:23.120880+0800 002---KVO原理探讨[31915:843163] LJLKVOViewController:classes = (
    LJLKVOPerson,
    "NSKVONotifying_LJLKVOPerson",
    LGStudent
)

그것은 볼 수있는, 관찰자를 제거한 후, 동적 서브 클래스는 파괴되지 않습니다.

짧게 요약한다 :

제 1 등록 관찰자의 클래스 인스턴스는 시스템이 수행 할 때 다음의
    (1)은, 원래의 클래스에서 동적으로 생성 된 중간 클래스 상속 NSKVONotifying_xxx
    2 중간 클래스 NSKVONotifying_xxx (ISA-스위 즐링) 원래 목적 지점을 수정할 사
    3. 서브 오버라이드 클래스 방법이 여전히 원래의 클래스로 돌아가서 서브보다는
    4 오버라이드 할당 해제의 방법
    (5)는 세터를 오버라이드 키 패스 해당 속성은 (는 성질을 관찰 할 수 있지만 관측 인스턴스 변수)
    _ 6은 용액에 isKVOA 방법


     KVO 원리
     1 : 동적 하위 카테고리를 생성 : NSKVONotifying_xxx
     2 : 세터 관찰 (관찰을 등록 아니지만 관측 인스턴스 변수 일 수 있음)
     3 : 동적 서브 많은 메소드 setNickName (세터) 클래스의 할당 해제, _isKVOA을 대체
     . (4) : ISA 때 돌아올 관측 지점을 제거
     동적 서브 클래스가 파괴되지 않습니다 : 5

 

KVO의 장점과 단점

장점 :

  1. 두 객체 사이의 동기화를 달성하는 간단한 방법을 제공 할 수
  2. 우리는 응답에 해당 개체의 내부 상태의 변화가 아닌 개체를 만들 수 있습니다 및 내부 객체의 구현을 변경할 필요가 없습니다. 최신 값은 호텔과 이전 값의 뷰를 제공 할 수 있습니다.
  3. 특성을 관찰 할 수있는 키 경로로, 그것은 또한 중첩 된 개체를 관찰 할 수있다
  4. 허가 관측에 추가 코드가 관찰되지 될 수 있기 때문에 관찰 객체의 완전한 추상화,

단점 :

  1. 이 때문에 관측 세터 방법은 특성이 관찰 인스턴스 변수가 될 수있다 관찰
  2. 코드를 관찰하기 위해 우리를 이끌 것이다 재산의 재건은 더 이상 사용할 수 없습니다
  3. 출시 관찰자 관찰자 제거, 또는 어떤 숨겨진 오류 여부를해야하는 경우
게시 83 개 원래 기사 · 원의 찬양 (12) · 전망 180 000 +

추천

출처blog.csdn.net/shengdaVolleyball/article/details/104725189