iOS weak 실현 원리 weak 대상의 초기화, 인용, 방출

10134 단어
weak표가 사실hash(해시)표라는 것을 아는 사람은 드물다. Key는 대상을 가리키는 주소이고Value는 weak지침의 주소 그룹이다.더 많은 사람들이 weak이 약한 인용이라는 것만 알고 인용 대상의 계수기는 1을 추가하지 않으며 인용 대상이 풀릴 때 자동으로 nil로 설정됩니다.일반적으로 순환 인용 문제를 해결하는 데 쓰인다.하지만 이제는 이것만으로는 면접에 대응할 수 없다는 것을 알고 많은 회사들이 weak의 원리를 물어본다.weak의 원리는 무엇입니까?다음은 weak의 작업 원리를 분석해 보겠습니다.

weak 실현 원리의 개괄


런타임은 대상을 가리키는 모든 weak 바늘을 저장하는 데 사용되는 weak 테이블을 유지합니다.weak표는 사실hash(해시)표입니다. Key는 대상의 주소를 가리키고Value는 weak포인터의 주소(이 주소의 값은 대상의 포인터의 주소를 가리킨다) 그룹입니다.

weak의 실현 원리는 세 가지를 요약할 수 있다.


1、초기화 시:runtime는 objc 를 호출합니다initWeak 함수, 새로운 weak 포인터가 대상의 주소를 가리키는 것을 초기화합니다.2、인용 추가시:objcinitWeak 함수는 objc 를 호출합니다.storeWeak () 함수, objcstoreWeak () 의 역할은 포인터 지향을 업데이트하고 대응하는 약한 인용표를 만드는 것입니다.3. 해제할 때clearDeallocating 함수를 호출합니다.clearDeallocating 함수는 우선 대상 주소에 따라 모든 weak 포인터 주소의 그룹을 가져오고, 이 그룹을 옮겨다니며 그 중의 데이터를 nil로 설정하고, 마지막으로 이entry를 weak표에서 삭제하고, 마지막으로 대상의 기록을 정리합니다.

각 단계를 자세히 살펴보겠습니다.


1、초기화 시:runtime는 objc 를 호출합니다initWeak 함수, objcinitWeak 함수는 새로운 weak 포인터가 대상의 주소를 가리키는 것을 초기화합니다.


코드 예:
{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}


weak 변수를 초기화하면 runtime에서 NSObject를 호출합니다.mm의 objcinitWeak 함수.이 함수는 Clang에서 다음과 같이 설명합니다.
id objc_initWeak(id *object, id value);


objcinitWeak () 방법의 실현
id objc_initWeak(id *location, id newObj) {

    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeakfalse, true, true>
    (location, (objc_object*)newObj);
}


이를 통해 알 수 있듯이 이 함수는 단지 심층 함수의 호출 입구일 뿐이고 일반적인 입구 함수에서는 간단한 판단(예를 들어objc msgSend의 캐시 판단)을 할 수 있다. 여기서 바늘이 가리키는 클래스 대상이 유효한지 판단하고 무효가 직접 방출되며 심층 호출 함수를 사용하지 않는다.그렇지 않으면 object는value를 가리키는 로 등록됩니다대상이 일은 아마objcstoreWeak 함수로 한 거야.

주의: objcinitWeak 함수에는 전제 조건이 있습니다. Object는 로 등록되지 않은 것이어야 합니다.weak 대상의 유효한 지침입니다.value는null이거나 유효한 대상을 가리킬 수 있습니다.


2、인용 추가시:objcinitWeak 함수는 objc 를 호출합니다.storeWeak () 함수, objcstoreWeak () 의 역할은 포인터 지향을 업데이트하고 대응하는 약한 인용표를 만드는 것입니다.


objc_storeWeak의 함수 선언은 다음과 같습니다.
id objc_storeWeak(id *location, id value);


objc_storeWeak()의 구현은 다음과 같습니다.

template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {

    Class previouslyInitializedClass = nil;
    id oldObj;

    SideTable *oldTable;
    SideTable *newTable;

retry:
    if (HaveOld) {

        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {

        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);

    if (HaveOld  &&  *location != oldObj) {
        SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
        goto retry;
    }

    if (HaveNew  &&  newObj) {

        Class cls = newObj->getIsa();

        if (cls != previouslyInitializedClass  &&
            !((objc_class *)cls)->isInitialized()) {

            SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);

            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
                                                      (id)newObj, location,
                                                      CrashIfDeallocating);

        if (newObj  &&  !newObj->isTaggedPointer()) {

            newObj->setWeaklyReferenced_nolock();
        }

        *location = (id)newObj;
    }
    else {

    }
    SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
    return (id)newObj;
}


원본 코드의 각종 자물쇠 조작을 버리고 이 코드가 무엇을 했는지 봅시다.

1)、SideTable


SideTable 구조체, 나는 그에게 인용 계수와 약한 인용 의존표를 지어 주었다. 왜냐하면 이것은 주로 관리 대상의 인용 계수와 weak 표에 사용되기 때문이다.NSObject에서mm에서 데이터 구조를 선언합니다.
struct SideTable {

    spinlock_t slock;

    RefcountMap refcnts;

    weak_table_t weak_table;


}slock과refcnts 두 멤버는 말할 것도 없고, 첫 번째는 경쟁을 방지하기 위해 선택한 자전거 자물쇠이고, 두 번째는 협조 대상의isa지침의extrarc는 계수의 변수를 공동으로 인용한다.여기에는 주로 weak 전역hash표의 구조와 작용을 본다.

2)、weak표


weak표는 약한 인용표로 하나의 weak 로 실현된다table_t 구조체는 어떤 대상과 관련된 모든 약한 인용 정보를 저장한다.그 정의는 다음과 같다(구체적으로 objc-weak.h에 정의되어 있다).
   struct weak_table_t {

    weak_entry_t *weak_entries;

    size_t    num_entries;

    uintptr_t mask;

    uintptr_t max_hash_displacement;
};


이것은 전체 국면의 약한 인용hash표입니다.부정확한 유형의 대상의 주소를 키로 사용하고weakentry_t 유형 구조체의 대상은value입니다.그중의 weakentries 구성원은 글자의 뜻으로 보면 약한 인용표 입구이다.그 실현도 마찬가지다.
그중에서weakentry_t는 약한 인용표에 저장된 내부 구조체로 한 대상을 가리키는 모든 약한 인용hash표를 유지하고 저장합니다.정의는 다음과 같습니다.
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtrobjc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {

            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    }
}


weakentry_t의 구조에서 Disguised Ptr referent는 일반 대상의 바늘을 봉인하여 이 일반 클래스를 통해 메모리 유출 문제를 해결한다.주석에서 out 쓰기of_라인 멤버가 최저 유효 비트, 0일 때 weakreferrer_t 구성원은 여러 줄 정적hash table로 확장됩니다.사실 그중에 weak...referrer_t는 2차원objcobject의 별명은 2차원 포인터 주소를 통해 이동하고 하표를hash의 키로 표시하여 약한 인용 산열을 만들었습니다.그러면 유효 비트가 적용되지 않을 때 outof_line 、 num_refs、 mask 、 max_hash_displacement는 어떤 작용을 합니까?다음은 필자의 추측이다.
out_of_line: 최저 유효 비트이자 표지 비트입니다.표지 위치가 0일 때, 인용표 바늘의 위도를 증가시킵니다.num_refs: 인용 수치입니다.약인용표에 유효한 숫자를 기록합니다. 약인용표는 정적hash구조를 사용하기 때문에 변수를 사용하여 숫자를 기록해야 합니다.mask: 계수 보조량.max_hash_displacement:hash 요소 상한 밸브값.사실 outof_line의 값은 통상적으로 0과 같기 때문에 약한 인용표는 항상objcobjective 포인터 2차원 그룹입니다.1차원 objcobjective 바늘은 약한 인용산 목록을 구성할 수 있으며, 3위도를 통해 여러 개의 산 목록을 실현하였으며, 표 수량은WEAK 이다INLINE_COUNT .
요약하자면StripedMap[]:StripedMap은 템플릿 클래스입니다. 이 클래스에array 구성원이 있는데 PaddedT 대상을 저장합니다. 그리고 [] 문자에 대한 재부팅 정의에서 이PaddedT의value 구성원을 되돌려줍니다. 이value는 우리가 전송한 T범주 구성원, 즉sideTable 대상입니다.array의 하표에서 indexForPointer 방법을 사용하여 비트 연산을 통해 하표를 계산하여 정적HashTable를 실현하였다.weaktable 중, 그 구성원 weakentry는 전송된 대상의 주소를 봉인하고 전역 약한 인용표에 접근하는 입구도 있습니다.
31B622B5-77ED-4D50-8CF9-0803785117BC.png

이전 대상의 등록 해제 작업weakunregister_no_lock


이 방법의 주요 역할은 옛 대상을 weak 에table에서 weak 바늘에 접촉하는 대응하는 귀속입니다.함수명에 따라 등록 해제 작업이라고 부른다.원본 코드에서 그 기능이 바로 weak 에서table에서 weak 포인터를 접촉하는 귀속입니다.그중의 스트리밍 조회는 바로 weak 에 대한 것이다entry의 여러 장의 약한 인용산 목록입니다.

새 객체 추가 등록 작업 weakregister_no_lock


이 단계는 이전 단계와 반대로 weak 을 통과한다register_no_lock 함수는 마음의 대상을 등록 조작하고 대응하는 약한 인용표와 귀속 조작을 완성합니다.

약한 인용 대상 프로세스 일람 초기화


약인용의 초기화는 앞에서 분석한 바와 같이 주요한 조작 부분은 약인용표의 키 찾기, 조회 산열, 약인용표 만들기 등 조작에서 다음과 같은 절차도를 요약할 수 있다.
C6029C92-5145-4334-9C25-3CDBA50F142B.png
이 그림은 많은 상황의 판단을 생략하였으나, 을 성명할 때weak은 위의 그림에서 이 방법들을 호출할 것입니다.물론storeWeak 방법은 에만 쓰이는 것이 아니다weak의 성명에서,class 내부의 작업에서도 자주 이 방법을 통해 weak 대상을 조작합니다.

3. 해제할 때clearDeallocating 함수를 호출합니다.clearDeallocating 함수는 우선 대상 주소에 따라 모든 weak 포인터 주소의 그룹을 가져오고, 이 그룹을 옮겨다니며 그 중의 데이터를 nil로 설정하고, 마지막으로 이entry를 weak표에서 삭제하고, 마지막으로 대상의 기록을 정리합니다.


weak 인용이 가리키는 대상이 풀렸을 때 weak 지침을 어떻게 처리합니까?객체를 해제할 때 기본 절차는 다음과 같습니다.


1、objc 호출release 2, 대상의 인용 계수가 0이기 때문에 dealloc 3을 실행하고 dealloc에서 호출되었습니다objc_rootDealloc 함수 4,objc_rootDealloc에서 Object 호출됨dispose 함수 5, objc 호출destructInstance 6, 마지막 호출 objcclear_deallocating
대상이 풀려날 때 호출되는objc 에 중점을 두다clear_deallocating 함수.이 함수는 다음과 같습니다.
void  objc_clear_deallocating(id obj) 
{
    assert(obj);
    assert(!UseGC);
    if (obj->isTaggedPointer()) return;
   obj->clearDeallocating();
}


즉clearDeallocating을 호출했습니다. 계속 추적하면, 최종적으로 교체기를 사용하여 weak표의value를 찾은 다음 weak 를 호출합니다.clear_no_lock, 그리고 대응하는value를 찾아서 이 weak 바늘을 비워라, weakclear_no_lock 함수는 다음과 같습니다.

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
    objc_object *referent = (objc_object *)referent_id;
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {

        return;
    }

    weak_referrer_t *referrers;
    size_t count;

    if (entry->out_of_line) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    }
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.
", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); }

objc_clear_deallocating 함수의 동작은 다음과 같습니다.
1. weak표에서 폐기 대상의 주소를 키 값으로 얻는 기록 2. 기록에 포함된 모든 weak 수식자 변수의 주소를 nil3로 하고 weak표에서 이 기록을 삭제하는 4 인용 계수표에서 폐기 대상의 주소를 키 값으로 하는 기록을 삭제한다.
objc-weak을 봤어요.mm의 원본 코드를 알 수 있다. 사실 Weak표는hash(해시)표이고 그 안의 키는 대상의 주소를 가리키며Value는 Weak바늘의 주소를 가리키는 수조이다.

보충:.m 및.mm의 차이


.m: 원본 코드 파일, 이 전형적인 원본 코드 파일의 확장자, OC와 C 코드를 포함할 수 있습니다.mm: 원본 코드 파일, 이런 확장명을 가진 원본 코드 파일, OC와 C 코드를 포함할 수 있는 것 외에 C++ 코드도 포함할 수 있습니다.OC 코드에서 C++ 클래스나 특성을 사용해야 할 때만 이 확장자를 사용합니다.
참고 자료: weak 약 인용의 실현 방식 weak의 생명주기: 구체적인 실현 방법
본문 은 전재 문장 으로 원문 의 출처 가 있다

좋은 웹페이지 즐겨찾기