首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >iOS_Extension、Category、load、initialize

iOS_Extension、Category、load、initialize

作者头像
mikimo
发布2022-07-20 14:34:14
发布2022-07-20 14:34:14
7250
举报
文章被收录于专栏:iOS开发~iOS开发~

文章目录

Extension、Category、load、initialize

一、Extension 延展

定义:

Extension(也叫:扩展/延展/匿名分类),可以声明属性、方法和成员变量。

​ 创建Extension的文件的话,只会生成一个.h文件,或者可以寄生于类的.m文件中。如:

代码语言:javascript
复制
// MOPerson_xxx.h 或 MOPerson.m
@interface MOPerson()
@property (nonatomic, copy, readwrite) NSString *name;
@end

使用:

​ 一个属性可以在.h文件中声明为只读的,在.m文件的Extension中声明为可写的,从而实现对数据的保护。

二、Category 类别

定义:

Category(也叫:类别/分类/类目),无需继承即可为类新增方法和协议,不需要获取源代码。

Category的名字不能重复,否则会报错

​ 如果与原有类方法重名:在方法列表中Category的方法会排在类原有方法的前面,从而有“覆盖”了原类方法的错觉。(所以尽量不起同名的方法,除非是故意想覆盖)

Category中声明的属性,只会生成setter和getter的声明,不会实现setter、getter和成员变量 如:

代码语言:javascript
复制
// MOPerson+Fitness.h
@interface MOPerson (Fitness)
- (void)fit_run();
- (void)fit_swim();
@end
// MOPerson+Fitness.m
@implementation MOPerson (Fitness)
- (void)fit_run() { ... }
- (void)fit_swim() { ... }
@end

使用:

  • 当一个大型的类功能繁多时,可以将不同的功能分在不同的分类中实现,进而可以按需引入不同的Category,优化代码;有助于提高可维护性,简化单个文件的管理
  • 当需要扩展系统类时。(如:NSString、NSArray、NSNumber等,因为系统本身不提倡使用继承去扩展方法,所以这些类内部实现对继承有所限制)
  • 模拟多继承(另外可以模拟多继承的还有protocol
  • framework的私有方法公开

三、+load

定义:

程序启动装载类信息的时候(main函数之前,初始化runtime之后,加入runtime之前)仅调用一次,不会自动继承(复写也无需加[super load]),系统自动调用(无须手动调用)。

​ 调用顺序:父类->子类->分类,类/不同的分类 的执行顺序,跟Targets->Build Phases->**Compile Sources**中出现的一致,前提要遵循父类先调用(所有Category的load类的load之后调用)。

使用:

​ 方法交换method swizzling;尽量不要在load方法里做耗时的操作,否则会增加app的启动时间,降低用户体验。

四、+initialize

定义:

​ 在该类第一次接收到消息之前(惰性)以线程安全(加锁)的方式调用,其他的消息会等待initialize完成。系统自动调用(无须手动调用)。

调用顺序:

  • 父类的会比子类的先执行(所以复写也无需加[super initialize]
  • 子类未实现:调用父类的(继承思想),在此之前父类的已经被调用了一次,所以父类的可能会被调用多次
  • 分类实现了:调用分类的(会覆盖当前类的)

使用:

​ 如果想防止父类的initialize被调用多次,可以实现如下:

代码语言:javascript
复制
+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

​ 因为initialize是以阻塞方式调用的,所以将initialize实现限制在所需的最小工作量是很重要的,特别是获取其他类需要的锁代码时,容易导致死锁!因此,不应该依赖initialize进行复杂的初始化。

​ 可以做一些简单的初始化工作,如:初始化 全局变量 或 静态变量(整个类共用的数据);

五、灵魂拷问

1、Category和Extension是什么?两者的区别?

  • Category有名字,Extension没有
  • Category声明的属性,不会自动生成ivarsettergetter
  • Extension可以添加实例变量,Category不可以
  • Extension在编译时,其数据就包含在类信息中;Category在运行时,才会将数据合并到类信息中
  • Extension不能像Category那样拥有独立的**@implementation部分。也就是说Extension声明的方法必须依托对应类的@implementation**部分实现。

2、为什么Category可以添加属性和方法,却不能添加成员变量?

Class结构体如下:

代码语言:javascript
复制
struct objc_class {
  Class isa;
	...
  struct objc_ivar_list *ivars; // 成员变量链表
  struct objc_method_list **methodLists; // 方法定义的链表
  struct objc_cache *cache; // 方法缓存
  struct objc_protocol_list *protocols; // 协议链表
} OBJC2_UNAVAILABLE;

Category结构体如下:

代码语言:javascript
复制
  typedef struct category_t {
  const char *name; // class类名
  classref_t cls;  // 类对象地址
  struct method_list_t *instanceMethods; // 对象方法
  struct method_list_t *classMethods; // 类方法
  struct protocol_list_t *protocols; // 协议
  struct property_list_t *instanceProperties; // 属性
} category_t;

​ 现象:添加成员变量,会报错:build failure

​ 原因:类的内存布局在编译时期就已经确定了,而Category是由runtime(运行时)才加载的。分类不是类没有自己的isa,只会将自己的methodattach到主类,并不会影响到主类的IvarList`。

ivars是指向名为objc_ivar_list的结构体的指针(指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数);而methodLists是一个指针,它指向另一个指针,另一个指针指向名为objc_method_list的结构体(可以修改另一个指针,即*methodLists的值来增加成员方法,虽不能扩展methodLists指向的内存区域,却可以改变这个内存区域的值);Runtime时objc_class结构体的大小是固定的,不能往其中添加数据,只能修改。

虽然说runtime有一个 lass_addIvar() 添加成员变量的方法,但是只能在“构建一个类的过程中”调用。一但完成类定义,就不能再添加成员变量了。而分类是在运行时才加载,所以分类不能add

3、继承Inherit和分类Category在实现中有和区别?(耦合度)

​ 分类Category

  • 允许开发者在不改动原有类的情况下,对该类进行扩展使用,是对一个功能完备的类的一种补充;

​ 继承Inherit

  • 耦合度比较高,依赖父类,要求对父类的工作流程相对熟悉;
  • 如果继承体系太复杂会导致整个系统混乱难以维护。
  • 当需要扩展的方法与原方法同名时,并且需要调用父类的同名方法,则需要用继承;(因为分类的同名方法会覆盖原方法的实现,进而访问不到原方法。)

4、系统是怎么实现Category的?Category是如何附加到主类上面的?

Category的实现结构上文有给出,作用时机:runtime初始化时,会查询分类,合并分类的方法、属性、协议等,并将分类的东西和原类的合并到一起(把原类的东西后移,将分类的放在前面)。

具体过程可以看这篇文章:iOS底层原理总结 - Category的本质

5、Category为什么只能加方法,而不能加属性?

​ 可以添加属性,只是系统不会自动为Category中的属性实现settergetter方法;因为不能添加实例变量,所以需要通过runtime动态绑定的方式,实现setter和getter方法。

6、Category有load方法吗?load方法是什么时候调用的?load方法能继承吗?

Categoryload方法,load方法在程序启动装载类信息的时候(main函数之前,初始化runtime之后)调用,仅调用一次。不会自动继承,但是可以继承(自己在Category的load方法里,调用父类的load方法/子类的load方法;但感觉还是没必要,因为在此之前它们都会被调用,还是看具体需求吧~)

7、method swizzling方法交换写在load还是initialize里?为什么?

​ 写在load,程序启动加载类信息的时候调用,仅调用一次。如果写在initialize,可能会被调用多次,或者一次都没调用。为了安全起见,在load中实现method swizzling也要做唯一性判断:(如:防止子类有调用[super load]的情况)

代码语言:javascript
复制
+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    // doSomething
  });
}

参考:

官网文档:load()initialize() iOS底层原理总结 - Category的本质 (源码底层实现,数据结构,怎么attach到原类上) 深入理解Objective-C:Category(美团技术团队的文章) 深入详解 iOS的 +load和+initialize

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • Extension、Category、load、initialize
    • 一、Extension 延展
      • 定义:
      • 使用:
    • 二、Category 类别
      • 定义:
      • 使用:
    • 三、+load
      • 定义:
      • 使用:
    • 四、+initialize
      • 定义:
      • 使用:
    • 五、灵魂拷问
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档