Back home

Investigación sobre la implementación subyacente del modificador de copia de iOS

Eche un vistazo a la implementación subyacente de los modificadores copiar, fuerte, retener, atmoico y no atómico.

Con respecto a las funciones y diferencias de los modificadores de atributos @property comúnmente utilizados, como copiar, fortalecer, retener, etc., hay muchas discusiones en línea, pero no he encontrado ninguna que lo explique claramente. Algunos incluso se equivocan, u otros lo han dejado claro, pero no lo entiendo bien. Como dice el refrán, 源码面前,了无秘密, basta con mirar el código fuente para ver cómo se implementa. Una vez que comprenda el código fuente, nunca se desviará de su significado original. Aquí nos fijamos principalmente en la implementación de la copia.

1. Eche un vistazo al código fuente de C++ fuerte, retenido, copiado, atómico y no atómico

@interface propertyTest : NSObject
@property (nonatomic, strong) NSString *nsstring___StrongTest;
@property (nonatomic, retain) NSString *nsstring___RetainTest;
@property (nonatomic, copy) NSString *nsstring___CopyTest;
@property (atomic, copy) NSString *nsstring___AtomicCopyTest;
@end

Utilice clang -rewrite-objc propertyTest.m para generar el código fuente de C++ como se muestra a continuación. Los comentarios son muy claros.

// @implementation propertyTest

//nsstring___StrongTest get方法
static NSString * _I_propertyTest_nsstring___StrongTest(propertyTest * self, SEL _cmd)
{
    //strong get 根据地址偏移找到对应的实例变量  直接返回
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest));
}

//nsstring___StrongTest set方法
static void _I_propertyTest_setNsstring___StrongTest_(propertyTest * self, SEL _cmd, NSString *nsstring___StrongTest)
{
    //strong set 根据地址偏移找到对应的实例变量  直接赋值
    (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___StrongTest)) = nsstring___StrongTest;
}

 //////===============
//nsstring___RetainTest get方法
static NSString * _I_propertyTest_nsstring___RetainTest(propertyTest * self, SEL _cmd)
{
    //retain get 根据地址偏移找到对应的实例变量  直接返回
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___RetainTest));
}

//由于下面要用objc_setProperty,声明objc_setProperty在外部文件定义
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_propertyTest_setNsstring___RetainTest_(propertyTest * self, SEL _cmd, NSString *nsstring___RetainTest)
{
    //retain set方法 ,和strong不一样,这里用到了objc_setProperty
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___RetainTest), (id)nsstring___RetainTest, 0, 0);
}

 //////===============
//nsstring___CopyTest get方法
static NSString * _I_propertyTest_nsstring___CopyTest(propertyTest * self, SEL _cmd)
{
    //copy get 根据地址偏移找到对应的实例变量  直接返回
    return (*(NSString **)((char *)self + OBJC_IVAR_$_propertyTest$_nsstring___CopyTest));
}

//nsstring___CopyTest set方法
static void _I_propertyTest_setNsstring___CopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___CopyTest)
{
    //copy set方法 ,和strong不一样,和retain一样, 用到了objc_setProperty
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___CopyTest), (id)nsstring___CopyTest, 0, 1);
}

 //////===============
//由于下面要用objc_getProperty,声明objc_getProperty在外部文件定义
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

//nsstring___AtomicCopyTest get方法
static NSString * _I_propertyTest_nsstring___AtomicCopyTest(propertyTest * self, SEL _cmd)
{
    //atomic get方法  用到了objc_getProperty
    typedef NSString * _TYPE;
    return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), 1);
}

//nsstring___AtomicCopyTest set方法
static void _I_propertyTest_setNsstring___AtomicCopyTest_(propertyTest * self, SEL _cmd, NSString *nsstring___AtomicCopyTest)
{
     //atomic set方法 同样用到了 objc_setProperty
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___AtomicCopyTest), (id)nsstring___AtomicCopyTest, 1, 1);
}
// @end

####Resumen: Los métodos get de strong, copy y retain encuentran las variables de instancia correspondientes según el desplazamiento de dirección y las devuelven directamente. El método atómico get utiliza el método objc_getProperty

strong set encuentra la variable de instancia correspondiente según el desplazamiento de dirección y la asigna directamente Los métodos de retención, copia y conjunto atómico utilizan objc_setProperty

2. ¿Cómo se implementan objc_getProperty y objc_setProperty?

objc_getProperty y objc_setProperty no se encontraron en el archivo .cpp generado. Lo encontré en el código fuente de objc.

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{
    return objc_getProperty_non_gc(self, _cmd, offset, atomic);
}

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
    objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy);
}

Veamos primero el método objc_setProperty. Este método tiene un total de 6 valores. Los dos primeros parámetros son comunes al método oc. Si no está seguro, puede consultar el tiempo de ejecución. Ya hay mucha información en Internet. El tercer parámetro de desplazamiento es el desplazamiento relativo a uno mismo, que se utiliza para encontrar la variable miembro correspondiente. Los últimos tres parámetros son id newValue, BOOL atomic y signed char. debería copiar, el nombre de los parámetros es muy claro, no es necesario explicar más, objc_setProperty llama a objc_setProperty_non_gc, los parámetros son los mismos. Eche un vistazo a la implementación de objc_setProperty_non_gc

void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

Puede ver que el parámetro de la función objc_setProperty_non_gc llama a la función ActuallySetProperty. Como último parámetro, la función realSetProperty tiene un total de 7 parámetros. Los primeros 5 parámetros corresponden a los primeros 5 parámetros de objc_setProperty_non_gc. Los valores de los dos últimos parámetros están determinados por shouldCopy. El código anterior muestra que el valor de debería copiar aquí es 0 o 1. Consideremos estas dos situaciones primero:

Si debería copiar = 0, entonces copiar = NO, copia mutable = NO Si debería copiar = 1, entonces copiar = SÍ, copia mutable = NO

Veamos la implementación de ActuallySetProperty.

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

Si debería copiar = 0, entonces copiar = NO, copia mutable = NO Si debería copiar = 1, entonces copiar = SÍ, copia mutable = NO

if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

上面就是copy关键字的实现了,copy == YES,调用[newValue copyWithZone:nil],返回的是不可变对应,最终还是调用了copyWithZone方法

Si tanto copy como mutableCopy son NO, se llamará objc_retain

objc_retain 实现如下
id objc_retain(id obj) { return [obj retain]; }

También podemos ver en lo anterior que cuando se asigna el atributo modificado con copy, independientemente de si es un objeto mutable o no, será un objeto inmutable después de ser asignado al atributo.

Echemos un vistazo al código relevante de atomic

 if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

Puede ver que el bloqueo spinlock_t se usa cuando atómico es sí.

El método de retención de conjunto se llama de la siguiente manera

 objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct propertyTest, _nsstring___RetainTest), (id)nsstring___RetainTest, 0, 0);

De acuerdo con el proceso de llamada a la función anterior, podemos obtener: función realmenteSetProperty El parámetro atómico es NO, el parámetro de copia es NO y mutableCopy también es NO. La implementación final es equivalente a

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{

    id oldValue;
    id *slot = (id*) ((char*)self + offset);
    if (*slot == newValue) return;
    newValue = objc_retain(newValue);

    oldValue = *slot;
    *slot = newValue;
    
    objc_release(oldValue);
}

La implementación final del método de conjunto de copias es la siguiente:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
 

    id oldValue;
    //取出变量
    id *slot = (id*) ((char*)self + offset);

  
        newValue = [newValue copyWithZone:nil];
           oldValue = *slot;
        *slot = newValue;

    objc_release(oldValue);
}

El método del conjunto de copias atómicas es el siguiente

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    id oldValue;
   
    id *slot = (id*) ((char*)self + offset);


        newValue = [newValue copyWithZone:nil];
   

        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
  
    objc_release(oldValue);
}

3 Eche un vistazo a la implementación de objc_getProperty

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic)
{
    return objc_getProperty_non_gc(self, _cmd, offset, atomic);
}

Implementación de objc_getProperty_non_gc

id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

De lo anterior, se puede ver que cuando atómico es sí, los métodos set y get de los atributos correspondientes utilizan el bloqueo spinlock_t, que garantiza la seguridad de los subprocesos de los métodos set y get, pero no garantiza la seguridad de los subprocesos de otras operaciones, como las operaciones de liberación de atributos.

FAQ

What to read next

Related

Continue reading