파트 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]));
}
코드 키 포인트 :
import runtime
관련 헤더objc/runtime.h
파일 : .- 사용
objc_allocateClassPair
방법은 새로운 클래스를 생성합니다. - 사용
class_addMethod
방법은 클래스에 새로운 방법을 추가 할 수 있습니다. - 사용하여
objc_registerClassPair
새로운 클래스 메소드를 등록 할 수 있습니다. - 사용
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 태그 포인터 소개
태그 포인터는 제기 된 문제를 해결하는 것입니다.
원리 : 개체에 대한 포인터가 두 부분으로 분할됩니다. 도는 다음과 같습니다 :
아래와 같이 소개 한 후, 메모리를 변경 :
특징 :
- 예를 들어 작은 물체를 저장하도록 설계
NSNumber
하고,NSDate
- 주소 포인터의 값은 더 이상 없지만, 진정한 가치. 그래서, 사실 더 이상 객체 없으며, 객체는 단지 일반 변수의 사람 '피부'입니다. 그래서,이 메모리 힙에 저장되지 않습니다, 당신은 필요하지 않습니다
malloc
및free
- 106 배 빠른 이전보다 만들 때 읽어 메모리의 효율성에 이전에 세 번있다
참고 : Tagged Pointer
이 아닌 실제 개체하지만, 의사의 객체가 아닌 isa
포인터를
ISA에서의 포인터 (64)의 2.2 최적화
(32) 환경 :
오브젝트의 참조 카운트는 외부 테이블에 저장된다.
Retain
운영은 다음의 다섯 단계를 포함한다 :
- 기록 글로벌 참조 횟수 취득
hash
시트. - 스레드 안전을 위해에
hash
테이블 잠금. - 대상 객체 참조 카운트를 찾을 수 있습니다.
- 레퍼런스 카운트에 1을 더한, 재기록
hash
테이블. - 받는
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
운영은 다음의 다섯 단계를 포함한다 :
- 그렇지 않으면 2 단계로 이동, 참조 카운트가 변수 ISA에 저장되어 있지 않은 경우, 이전 단계가 사용되어 있는지, ISA 포인터 플래그 이상 확인합니다.
- 현재 개체가 해제되어 있는지 확인하고, 만약 그렇다면, 아무것도하지 않습니다.
- 객체의 참조 횟수를 증가하지만, 즉시 ISA 변수에 다시 기록되지.
- 그렇지 않으면 5 단계로 이동하여 상기 기준 카운트 값은, 이전의 방식으로 전환하지 않는 경우 (19), 표현 될 수 증가 여부를 점검하기.
- 원자 기입 동작을 실시, 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) 부분 :
isa
포인터는, 모든 객체는 객체 관련 기능의 구현에 대한 포인터를 가지고있다.flags
프레스bit
비트들의 일부 도시block
후술 부가 정보의block copy
구현 코드는 변수의 사용을 알 수있다.reserved
예약 변수.invoke
특정에 함수 포인터block
기능은 주소를 달성하기 위해 호출합니다.descriptor
이는 것을 나타내는block
추가 정보를 주로 설명하는size
크기 및copy
포인터 기능과 폐기.variable
capture
이상의 변수, 로컬 변수 블록 외부 액세스 구조에 이러한 변수 (또는 변수의 주소) 때문이다.
3.2 카테고리 :
block
유형 :
_NSConcreteGlobalBlock
글로벌 정적block
, 외부 변수에 액세스하지 않습니다._NSConcreteStackBlock
스택에 저장된block
함수가 반환이 파괴 될 것이다._NSConcreteMallocBlock
힙에 저장된block
참조 횟수가 0이 파괴 될 것입니다.
참고 : clang
분석의 block
실현을
clang
명령을 제공합니다, 당신은 할 수 있습니다Objective-C
C 언어를 다시 소스.
명령은 다음과 같습니다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
우리가 볼 수있는 블록을 실현하는 것입니다 :
- A는
block
그것은 주로 구성되어, 실제로 목적isa
중 하나impl
와descriptor
조성물. - 여기 저기 열린 없기 때문에
ARC
, 그래서 우리는 볼isa
여전히 점을_NSConcreteStackBlock
. 그러나 개방에ARC
때block
그것은해야_NSConcreteGlobalBlock
처럼. impl
실제 함수 포인터인가,이 경우에 가리 킵니다__main_block_func_0
. 여기impl
앞서 언급에 해당하는invoke
변수는하지만,clang
컴파일러는 그냥 같은 변수를 이름.descriptor
이 전류를 설명하는 데 사용되는block
필요한 구조의 크기를 포함하여, 추가 정보capture
및dispose
변수의리스트 등을 포함한다.
각각의 크기를 보존해야 할 필요성으로 인해 구조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;
}
이 경우, 우리는 볼 수 있습니다 :
- 본 실시 예에서,
isa
포인트_NSConcreteStackBlock
분배 스택의 예를 설명한다. __main_block_impl_0
변수를 추가하려면a
에block
참조 변수a
, 그것은 성명에서 실제로block
, 복사 할 때__main_block_impl_0
변수 구조체a
.__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;
}
우리는 코드에서 볼 수있다 :
- 소스는 이름이 추가
__Block_byref_i_0
우리가 저장하고자, 구조capture
및 변수를 수정i
. __main_block_impl_0
이 인용__Block_byref_i_0
은 외부 수정 변수의 역할을 할 수 있도록하는 구조 포인터.__Block_byref_i_0
구조적 본체와isa
, 그 오브젝트의 설명이다.- 우리는 책임이해야 할
__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 유형의 결과로, 힙이 블록을 복사합니다,이 블록 복사 방법을 호출하는 경우에만, 소스 코드에 직접 표시되지 않습니다.