iOS_Extension、Category、load、initialize

Extension、Category、load、initialize

一、Extension 延展

定义:

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

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

// 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和成员变量
如:

// 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被调用多次,可以实现如下:

+ (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结构体如下:

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结构体如下:

  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]的情况)

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    // doSomething
  });
}

参考:

官网文档:load()initialize()

iOS底层原理总结 - Category的本质 (源码底层实现,数据结构,怎么attach到原类上)

深入理解Objective-C:Category(美团技术团队的文章)

深入详解 iOS的 +load和+initialize

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫小言mo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值