objc源码分析及相关原理

深入理解objc的功能莫过于理解objc的源码,最新源码objc下载地址

NSObject结构分析

NSObject结构定义在NSObject.h文件中,通过查看其定义知其含有一个Class类型对象,其结构定义为

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
/// An opaque type that represents an Objective-C class
typedef struct objc_class *Class;

在Objc中,老版本objc_class的定义为

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class   OBJC2_UNAVAILABLE;
    const char *name    OBJC2_UNAVAILABLE;
    long version        OBJC2_UNAVAILABLE;
    long info           OBJC2_UNAVAILABLE;
    long instance_size  OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars        OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
    struct objc_cache *cache            OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocol OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

新版本objc_class的定义为

struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;
    ...
};
struct objc_object {
private:
    isa_t isa;
public:
    ...
};
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
};

NSObject方法及协议分析

  • hash
  • dealloc
  • 消息转发
  • 内存管理
  • 拷贝

hash方法分析

通过查看apple的runtime源码,发现NSObject执行hash方法时调用了_objc_rootHash方法返回当前对象的指针的整形数据,该函数的实现如下

#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long       uintptr_t;
#endif /* _UINTPTR_T */

uintptr_t _objc_rootHash(id obj)
{
    return (uintptr_t)obj;
}

通过测试代码可以验证hash返回该对象指针数据的整形值,测试代码如下所示:

NSObject  *obj = [[NSObject alloc] init];
NSLog(@"hash = %lu, selfPointer = %p", (unsigned long)[obj hash], obj);
2016-12-27 09:55:57.082 demo[3545:56563] hash = 140686818358448, selfPointer = 0x7ff433d534b0

dealoc方法分析

通过apple的runtime源代码,发现NSObject执行dealloc时调用_objc_rootDealloc,再调用objc_object对象的rootDealloc,继续调用object_dispose,在继续调用_object_dispose方法,这个方法的源码如下所示:

/**************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
* CoreFoundation and other clients do call this under GC.
***************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa = obj->getIsa();

        if (isa->hasCxxDtor()) {
            object_cxxDestruct(obj);
        }

        if (isa->instancesHaveAssociatedObjects()) {
            _object_remove_assocations(obj);
        }

        objc_clear_deallocating(obj);
    }

    return obj;
}

这个函数简单的做了3件事情:

1 查询当前对象是否存在C++的析构函数,如果有则执行object_cxxDestruct函数
2 查询当前对象是否有关联的对象,如果有则执行_object_remove_assocations函数
3 执行objc_clear_deallocating函数

通过调试程序时对属性的观察可知,在ARC下变量的释放就是在.cxx_struct,其测试的代码是观察类的某个属性,其lldb下的代码:

(lldb) watchpoint set variable self->_name
Watchpoint created: Watchpoint 1: addr = 0x7ff73142f0a8 size = 8 state = enabled type = w
    watchpoint spec = 'self->_name'
    new value: 0x0000000108f751b0
2016-12-27 11:12:25.187 demo[5544:99205] IMP: -[NSObject (demo) foo]
2016-12-27 11:12:31.187 demo[5544:99205] 140699660120224, 0x7ff73142f0a0

Watchpoint 1 hit:
old value: 0x0000000108f751b0
new value: 0x0000000000000000
(lldb) 

发现果然跟到了.cxx_destruct方法,而且是在objc_storeStrong的过程中释放。

object_cxxDestruct函数分析

通过Apple的源代码可以查看到,它主要调用<font color=#D2691E">object_cxxDestructFromClass</font>函数实现,该函数的实现源码如下:

/***************************************************
* object_cxxDestructFromClass.
* Call C++ destructors on obj, starting with cls's 
*   dtor method (if any) followed by superclasses' dtors (if any), 
*   stopping at cls's dtor (if any).
* Uses methodListLock and cacheUpdateLock. The caller must hold neither.
***************************************************/
static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // Call cls's dtor first, then superclasses's dtors.

    for ( ; cls; cls = cls->superclass) {
        if (!cls->hasCxxDtor()) return; 
        dtor = (void(*)(id)lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            if (PrintCxxCtors) {
                _objc_inform("CXX: calling C++ destructors for class %s", 
                             cls->nameForLogging());
            }
            (*dtor)(obj);
        }
    }
}

代码也不难理解,沿着继承链逐层向上搜寻<font color=#D2691E >SEL_cxx_destruct</font>这个selector,找到函数实现(void (*)(id)(函数指针)并执行

_object_remove_assocation函数分析

通过Apple的源代码查看_object_remove_assocation,其代码如下所示

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;  //全局的对象
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}
objc_clear_deallocating函数分析

通过Apple的源代码查看可获知其调用了clearDeallocating,然后在调用sidetable_clearDeallocating,该函数的源码如下所示:

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

消息转发

在NSObject中performSelector只是将方法做个转发功能,其内部代码主要是调用objc_msgSend实现,源码实现如下

+ (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)((id)self, sel);
}

+ (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id))objc_msgSend)((id)self, sel, obj);
}

+ (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id, id))objc_msgSend)((id)self, sel, obj1, obj2);
}

- (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}

- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2);
}

在NSObject上forwardInvocation方法只是简单调用NSInvocation的selector方法,其源码如下

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

内存管理

NSObject的内存管理主要是通过引用计数来实现,那retain、release、autorelease等是如何实现这些计数的呢?

  • retain剖析
  • release剖析
  • autorelease剖析

retain剖析

NSObject的retain的源码如下

+ (id)retain {
    return (id)self;
}

// Replaced by ObjectAlloc
- (id)retain {
    return ((id)self)->rootRetain();
}

继续查看rootRetain的源码,调用的objc_object的rootRetain方法

inline id objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

在查看sidetable_retain的源码

id objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

这是我们看到SideTables对象,这个对象是干什么的呢?看起源码

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    ...
};

在该结构体内包含个weak_table_t结构,查看其结构时

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

其中的weak_entry_t的源码如下所示

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

release剖析

NSObject的release的源码如下

// Replaced by ObjectAlloc
- (oneway void)release {
    ((id)self)->rootRelease();
}

+ (id)autorelease {
    return (id)self;
}

NSObject的autorelease的源码如下

// Replaced by ObjectAlloc
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

autorelease剖析

NSObject函数在调用autorelease时,其内部其实是调用AutoreleasePoolPage函数实现内存管理

// Replaced by ObjectAlloc
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

// Base autorelease implementation, ignoring overrides.
inline id objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

rootAutorelease2方法是调用

__attribute__((noinline,used)) id objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

往下看autorelease的实现

static inline id autorelease(id obj)
{
    assert(obj);
   assert(!obj->isTaggedPointer());
   id *dest __unused = autoreleaseFast(obj);
   assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
}

接着看autoreleaseFast函数的实现

static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
       return page->add(obj);
    } else if (page) {
       return autoreleaseFullPage(obj, page);
    } else {
        return autoreleaseNoPage(obj);
     }
}

id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}

static __attribute__((noinline)) id *autoreleaseNoPage(id obj)
{
    ...
    // Install the first page.
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
      
    // Push a boundary on behalf of the previously-placeholder'd pool.
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
        
    // Push the requested object or pool.
    return page->add(obj);
}

可以了解到开始创建了AutoreleasePoolPage对象,第一次没有page时,将调用autoreleaseNoPage创建个page,将对象加入到page中。只要当前的hotPage不满,直接将其加入当前的page中。否则调用autoreleaseFullPage重新申请新的page,将对象加入其中。
对于AutoreleasePoolPage到底是什么呢?

class AutoreleasePoolPage 
{
    ....
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
    ...
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容