iOS 메시지 전송 및 전송 예시 상세 설명

머리말
Objective-C 는 동적 언어 로 많은 정적 언어 를 컴 파일 과 링크 시기 에 하 는 일 을 실행 할 때 처리 합 니 다.이런 특성 을 갖 출 수 있 는 이 유 는 런 타임 이라는 라 이브 러 리 를 떠 날 수 없다.런 타임 은 운영 시기 에 어떻게 호출 방법 을 찾 느 냐 는 문 제 를 잘 해결 했다.다음은 더 이상 할 말 이 없 으 니 같이 공부 합 시다.
메시지 전송
Objective-C 에서 대상 에 게 메 시 지 를 보 내 는 방법 을 호출 합 니 다.

// MyClass  
@interface MyClass: NSObject
- (void)printLog;
@end
@implementation MyClass
- (void)printLog {
NSLog(@"print log !");
}
@end
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
//   : print log !
위 코드 의[my Class printLog]도 이렇게 쓸 수 있 습 니 다.

((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
[my Class printLog]컴 파일 을 거 친 후 obbc 를 호출 합 니 다.msgSend 방법.
이 방법의 문서 정 의 를 봅 시다.

id objc_msgSend(id self, SEL op, ...);
self:메시지 수신 자 op:메시지 의 방법 명,C 문자열...:매개 변수 목록
Runtime 은 어떻게 실례 방법의 구체 적 인 실현 을 찾 습 니까?
기초 개념
말 하기 전에 우 리 는 먼저 몇 가지 기초 개념 을 이해 해 야 한다.Objective-C 는 표면적 으로 대상 을 향 한 언어 이 고 대상 은 실례 대상,클래스 대상,클래스 대상 과 루트 대상 으로 나 뉜 다.그것들 은 isa 라 는 지침 을 통 해 연 결 됩 니 다.구체 적 인 관 계 는 다음 과 같 습 니 다.

우리 가 위의 코드 를 예 로 들 면:

MyClass *myClass = [[MyClass alloc] init];
상호 간 의 관 계 를 정리 하 다.
  • my Class 는 인 스 턴 스 대상
  • MyClass 는 클래스 대상
  • MyClass 의 원류 가 NSObject 의 원류
  • NSObject 는 Root class(class)
  • NSObject 의 슈퍼 클 라 스 는 nil
  • NSObject 의 원 류 는 바로 자신 이다
  • NSObject 의 슈퍼 클 라 스 가 NSObject
  • 입 니 다.
    위의 그림 의 위치 관 계 는 다음 과 같 습 니 다.

    이어서 우 리 는 코드 로 위의 관 계 를 검증 합 니 다.
    
    MyClass *myClass = [[MyClass alloc] init];
    
    Class class = [myClass class];
    Class metaClass = object_getClass(class);
    Class metaOfMetaClass = object_getClass(metaClass);
    Class rootMetaClass = object_getClass(metaOfMetaClass);
    Class superclass = class_getSuperclass(class);
    Class superOfSuperclass = class_getSuperclass(superclass);
    Class superOfMetaOfSuperclass = class_getSuperclass(object_getClass(superclass));
    
    NSLog(@"MyClass      :%p",myClass);
    NSLog(@"MyClass     :%p",class);
    NSLog(@"MyClass      :%p",metaClass);
    NSLog(@"MyClass           :%p",metaOfMetaClass);
    NSLog(@"MyClass       :%p",rootMetaClass);
    NSLog(@"MyClass    :%@",class_getSuperclass(class));
    NSLog(@"MyClass       :%@",superOfSuperclass);
    NSLog(@"MyClass          :%@",superOfMetaOfSuperclass);
    
    NSLog(@"NSObject      :%p",object_getClass([NSObject class]));
    NSLog(@"NSObject    :%@",[[NSObject class] superclass]);
    NSLog(@"NSObject         :%@",[object_getClass([NSObject class]) superclass]);
    
    //  :
    MyClass      :0x60c00000b8d0
    MyClass     :0x109ae3fd0
    MyClass      :****0x109ae3fa8
    MyClass           :****0x10ab02e58**
    MyClass       :0x10ab02e58
    MyClass    :NSObject
    MyClass       :(null)
    MyClass          :NSObject
    NSObject      :0x10ab02e58
    NSObject    :(null)
    NSObject         :NSObject
    출력 결 과 는 우리 의 결론 에 완전히 부합 한 다 는 것 을 알 수 있 습 니 다!
    이제 우 리 는 각종 대상 간 의 관 계 를 알 수 있다.
    인 스 턴 스 대상 은 isa 지침 을 통 해 클래스 대상 Class 를 찾 습 니 다.클래스 대상 역시 isa 지침 을 통 해 원 류 대상 을 찾 습 니 다.원류 대상 도 isa 지침 을 통 해 뿌리 원류 대상 을 찾 습 니 다.마지막 으로 루트 대상 의 isa 지침 은 자신 을 가리킨다.NSObject 는 전체 메시지 체제 의 핵심 이 고 절대 다수의 대상 이 이 를 계승 한 다 는 것 을 알 수 있다.
    흐름 찾기
    위 에서 언급 한 바 와 같이 Objective-C 방법 은 obbc 로 컴 파일 됩 니 다.msgSend,이 함 수 는 두 개의 기본 매개 변수,id 형식의 self,SEL 형식의 op 이 있 습 니 다.우리 먼저 id 의 정 의 를 봅 시다.
    
    typedef struct objc_object *id;
    struct objc_object {
     Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    };
    우 리 는 볼 수 있다.object 구조 체 에는 Class 형식 을 가리 키 는 isa 포인터 만 있 습 니 다.
    Class 의 정 의 를 다시 봅 시다.
    
    struct objc_class {
     Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
     Class _Nullable super_class OBJC2_UNAVAILABLE;
     const char * _Nonnull name OBJC2_UNAVAILABLE;
     long version OBJC2_UNAVAILABLE;
     long info OBJC2_UNAVAILABLE;
     long instance_size OBJC2_UNAVAILABLE;
     struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
     struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
     struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
     struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;
    안에 많은 인자 가 있 습 니 다.이 줄 을 눈 에 띄 게 볼 수 있 습 니 다.
    
    struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
    이름 을 봐 도 쉽게 이해 할 수 있 습 니 다.이 methodLists 는 방법 목록 을 저장 하 는 데 사 용 됩 니 다.우리 다시 보 자 obbcmethod_list 이 구조 체:
    
    struct objc_method_list {
     struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
     
     int method_count OBJC2_UNAVAILABLE;
    #ifdef __LP64__
     int space OBJC2_UNAVAILABLE;
    #endif
     /* variable length structure */
     struct objc_method method_list[1] OBJC2_UNAVAILABLE;
    }
    안의 obbcmethod,즉 우리 가 잘 아 는 Method:
    
    struct objc_method {
     SEL _Nonnull method_name OBJC2_UNAVAILABLE;
     char * _Nullable method_types OBJC2_UNAVAILABLE;
     IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
    }
    Method 에 세 개의 인자 가 저장 되 어 있 습 니 다:
  • 방법의 명칭
  • 방법의 유형
  • 방법의 구체 적 인 실현 은 IMP 포인터 가 가리킨다
  • 층 층 이 발굴 한 결과 우 리 는 실례 대상 호출 방법의 대체적인 논 리 를 이해 할 수 있다.
    
    MyClass *myClass = [[MyClass alloc] init];
    [myClass printLog];
  • 먼저 컴 파일 됨  ((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
  • my Class 에 들 어간 isa 지침 을 따라 my Class 의 클래스 대상(Class),즉 MyClass
  • 을 찾 습 니 다.
  • 이 어 MyClass 의 방법 목록 methodLists 에서 해당 하 는 Method
  • 를 찾 습 니 다.
  • 마지막 으로 Method 의 IMP 지침 을 찾 아 구체 적 인 실현
  • 클래스 대상 의 클래스 방법 은 어떻게 찾 아 실행 합 니까?
    위의 글 에서 우 리 는 인 스 턴 스 대상 이 isa 지침 을 통 해 클래스 대상(Class)에 저 장 된 방법 목록 의 구체 적 인 실현 을 찾 았 다 는 것 을 알 고 있 습 니 다.
    예 를 들 면:
    
    MyClass *myClass = [[MyClass alloc] init];
    [myClass printLog];
    printLog 방법 은 MyClass 에 저 장 된 것 으로 이해 할 수 있 습 니 다.
    그렇다면 이런 방법 이 라면 어디 에 저장 되 어 있 을 까?
    클래스 를 돌 이 켜 보 겠 습 니 다.  정의:
    
    struct objc_class {
     Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
     Class _Nullable super_class OBJC2_UNAVAILABLE;
     const char * _Nonnull name OBJC2_UNAVAILABLE;
     long version OBJC2_UNAVAILABLE;
     long info OBJC2_UNAVAILABLE;
     long instance_size OBJC2_UNAVAILABLE;
     struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
     struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
     struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
     struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;
    이 줄 을 발견 할 수 있 습 니 다:
    
    Class _Nonnull isa OBJC_ISA_AVAILABILITY;
    이곳 의 isa 역시 Class 를 가리 키 는 지침 입 니 다.위의 글 에서 우 리 는 유형 대상 의 isa 지침 이 원류 대상 을 가리 키 는 것 임 을 알 게 되 었 다.그렇게 어렵 지 않다.
    클래스 대상 의 클래스 방법 은 클래스 대상 에 저 장 됩 니 다!
    클래스 대상 과 클래스 대상 은 모두  Class 유형 은 서비스의 대상 이 다 를 뿐 입 니 다.원류 대상 을 찾 으 면 자 연 스 럽 게 원류 대상 중의 methodLists 를 찾 을 수 있 습 니 다.다음은 실례 대상 의 방법 과 같은 절 차 를 찾 을 수 있 습 니 다.
    부모 클래스 에 대하 여(superclass)
    Objective-C 에서 하위 클래스 는 하나의 방법 을 호출 합 니 다.하위 클래스 가 실현 되 지 않 으 면 부모 클래스 가 실 현 됩 니 다.부모 클래스 의 실현 을 호출 합 니 다.위의 글 에서 methodLists 를 찾 은 후 Method 를 찾 는 과정 은 다음 과 같 습 니 다.

    어떻게 방법 찾기 의 효율 을 높 입 니까?
    위의 글 에서 우 리 는 방법 이 isa 지침 을 통 해 Class 의 methodLists 를 찾 는 것 임 을 대충 알 고 있 습 니 다.만약 하위 클래스 가 대응 하 는 방법 을 실현 하지 못 한다 면,부 류 를 따라 찾 아 볼 것 이다.전체 공사 에 수만 억 개의 방법 이 있 을 수 있 는데 어떻게 성능 문 제 를 해결 합 니까?
    예 를 들 면:
    
    for (int i = 0; i < 100000; ++i) {
     MyClass *myObject = myObjects[i];
     [myObject methodA];
    }
    이러한 고주파 호출 methodA 는 호출 할 때마다 옮 겨 다 녀 야 한다 면 성능 이 매우 떨어진다.그래서 Class Cache 메커니즘 을 도입 했다.
    Class Cache 는 하나의 방법 이 호출 되면 나중에 호출 될 가능성 이 높다 고 생각 합 니 다.
    방법 을 찾 을 때 캐 시 에서 찾 아 직접 되 돌려 줍 니 다.찾 을 수 없습니다.Class 방법 목록 에서 찾 으 세 요.
    위의 글 에서 Class 의 정의 에서 우 리 는 발견 할 수 있 습 니 다.  cache:
    
    struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
    캐 시 는 클래스 에 존재 하 는 것 을 설명 합 니 다.각 클래스 는 하나의 방법 으로 캐 시 를 합 니 다.모든 종류의 object 가 한 부 를 저장 하 는 것 이 아 닙 니 다.
    메시지 전달
    방법 목록(methodLists)에서 해당 하 는 selector 를 찾 지 못 하면?
    
    // ViewController.m   (    myTestPrint   )
    [self performSelector:@selector(myTestPrint:) withObject:@",   !"];
    시스템 은 세 차례 의 보완 기 회 를 제공 할 것 이다.
    처음
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {} (    )
    + (BOOL)resolveClassMethod:(SEL)sel {} (   )
    이 두 가지 방법 중 하 나 는 실례 를 겨냥 한 방법 이다.유형 에 맞 는 방법반환 값 은 모두 Bool 입 니 다.
    사용 예시:
    
    // ViewController.m  
    void myMethod(id self, SEL _cmd,NSString *nub) {
     NSLog(@"ifelseboyxx%@",nub);
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
     if (sel == @selector(myTestPrint:)) {
    #pragma clang diagnostic pop
      class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
      return YES;
     }else {
      return [super resolveInstanceMethod:sel];
     }
    }
    우 리 는 resolve InstanceMethod:방법 에서 class 를 이용 해 야 합 니 다.addMethod 방법,구현 되 지 않 은 my TestPrint:my Method 에 연결 하면 퍼 가기 가 완료 되 고 마지막 으로 YES 로 돌아 갑 니 다.
    두 번 째
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {}
    이 방법 은 id 를 되 돌려 달라 고 요구 합 니 다.장면 을 사용 하 는 것 은 보통 A 류 의 특정한 방법 을 B 류 의 실현 에 전달 하 는 것 이다.
    사용 예시:
    Person 클래스 에 전송 하고 싶 은-my TestPrint:방법 중:
    
    @interface Person : NSObject
    @end
    @implementation Person
    - (void)myTestPrint:(NSString *)str {
     NSLog(@"ifelseboyxx%@",str);
    }
    @end
    
    // ViewController.m  
    - (id)forwardingTargetForSelector:(SEL)aSelector {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
     if (aSelector == @selector(myTestPrint:)) {
    #pragma clang diagnostic pop
      return [Person new];
     }else{
      return [super forwardingTargetForSelector:aSelector];
     }
    }
    세 번 째
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
    - (void)forwardInvocation:(NSInvocation *)anInvocation {}
    첫 번 째 는 한 가지 방법 으로 서명 하고 두 번 째 방법 으로 구체 적 인 실현 을 전달 해 야 한다.두 사람 은 서로 의존 하고 정확 한 방법 으로 서명 해 야 두 번 째 방법 을 실행 할 수 있다.
    이번 리 트 윗 작용 은 두 번 째 와 유사 하 게 A 류 의 어떤 방법 을 B 류 의 실현 에 리 트 윗 하 는 것 이다.다른 것 은 세 번 째 퍼 가기 가 두 번 째 퍼 가기 보다 더욱 유연 하 다 는 것 이다.forwardingTargetForSelector:한 대상 에 게 만 고정 적 으로 퍼 가기;forwardInvocation:  여러 대상 에 게 전달 할 수 있 습 니 다.
    인 스 턴 스 사용:
    Person 류 및 Animal 류 에 전달 하고 싶 은-my TestPrint:방법 중:
    
    @interface Person : NSObject
    @end
    @implementation Person
    - (void)myTestPrint:(NSString *)str {
     NSLog(@"ifelseboyxx%@",str);
    }
    @end
    
    @interface Animal : NSObject
    @end
    @implementation Animal
    - (void)myTestPrint:(NSString *)str {
     NSLog(@"tiger%@",str);
    }
    @end
    
    // ViewController.m  
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
     #pragma clang diagnostic push
     #pragma clang diagnostic ignored "-Wundeclared-selector"
     if (aSelector == @selector(myTestPrint:)) {
     #pragma clang diagnostic pop
     return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
     return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
     Person *person = [Person new];
     Animal *animal = [Animal new];
     if ([person respondsToSelector:anInvocation.selector]) {
     [anInvocation invokeWithTarget:person];
     }
     if ([animal respondsToSelector:anInvocation.selector]) {
     [anInvocation invokeWithTarget:animal];
     }
    }
    ⚠️ 세 번 째 기회 가 되 어도 해당 하 는 실현 을 찾 지 못 하면 crash:
    
    unrecognized selector sent to instance 0x7f9f817072b0
    총결산
    여기까지 우 리 는 메시지 발송 과 전달 과정 을 대충 알 수 있 습 니 다.절차 도 를 동봉 합 니 다.
    자,이상 이 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

    좋은 웹페이지 즐겨찾기