iOS分类底层实现原理小记

http://www.jianshu.com/p/b7169a5a558e

OS 分类底层是怎么实现的?
本文将分如下四个模块进行探究

  1. 分类的结构体
  2. 编译时的分类
  3. 分类的加载
  4. 总结

本文使用的runtime源码版本是 objc4 - 680
文中类与分类代码如下

//类
@interface Person : NSObject
@property (nonatomic ,copy) NSString *presonName;
@end

@implementation Person
- (void)doSomeThing{
    NSLog(@"Person");
}
@end
// 分类
@interface Person(categoryPerson)
@property (nonatomic ,copy) NSString *categoryPersonName;
@end

@implementation Person(categoryPerson)
- (void)doSomeThing{
    NSLog(@"categoryPerson");
}
@end

1.分类的结构体

struct _category_t {
    const char *name;//类名
    struct _class_t *cls;//类
    const struct _method_list_t *instance_methods;//category中所有给类添加的实例方法的列表(instanceMethods)
    const struct _method_list_t *class_methods;//category中所有添加的类方法的列表(classMethods)
    const struct _protocol_list_t *protocols;//category实现的所有协议的列表(protocols)
    const struct _prop_list_t *properties;//category中添加的所有属性(instanceProperties)
};

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; // 属性列表

    // 如果是元类,就返回类方法列表;否则返回实例方法列表
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) {
            return classMethods;
        } else {
            return instanceMethods;
        }
    }

    // 如果是元类,就返回 nil,因为元类没有属性;否则返回实例属性列表,但是...实例属性
    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) {
            return nil; // classProperties;
        } else {
            return instanceProperties;
        }
    }
};

2.编译时的分类

2.1分类的属性

// Person(categoryPerson) 属性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"categoryPersonName","[email protected]\"NSString\",C,N"}}
};

// Person 属性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"presonName","[email protected]\"NSString\",C,N,V_presonName"}}
};

对比上述代码可以发现:在分类中可以声明属性,并且同样会生成一个 _prop_list_t 的结构体

2.2分类的实例变量?

// Person 实例变量列表

static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[1];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    1,
    {{(unsigned long int *)&OBJC_IVAR_$_Person$_presonName, "_presonName", "@\"NSString\"", 3, 8}}
};

因为 _category_t 这个结构体中并没有 _ivar_list_t
所以在编译时系统没有Person(categoryPerson) 没有生成类似的相应结构体,也没有生成 _categoryPersonName

2.3分类的实例方法

// Person 实例方法结构体
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"doSomeThing", "[email protected]:8", (void *)_I_Person_doSomeThing},
    {(struct objc_selector *)"presonName", "@[email protected]:8", (void *)_I_Person_presonName},
    {(struct objc_selector *)"setPresonName:", "[email protected]:[email protected]", (void *)_I_Person_setPresonName_}}
};

// Person(categoryPerson )实例方法结构体
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"doSomeThing", "[email protected]:8", (void *)_I_Person_categoryPerson_doSomeThing}}
};

对比上述方法可以看到:虽然分类可以声明属性,但是编译时,系统并没有生成分类属性的 get/set 方法,所以,这就是为什么分类要利用
runtime 动态添加属性,如何动态添加属性,有兴趣的同学可以查看下面文章 iOS分类中通过runtime添加动态属性

2.4分类的结构体

// Person(categoryPerson ) 结构体
static struct _category_t _OBJC_$_CATEGORY_Person_$_categoryPerson __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_categoryPerson,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_categoryPerson,
};

这是系统在编译时实例化 _category_t 生成的
_OBJC_$_CATEGORY_Person_$_categoryPerson

2.5分类数组

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Person_$_categoryPerson,
};

编译器最后生成了一个数组,数组的元素就是我们创建的各个分类,用来在运行时加载分类。

3.分类的加载

3.1加载分类调用栈

_objc_init
└──map_2_images
    └──map_images_nolock
        └──_read_images

分类加载的调用栈如上述

  • _objc_init 算是整个 objc4 的入口,进行了一些初始化操作,注册了镜像状态改变时的回调函数
  • map_2_images 主要是加锁并调用 map_images_nolock
  • map_images_nolock 在这个函数中,完成所有 class 的注册、fixup等工作,还有初始化自动释放池、初始化 side table 等工作并在函数后端调用了 _read_images
  • _read_images 方法干了很多苦力活,比如加载类、Protocol、Category,加载分类的代码就写在 _read_images 函数的尾部

该调用栈入口函数 void _objc_init(void) 在 objc-os.mm 中,有兴趣的同学可以去看看这些函数里都做了什么

3.2 _read_images 中加载分类的源码

加载分类的源码主要做了两件事

  • 把category的实例方法、协议以及属性添加到类上
  • 把category的类方法和协议添加到类的metaclass上

略去与本文无关的代码,得到如下代码

// 获取 镜像中的所有分类
category_t **catlist = _getObjc2CategoryList(hi, &count);
// 遍历 catlist
for (i = 0; i < count; i++) {
    category_t *cat = catlist[i];
    Class cls = remapClass(cat->cls);
    if (cat->instanceMethods ||  cat->protocols
        ||  cat->instanceProperties)
    {
        addUnattachedCategoryForClass(cat, cls, hi);
        if (cls->isRealized()) {
            remethodizeClass(cls);
            classExists = YES;
        }
    }
    if (cat->classMethods  ||  cat->protocols
        /* ||  cat->classProperties */)
    {
        addUnattachedCategoryForClass(cat, cls->ISA(), hi);
        if (cls->ISA()->isRealized()) {
            remethodizeClass(cls->ISA());
        }
    }
}

做上述事情主要用到是如下两个函数

  • addUnattachedCategoryForClass(cat, cls, hi) 为类添加未依附的分类
    执行过程伪代码:
    1.取得存储所有 unattached 分类的列表

    NXMapTable *cats = unattachedCategories();

    2.从 cats 列表中找倒 cls 对应的 unattached 分类的列表

    category_list *list = (category_list *)NXMapGet(cats, cls);

    3.将新来的分类 cat 添加刚刚开辟的位置上

    list->list[list->count++] = (locstamped_category_t){cat, catHeader};

    4.将新的 list 重新插入 cats 中,会覆盖老的 list

    NXMapInsert(cats, cls, list);

    执行完上述过程后,系统将这个分类放到了一个 cls 对应的 unattached 分类的 list 中(有点绕口....),这个 list 会在 remethodizeClass(cls) 方法用到

  • remethodizeClass(cls)
    执行过程伪代码:
    1.取得 cls 类的 unattached 的分类列表
    category_list *cats = unattachedCategoriesForClass(cls, false/*not realizing*/)

    2.将 unattached 的分类列表 attach 到 cls 类上

    attachCategories(cls, cats, true /* 清空方法缓存 flush caches*/);

    执行完上述过程后,系统就把category的实例方法、协议以及属性添加到类上

  • 最后再来看一下
    attachCategories(cls, cats, true /* 清空方法缓存 flush caches*/)内部的实现过程
    1.在堆上创建方法、属性、协议数组,用来存储分类的方法、属性、协议
    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));

    2.遍历 cats ,取出各个分类的方法、属性、协议,并填充到上述代码创建的数组中

    int mcount = 0; // 记录方法的数量
    int propcount = 0; // 记录属性的数量
    int protocount = 0; // 记录协议的数量
    int i = cats->count; // 从后开始,保证先取最新的分类
    bool fromBundle = NO; // 记录是否是从 bundle 中取的
    while (i--) { // 从后往前遍历
      auto& entry = cats->list[i]; // 分类,locstamped_category_t 类型
      // 取出分类中的方法列表;如果是元类,取得的是类方法列表;否则取得的是实例方法列表
      method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
      if (mlist) {
          mlists[mcount++] = mlist; // 将方法列表放入 mlists 方法列表数组中
          fromBundle |= entry.hi->isBundle(); // 分类的头部信息中存储了是否是 bundle,将其记住
      }
      // 取出分类中的属性列表,如果是元类,取得是nil
      property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
      if (proplist) {
          proplists[propcount++] = proplist; // 将属性列表放入 proplists 属性列表数组中
      }
      // 取出分类中遵循的协议列表
      protocol_list_t *protolist = entry.cat->protocols;
      if (protolist) {
          protolists[protocount++] = protolist; // 将协议列表放入 protolists 协议列表数组中
      }
    }

    3.取出 cls 的 class_rw_t 数据

    auto rw = cls->data();

    4.存储方法、属性、协议数组到 rw

    // 准备 mlists 中的方法
    prepareMethodLists(cls, mlists, mcount/*方法列表的数量*/, NO/*不是基本方法*/, fromBundle/*是否来自bundle*/);
    // 将新属性列表添加到 rw 中的属性列表数组中
    rw->properties.attachLists(proplists, propcount);
    // 释放 proplists
    free(proplists); // 释放 proplists
    // 将新协议列表添加到 rw 中的协议列表数组中
    rw->protocols.attachLists(protolists, protocount);
    // 释放 protolists
    free(protolists); // 释放 protolists
    // 将新协议列表添加到 rw 中的协议列表数组中
    rw->protocols.attachLists(protolists, protocount);
    // 释放 protolists
    free(protolists);

4.总结

至此,本文接近尾声
希望本文的读者能够了解到

  • 分类的结构体
  • 分类中是否能添加属性
  • 分类中是否有实例变量
  • 分类是如何 attach 到类上的

行笔简陋,如有问题,敬请指正

时间: 2024-10-21 03:53:41

iOS分类底层实现原理小记的相关文章

PHP底层工作原理

分类: PHP本质2011-11-15 15:55 2840人阅读 评论(0) 收藏 举报 php工作apacheextensionzendvariables 目录(?)[+] 简介 先看看下面这个过程: 我们从未手动开启过PHP的相关进程,它是随着Apache的启动而运行的: PHP通过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程序编程接口): PHP总共有三个模块:内核.Zend引擎.以及扩展层: PHP内核用来处理请求.文件流.错误处理等相关操作: Ze

【iOS】文件上传小记

iOS中用系统提供的API能实现能实现文件的上传与下载,分别有两种方式.NSURLConnection与NSURLSession. 其中NSURLConnection是使用很久的的一种方式,NSURLSession是新出来的一种方式. 一. POST方式上传 POST方式提交信息默认使用的是 : *Content-Type:  application/x-www-form-urlencoded. *输入中文时,post方式自动进行转义(苹果中自动). 国内的绝大多数网站都采用这种方式上传文件(支

iOS 简易底层 Basement-敏捷开发

iOS 简易底层 我自己在使用的Basement 简易可自定义度底层 1. 没有的东西: 网络请求  数据存储 - ASI二次开发 AF MK 各有所好. 所以不写. CoreData 使用 各有所好, 推荐Magic FMDB - 数据库相关操作 - 各有所好不写 YTKeyValue - 键值存储(数据库) 2. 写了的东西:HMSegmentControl 方便 简单 好看的Segment INTULocationManager 位置管理器 加入了iOS8.0新方法 MBProgressH

迭代器Iterator的底层实现原理

第一步:没有接口的迭代器简单实现原理 1 package com.bjsxt.xiaofei; 2 /** 3 * 迭代器底层原理 4 * 方法: 5 * hasNext() 6 * next() 7 * remove() 8 * @ClassName: MyAarryList 9 * @Description: TODO(这里用一句话描述这个类的作用) 10 * @author 尚晓飞 11 * @date 2014-7-29 下午7:06:09 12 * 13 */ 14 public cl

浅议事件异步处理底层实现原理

//主类 package cn.com.likeshow.bluetoothchat; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.LinearLayout; import android.widget.TextView; public class MainActivity extends Activity { @Override pr

iOS多线程之GCD小记

iOS多线程之GCD小记 iOS多线程方案简介 从各种资料中了解到,iOS中目前有4套多线程的方案,分别是下列4中: 1.Pthreads 这是一套可以在很多操作系统上通用的多线程API,是基于C语言的,在在oc中使用时需要包含 #import<pthread.h> 使用这种多线程方案需要手动处理线程的各个状态的转换,也就是要管理线程的生命周期. 2.NSThread 这种多线程方案经过了苹果的封装,是一种面向对象的方案,因此可以直接操控线程对象,相对来说比较便捷,其生命周期也要手动管理 3.

由PHP底层工作原理说起

之前做过.net,java开发,也写过几个Php的网站,似乎3种主要编程语言都接触了.但是越来越觉得自己对编程的整个流程缺乏一个整体的认识,尤其是底层的机制.譬如网络编程,编译原理,服务器端,数据库存储引擎原理等.于是看了一些书,比较经典的有apue,unp,tcp/ip,nginx,mysql的innodb存储引擎,深入理解jvm.渐渐发现无论用什么语言做开发,背后都有linux,shell,c/c++,nginx服务器,mysql的身影.也许只有掌握了这些核心的原理知识,一个程序员才具有核心

iOS分类、延展和子类的区别

iOS分类.延展和子类的区别 类别.延展.子类的区别   类别 延展 子类 功能 为类添加方法,不用知道类的源码,添加变量(通过运行时,具体参考下面注解) 为类添加私有变量和私有方法,在类的源文件中书写,所以知道类的源代码 即能为类添加方法又能添加变量 特点 添加的方法称为类的一部分,可以被子类继承 添加的变量和方法只有这个类内部访问,正常情况下外边不能访问(可以通过运行时,见注解),不能被子类继承 新添加的变量和方法只能子类才能具有,父类不具有 使用 使用原始类的对象(调用-方法)或者类(调用

IOS系统推送原理

IOS推送大致原理如下图 1.Provider:就是为指定IOS设备应用程序提供Push的服务器,(如果IOS设备的应用程序是客户端的话,那么Provider可以理解为服务端[消息的发起者]): 2.APNS:Apple Push Notification Service[苹果消息推送服务器]: 3.iPhone:用来接收APNS下发下来的消息: 4.Client App:IOS设备上的应用程序,用来接收iphone传递APNS下发的消息到制定的一个客户端 app[消息的最终响应者]: 上图可以