[원본] 아이폰 OS 개발자 고급 (당나라 치아 오) 연구 노트 (B)

파트 III : 기본 원칙의 아이폰 OS 개발

1, 오브젝티브 C 개체 모델

1.1 ISA 포인터

코드의 NSObject.h 부분 :

NS_ROOT_CLASS
@interface NSObject <NSObject> {
    Class isa;
}

코드의 objc.h 부분 :

typedef struct objc_class *Class;
typedet struct objc_object {
    Class isa;
} *id;

각 객체는 객체의 포인트 클래스를 포인터라는 이름의 ISA있다

다음 흐름도를 ISA 포인터 :

영상

C 언어 구조 (구조체)로서 클래스, ISA 포인터 변수가이 구조의 제 1 부재 인 경우, 클래스 변수의 다른 멤버들은 차례로 배치되는 구조

순서 :
|. 1 | ISA 포인터 |
| --- | --- |
| 2 | NSObject의 멤버 변수 |
. | 3 | NSObject의 서브 클래스 멤버 변수 |
. | 4 | 서브 클래스 NSObject의 서브 클래스 멤버 변수 |
| ... | ... |
|은 n - 1 | 부모 클래스의 멤버 변수 |
| N- 형 | 클래스 자체 멤버 변수 |

간단한 예제 코드는 상속 :

@interface Father : NSObject {
    int _father;
}

@end

@implementation Father
@end

@interface Child : Father {
    int _child;
}

@end

@implementation Child
@end

엑스 코드에서, 우리는이 구조가 상기와 일치하는지 다음 스크린 샷 참조

영상

메모리에 배치되는 오브젝트 구조로 볼 수 있기 때문에, 구조의 크기는 동적 런타임 객체 멤버 변수를 증가시킬 수없고, 동적 변경되지 않는다.

이 방법은 정의 된 개체 클래스의 가변 영역에 저장된다.
다음에 Objective-C 1.0, 우리는 방법의 정의 목록이라고 볼 수 있습니다 methodLists포인터
포인터의 포인터 값을 수정하여, 동적으로 단일 클래스로 멤버 메소드를 추가 할 수 Category의 원칙의 실현

오브젝티브 C 1.0 objc_class 코드

struct objc_class {
    Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super _class
    const char *name
    long version
    long info
    long instance_size
    struct objc_ivar_list *ivars
    struvt objc_method_list **methodLists
    struct objc_cache *cache
    struct objc_protocol_list *protocols
#endIf
} OBJC2_UNAVAILABLE

1.2 동적으로 생성 개체

#import <objc/runtime.h>

...

- (void)dynamicCreateClass {
    // 创建一个名为CustomView的类,它是UIView的子类
    Class newClass = objc_allocateClassPair([UIView class], "CustomView", 0);
    // 为这个类增加一个report的方法
    class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
    // 注册该类
    objc_registerClassPair(newClass);
    
    // 创建一个newClass的实例对象
    id instanceOfNewClass = [[newClass alloc] init];
    // 调用report方法
    [instanceOfNewClass performSelector:@selector(report)];
}

void ReportFunction(id self, SEL _cmd) {
    NSLog(@"This object is %p", self);
    NSLog(@"Class is %@, and super is %@", [self class], [self superclass]);
    Class currentClass = [self class];
    for (int i = 1; i < 5; i++) {
        NSLog(@"Following the isa pointer %d times gives %@ = %p", i, currentClass, currentClass);
        // 获取对象的isa指针所指向的对象
        currentClass = object_getClass(currentClass);
    }
    NSLog(@"NSObject class is %@ = %p", [NSObject class], [NSObject class]);
    NSLog(@"NSObject meta class is %@ = %p", object_getClass([NSObject class]), object_getClass([NSObject class]));
}

코드 키 포인트 :

  1. import runtime관련 헤더 objc/runtime.h파일 : .
  2. 사용 objc_allocateClassPair방법은 새로운 클래스를 생성합니다.
  3. 사용 class_addMethod방법은 클래스에 새로운 방법을 추가 할 수 있습니다.
  4. 사용하여 objc_registerClassPair새로운 클래스 메소드를 등록 할 수 있습니다.
  5. 사용 objc_getClass방법은 객체 ISA 객체에 대한 포인터를 얻을 수있다.

1.3 방법 (방법 스위 즐링) API 설명을 절환

오브젝티브 C는 다음과 같은 동적 대체 클래스 또는 인스턴스 메소드를 구현하는 API를 제공합니다 :

  • class_replaceMethod 또한 정의 된 클래스 메소드
class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)
  • method_exchangeImplementations 두 개의 스위칭 구현 방법
method_exchangeImplementations(Method  _Nonnull m1, Method  _Nonnull m2)
  • method_setImplementation 구현 방법이 제공된다
method_setImplementation(Method  _Nonnull m, IMP  _Nonnull imp)

비교 :

  • class_replaceMethod당신이 발견되지 않는 클래스의 원래의 방법을 대체 할 때, 방법은 호출 class_addMethod뿐만 아니라,이 때문에, 클래스에 새로운 메소드를 추가 class_replaceMethod하면 통과 할 필요가 호출 할 때 type매개 변수를, method_exchangeImplementations그리고 method_setImplementation필요가 없습니다
  • method_exchangeImplementations 내부 구하도록 구현하는 두 가지 방법이 있으며, 다음 교환

아래 문서 :

영상

사용 시나리오 :

  • class_replaceMethod 프로세스는 이러한 방법을 사용하는 것은 고려 될 수있는 경우 존재하지 않을 수 교체해야하는 경우.
  • method_exchangeImplementations 두 가지 방법을 사용하여 구현되는 경우 교환되어야한다.
  • method_setImplementation 방법으로 구현을 설정해야합니다에만 사용하는 경우는 간단한 사용법이다.

2, 태그 포인터 개체

2.1 원래 시스템 문제

64 비트 프로그램 전이 문제로 32 비트 :

  • 한 가지 문제 : 메모리는 두 배.
    iOS의 데이터 유형에서, 많은 데이터 타입은 CPU의 결정이 차지하는 메모리의 비트 수에 기초한다. 이어서, 64 비트 프로그램에 32 비트의 프로그램 전환은, 메모리의 데이터 형식이 배가 될 때. 아래 그림과 같이 :
    영상
  • 두 번째 문제 : 효율성.
    저장하고의 NSNumber 개체에 액세스하기 위해, 우리는 또한 참조 카운트를 유지하면서, 힙 메모리를 할당 할 필요가 수명주기를 관리 할 수 있습니다. 이러한 모든 운영 효율의 손실이 발생, 추가 프로그램 로직을 추가 한

2.2 태그 포인터 소개

태그 포인터는 제기 된 문제를 해결하는 것입니다.
원리 : 개체에 대한 포인터가 두 부분으로 분할됩니다. 도는 다음과 같습니다 :

영상

아래와 같이 소개 한 후, 메모리를 변경 :

영상

특징 :

  1. 예를 들어 작은 물체를 저장하도록 설계 NSNumber하고,NSDate
  2. 주소 포인터의 값은 더 이상 없지만, 진정한 가치. 그래서, 사실 더 이상 객체 없으며, 객체는 단지 일반 변수의 사람 '피부'입니다. 그래서,이 메모리 힙에 저장되지 않습니다, 당신은 필요하지 않습니다 mallocfree
  3. 106 배 빠른 이전보다 만들 때 읽어 메모리의 효율성에 이전에 세 번있다

참고 : Tagged Pointer이 아닌 실제 개체하지만, 의사의 객체가 아닌 isa포인터를

ISA에서의 포인터 (64)의 2.2 최적화

(32) 환경 :

오브젝트의 참조 카운트는 외부 테이블에 저장된다.

Retain 운영은 다음의 다섯 단계를 포함한다 :

  1. 기록 글로벌 참조 횟수 취득 hash시트.
  2. 스레드 안전을 위해에 hash테이블 잠금.
  3. 대상 객체 참조 카운트를 찾을 수 있습니다.
  4. 레퍼런스 카운트에 1을 더한, 재기록 hash테이블.
  5. 받는 hash테이블 잠금이 해제됩니다.

스레드 안전을 위해의 필요성 hash성능 관점에서 테이블 잠금은 매우 좋지 않습니다.

(64) 환경 :

ISA 포인터는 64 비트이다. 비트 당도의 의미는 다음과 같습니다

비트 비트 변수 이름 의미
1 비트 색인 0은 정상 ISA를 나타내고, 1은 태그 포인터 나타낸다
1 비트 has_assoc 객체가 아닌 경우, 메모리 소멸자의 빠른 출시를 객체를 연결했다 여부를 나타냅니다
1 비트 has_cxx_dtor 이 객체가 아니라면, 메모리 소멸자의 빠른 릴리스 ARC 또는 C ++ 소멸자가 있는지 여부를 나타냅니다
30 비트 shiftcls 포인터 클래스
9 비트 마법 이 값은 객체가 초기화 디버깅을 완료 여부를 구별하기 위해, 0xd2 고정
1 비트 weakly_referenced 객체는 객체가 너무 약이 있는지 여부는 그렇지 않은 경우, 메모리 소멸자의 빠른 릴리스를 나타냅니다
1 비트 할당 해제 이 객체가 파괴되었는지 여부를 나타냅니다
1 비트 has_sidetable_rc 참조 카운트는 개체가 ISA에 직접 저장할 수 너무 큰 것을 나타냅니다 여부
19 비트 extra_rc 그 표시 예를 들어 이상으로하는 오브젝트의 참조 카운트는 오브젝트의 레퍼런스 카운트는 6, 5 extra_rc 값이면

extra_rc는 19 비트 객체의 참조 카운트를 유지하는 데 사용,이 작업에 대한 참조 카운트는 기능을 수정해야합니다.

Retain 운영은 다음의 다섯 단계를 포함한다 :

  1. 그렇지 않으면 2 단계로 이동, 참조 카운트가 변수 ISA에 저장되어 있지 않은 경우, 이전 단계가 사용되어 있는지, ISA 포인터 플래그 이상 확인합니다.
  2. 현재 개체가 해제되어 있는지 확인하고, 만약 그렇다면, 아무것도하지 않습니다.
  3. 객체의 참조 횟수를 증가하지만, 즉시 ISA 변수에 다시 기록되지.
  4. 그렇지 않으면 5 단계로 이동하여 상기 기준 카운트 값은, 이전의 방식으로 전환하지 않는 경우 (19), 표현 될 수 증가 여부를 점검하기.
  5. 원자 기입 동작을 실시, ISA의 값은 라이트 백.

3. block개체 모델

3.1 정의 :

애플의 오픈 소스 프로젝트의 LLVM은 ( https://llvm.org/svn/llvm-project/compiler-rt/tags/Apple/Libcompiler_rt-10/BlocksRuntime/Block_private.h ), 우리가 볼 수있는 block데이터 구조를 아래와 같이이 정의된다 :

영상

다음과 같이 대응하는 구조가 정의된다 :

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables */
};

구성의 block예 (6) 부분 :

  1. isa 포인터는, 모든 객체는 객체 관련 기능의 구현에 대한 포인터를 가지고있다.
  2. flags프레스 bit비트들의 일부 도시 block후술 부가 정보의 block copy구현 코드는 변수의 사용을 알 수있다.
  3. reserved 예약 변수.
  4. invoke특정에 함수 포인터 block기능은 주소를 달성하기 위해 호출합니다.
  5. descriptor이는 것을 나타내는 block추가 정보를 주로 설명하는 size크기 및 copy포인터 기능과 폐기.
  6. variable capture 이상의 변수, 로컬 변수 블록 외부 액세스 구조에 이러한 변수 (또는 변수의 주소) 때문이다.

3.2 카테고리 :

block 유형 :

  1. _NSConcreteGlobalBlock글로벌 정적 block, 외부 변수에 액세스하지 않습니다.
  2. _NSConcreteStackBlock스택에 저장된 block함수가 반환이 파괴 될 것이다.
  3. _NSConcreteMallocBlock힙에 저장된 block참조 횟수가 0이 파괴 될 것입니다.

참고 : clang분석의 block실현을

clang명령을 제공합니다, 당신은 할 수 있습니다 Objective-CC 언어를 다시 소스.
명령은 다음과 같습니다clang -rewrite-objc block.c

3.2.1 NSConcreteGlobalBlock유형 block구현

달성하기 위해, block1.c 소스 파일의 이름을 만듭니다

#include <stdio.h>

int main(int argc, char const *argv[]) {
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

명령 줄을 입력 clang -rewrite-objc block1.c, 당신은 디렉토리에 그 소리 출력을 "block1.cpp"라는 파일을 볼 수있는 파일 블록은 C 언어로 구현된다.
다음과 같이 키 코드는 인용 :

...
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
...
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 printf("Hello, World!\n"); }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char const *argv[])
{
    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)) ();
    return 0;
}

코드, __main_block_impl_0우리가 볼 수있는 블록을 실현하는 것입니다 :

  1. A는 block그것은 주로 구성되어, 실제로 목적 isa중 하나 impldescriptor조성물.
  2. 여기 저기 열린 없기 때문에 ARC, 그래서 우리는 볼 isa여전히 점을 _NSConcreteStackBlock. 그러나 개방에 ARCblock그것은해야 _NSConcreteGlobalBlock처럼.
  3. impl실제 함수 포인터인가,이 경우에 가리 킵니다 __main_block_func_0. 여기 impl앞서 언급에 해당하는 invoke변수는하지만, clang컴파일러는 그냥 같은 변수를 이름.
  4. descriptor이 전류를 설명하는 데 사용되는 block필요한 구조의 크기를 포함하여, 추가 정보 capturedispose변수의리스트 등을 포함한다.
    각각의 크기를 보존해야 할 필요성으로 인해 구조 block것이다 capture변수에 추가 될 일부 변수 __main_block_impl_0구조체, 그 체적이 증가한다.

특정 문서를 참조하십시오 https://github.com/AlonerOwl/OC_Block/tree/master/NSConcreteGlobalBlock

3.2.2 NSConcreteStackBlock유형 block구현

달성하기 위해, block1.c 소스 파일의 이름을 만듭니다

#include <stdio.h>

int main(int argc, char const *argv[]) {
    int a = 100;
    void (^block2)(void) = ^{ // block 实现
        printf("%d\n", a);
    };
    block2();
    
    return 0;
}

그 소리 후 :

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

        printf("%d\n", a);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char const *argv[]) {
    int a = 100;
    void (*block2)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);

    return 0;
}

이 경우, 우리는 볼 수 있습니다 :

  1. 본 실시 예에서, isa포인트 _NSConcreteStackBlock분배 스택의 예를 설명한다.
  2. __main_block_impl_0변수를 추가하려면 ablock참조 변수 a, 그것은 성명에서 실제로 block, 복사 할 때 __main_block_impl_0변수 구조체 a.
  3. __main_block_impl_0하나 개의 변수의 증가로 인해 a, 구조가 크게되고, 구조체의 크기는에 기록된다 __main_block_desc_0 中.

우리는 변수 전면 증가, 위의 코드를 수정 __block키워드 :

#include <stdio.h>

int main(int argc, char const *argv[]) {
    __block int i = 1024;
    void (^block2)(void) = ^{ // block 实现
        printf("%d\n", i);
        i = 1023;
    };
    block2();
    
    return 0;
}

그 소리 후, 아주 다른 전 :

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

        printf("%d\n", (i->__forwarding->i));
        (i->__forwarding->i) = 1023;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char const *argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
    void (*block2)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);

    return 0;
}

우리는 코드에서 볼 수있다 :

  1. 소스는 이름이 추가 __Block_byref_i_0우리가 저장하고자, 구조 capture및 변수를 수정 i.
  2. __main_block_impl_0이 인용 __Block_byref_i_0은 외부 수정 변수의 역할을 할 수 있도록하는 구조 포인터.
  3. __Block_byref_i_0구조적 본체와 isa, 그 오브젝트의 설명이다.
  4. 우리는 책임이해야 할 __Block_byref_i_0모든 메모리 관리와 관련된 구조 __main_block_desc_0에 추가 copy하고 dispose이전과 호 참조 카운트 후 적절한 변수를 수정하기위한 함수 포인터.

특정 문서를 참조하십시오 https://github.com/AlonerOwl/OC_Block/tree/master/NSConcreteStackBlock

요약 :
block 외부 변수를 들면, 비 __block변형 변수는 단순히에 복사 block, 액세스 달성하는 데이터 구조를 __block수정 변수에 액세스를 얻기 위해 기준 가변속 주소를 복사한다.

3.2.3 NSConcreteMallocBlock유형 block구현

블록의 NSConcreteMallocBlock 유형은 일반적으로 시스템은 블록의 NSConcreteMallocBlock 유형의 결과로, 힙이 블록을 복사합니다,이 블록 복사 방법을 호출하는 경우에만, 소스 코드에 직접 표시되지 않습니다.

참고 :에서 ARC개방의 경우 만 존재합니다 NSConcreteGlobalBlockNSConcreteMallocBlock유형 block. 원래는 NSConcreteStackBlockNSConcreteMallocBlock대신 수행.

추천

출처www.cnblogs.com/gfxxbk/p/11738758.html