분류(Category)
역할:
클래스에 새 메서드를 추가할 때 OC는 모두 4가지 메서드를 제공합니다.
장점과 단점:
Category의 이점:
Category 단점:
Category 구현 원리:
Category 소스: objc-runtime-new.h와objc-runtime-new.mm 두 파일 중
Category runtime의 정의:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
통과:
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
구조체에서 이 네 가지 변수를 보면 분류는 실례 방법, 클래스 방법, 프로토콜과 속성 목록을 추가할 수 있지만 실례 변수를 추가할 수 없다. 왜냐하면 실례 변수에 대응하는 바늘 변수가 저장되어 있지 않기 때문이다.
Category 로드의 세부 단계 및 호출된 함수:
_objc_init(void)->map_images(...)->map_images_nolock(...)->_read_images(...)->remethodizeClass(Class cls)->attachCategories(Class cls, category_list *cats, bool flush_caches)->void attachLists(List* const * addedLists, uint32_t addedCount
함수 설명:
주로 Category 원리에 관련된 세 가지 방법:read_images,attachCategories,attachLists가 보자read_images 함수 원본 코드의 중요한 부분:
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
// Category discovery MUST BE LAST to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// Print preoptimization statistics
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
for (EACH_HEADER) {
if (hi->isPreoptimized()) {
_objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
"in %s", hi->fname());
}
else if (hi->info()->optimizedByDyld()) {
_objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
"in %s", hi->fname());
}
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
PreoptTotalClasses++;
if (hi->isPreoptimized()) {
PreoptOptimizedClasses++;
}
const method_list_t *mlist;
if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
}
}
_objc_inform("PREOPTIMIZATION: %zu selector references not "
"pre-optimized", UnfixedSelectors);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
PreoptOptimizedMethodLists, PreoptTotalMethodLists,
PreoptTotalMethodLists
? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists
: 0.0);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
PreoptOptimizedClasses, PreoptTotalClasses,
PreoptTotalClasses
? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
: 0.0);
_objc_inform("PREOPTIMIZATION: %zu protocol references not "
"pre-optimized", UnfixedProtocolReferences);
}
여기서 코드는 다음과 같습니다.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
//
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
//
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
주석이 있는 부분은 중요한 관건이다. 1. 귀속 분류와 목표류를 제거한다.2. 재구성 방법 목록.다음으로는 attachCategories 함수를 살펴보겠습니다.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
원본 코드를 보면 목표 클래스에서 확장된 모든 클래스의 속성, 방법, 프로토콜 목록 헤더를 하나의 목록에 꺼내서 attachLists라는 함수에 통일적으로 전달하는 것을 알 수 있다.다음은 attachLists 소스를 살펴보겠습니다.
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
여기서 강조하자면oldCount와 newCount는 각각 목표 클래스의 방법, 속성, 프로토콜 목록의 길이와 클래스의 방법, 속성, 프로토콜 목록의 길이를 대표한다.realloc 함수를 통해 메모리를 신청합니다. 여기서 realloc 신청 메모리를 사용하는 몇 가지 상황은 여러분이 직접 찾아보실 수 있습니다. 여기서 설명하지 않겠습니다. Realloc가 메모리를 다시 신청한 후에memmove와memcpy 두 함수를 사용하여 목록 내용을 우리가 다시 신청한 메모리에 넣을 수 있습니다. 여기서 알 수 있듯이 Category의 방법은 목표 클래스를 덮어쓰는 방법이 없습니다.단지 Category의 방법을 목표 클래스 방법의 앞에 두었을 뿐이다. 방법을 호출할 때 만약에 Category와 목표 클래스에 같은 이름이 있다면 시스템은 먼저 앞의 클래스에 넣는 방법을 찾을 것이다. 이것이 바로 Category에서 방법의 우선순위가 목표 클래스보다 높은 방법이다.
Category 추가 속성:
원본 코드에서 속성 목록을 가리키는 바늘 변수를 보았지만 시스템은 이 속성에 Getter와setter 함수를 생성하지 않았습니다. 실례 변수를 생성하고 Getter와setter 방법을 생성하는 수단이 필요합니다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.