Back home

بحث حول التنفيذ الأساسي لمعدل نسخة iOS

ألقِ نظرة على التنفيذ الأساسي للنسخة، والمعدلات القوية، والاحتفاظ، والذرية، وغير الذرية

فيما يتعلق بالوظائف والاختلافات بين معدِّلات سمات @property شائعة الاستخدام، مثل النسخ والقوي والاحتفاظ وما إلى ذلك، هناك العديد من المناقشات عبر الإنترنت، لكنني لم أجد واحدة تشرحها بوضوح. بل إن بعضهم مخطئ، أو أوضحه البعض الآخر، لكنني لا أفهمه بشكل صحيح. كما يقول المثل، 源码面前,了无秘密، ما عليك سوى إلقاء نظرة على الكود المصدري لمعرفة كيفية تنفيذه. بمجرد فهمك للكود المصدري، فلن تحيد أبدًا عن معناه الأصلي. نحن هنا ننظر بشكل أساسي إلى تنفيذ النسخ.

1. ألق نظرة على الكود المصدري القوي، المحتفظ به، المنسوخ، الذري، وغير الذري لـ c++

@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

استخدم clang -rewrite-objc propertyTest.m لإنشاء كود مصدر c++ كما هو موضح أدناه. التعليقات واضحة جدا.

// @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

####ملخص: تقوم طرق الحصول على القوة والنسخ والاحتفاظ بالعثور على متغيرات المثيل المقابلة بناءً على إزاحة العنوان وإعادتها مباشرة. تستخدم طريقة الحصول الذرية طريقة objc_getProperty

تعثر المجموعة القوية على متغير المثيل المقابل بناءً على إزاحة العنوان وتقوم بتعيينه مباشرةً تستخدم أساليب الاحتفاظ والنسخ والمجموعة الذرية objc_setProperty

2. كيف يتم تنفيذ objc_getProperty وobjc_setProperty؟

لم يتم العثور على objc_getProperty وobjc_setProperty في ملف .cpp الذي تم إنشاؤه. وجدته في الكود المصدري لـ 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);
}

دعونا نلقي نظرة على طريقة objc_setProperty أولاً. تحتوي هذه الطريقة على إجمالي 6 قيم. المعلمتان الأوليان شائعتان في طريقة oc. إذا لم تكن متأكدا، يمكنك إلقاء نظرة على وقت التشغيل. يوجد بالفعل الكثير من المعلومات على الإنترنت. إزاحة المعلمة الثالثة هي الإزاحة بالنسبة إلى الذات، والتي تُستخدم للعثور على متغير العضو المقابل. المعلمات الثلاثة الأخيرة هي id newValue، وBOOL atomic، وsigned char. يجب نسخ، تسمية المعلمة واضحة جدًا، لا حاجة للشرح بعد الآن، يستدعي objc_setProperty objc_setProperty_non_gc، المعلمات هي نفسها. ألق نظرة على تنفيذ 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);
}

يمكنك أن ترى أن معلمة الوظيفة objc_setProperty_non_gc تستدعي وظيفة realSetProperty. كمعلمة أخيرة، تحتوي الدالة realSetProperty على إجمالي 7 معلمات. تتوافق المعلمات الخمس الأولى مع المعلمات الخمس الأولى لـ objc_setProperty_non_gc. يتم تحديد قيم المعلمتين الأخيرتين بواسطة mustCopy. يوضح الكود أعلاه أن قيمة mustCopy هنا هي 0 أو 1. دعونا نفكر في هاتين الحالتين أولاً:

إذا كان ينبغي نسخ = 0 ثم نسخ = NO، mutableCopy = NO إذا كان ينبغي نسخ = 1 ثم نسخ = نعم، mutableCopy = لا

دعونا نلقي نظرة على تنفيذ حقاSetProperty

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);
}

إذا كان ينبغي نسخ = 0 ثم نسخ = NO، mutableCopy = NO إذا كان ينبغي نسخ = 1 ثم نسخ = نعم، mutableCopy = لا

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方法

إذا كانت قيمة كل من Copy وmutableCopy بالقيمة NO، فسيتم استدعاؤهما objc_retain

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

يمكننا أيضًا أن نرى مما سبق أنه عند تعيين السمة المعدلة باستخدام copy، بغض النظر عما إذا كانت كائنًا قابلاً للتغيير أم لا، فإنها ستكون كائنًا غير قابل للتغيير بعد تعيينها للسمة.

دعونا نلقي نظرة على الكود ذي الصلة بـ atomic

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

يمكنك أن ترى أنه يتم استخدام قفل spinlock_t عندما يكون atomic نعم.

يتم استدعاء طريقة الاحتفاظ المحددة على النحو التالي

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

وفقا لعملية استدعاء الوظائف المذكورة أعلاه، يمكننا الحصول على: وظيفة حقاSetProperty المعلمة الذرية هي NO، ومعلمة النسخ هي NO، وmutableCopy أيضًا NO. التنفيذ النهائي يعادل

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);
}

التنفيذ النهائي لطريقة مجموعة النسخ هو كما يلي:

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);
}

طريقة مجموعة النسخ الذرية هي كما يلي

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 ألق نظرة على تنفيذ objc_getProperty

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

تنفيذ 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);
}

مما سبق، يمكنك أن ترى أنه عندما تكون الذرية نعم، فإن أساليب المجموعة والحصول على السمات المقابلة تستخدم قفل Spinlock_t، مما يضمن سلامة خيط المجموعة والحصول على الأساليب، ولكنه لا يضمن سلامة الخيط للعمليات الأخرى، مثل عمليات تحرير السمات.

FAQ

What to read next

Related

Continue reading