学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

【读书笔记】iOS之师大小海腾博客

属性关键字和所有权修饰符

atomic 修饰的属性是怎么样保存线程安全的?

atomic 原子性(默认),编译器会自动生成互斥锁,对setter和getter方法进行加锁,可以保证属性的赋值和取值的原子性是线程安全的,但不包括操作和访问

比如说atomic修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的,但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在atomic的负责范围之内的,所以给被atomic修饰的数组添加对象或者移除对象是没办法保证线程安全的。

assign修饰对象类型

修饰对象类型时,不增加其引用计数,会产生悬垂指针(assign修饰的对象在被释放之后,指针仍然指向原对象地址,该指针变为悬垂指针,这时候如果继续通过该指针访问原对象的话,就可能导致程序崩溃)

Category和Extension

Category 的使用场合

① 给一个类添加新的方法,可以为系统的类扩展功能
分解体积庞大的类文件,可以将一个类按功能拆解成多个模块,方便代码管理。
创建对私有方法的前向引用:声明私有方法,把 Framework 的私有方法公开等。直接调用其他类的私有方法时编译器会报错的,这时候可以创建一个该类的分类,在分类中声明这些私有方法(不必提供方法实现),接着导入这个分类的头文件就可以正常调用这些私有方法。
向对象添加非正式协议:创建一个 NSObject 或其子类的分类称为 “创建一个非正式协议”。

正式协议是通过 protocol 指定的一系列方法的声明,然后由遵守该协议的类自己去实现这些方法。而非正式协议是通过给 NSObject 或其子类添加一个分类来实现。非正式协议已经渐渐被正式协议取代,正式协议最大的优点就是可以使用泛型约束,而非正式协议不可以。)

Category 中都可以添加哪些内容?

实例方法、类方法、协议、属性(只生成 setter 和 getter 方法的声明,不会生成 setter 和 getter 方法的实现以及下划线成员变量);
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中,但可以通过关联对象来间接实现这种效果。

Category 的实现原理

① 分类的实现原理取决于运行时决议;
② 同名分类方法谁能生效取决于编译顺序,最后参与编译的分类中的同名方法会最终生效;
③ 分类方法会“覆盖”同名的宿主类(原类)方法,这里说的“覆盖”并不是指原来的方法没了。消息传递过程中优先查找宿主类中靠前的元素,找到同名方法就进行调用,但实际上宿主类中原有同名方法的实现仍然是存在的。我们可以通过一些手段来调用到宿主类原有同名方法的实现,如可以通过Runtimeclass_copyMethodList方法打印类的方法列表,找到宿主类方法的imp,进行调用(可以交换方法实现)。

加载处理过程

在编译时,Category 中的数据还没有合并到类中,而是在程序运行的时候通过Runtime机制将所有分类数据合并到类(类对象、元类对象)中去。下面我们来看一下 Category 的加载处理过程。

① 通过Runtime加载某个类的所有 Category 数据;
② 把所有的分类数据(方法、属性、协议),合并到一个大数组中;(后面参与编译的 Category 数据,会在数组的前面)
③ 将合并后的分类数据(方法、属性、协议),插入到宿主类原来数据的前面。(所以会优先调用最后参与编译的分类中的同名方法)

load和 initialize

load方法的调用

① 调用时刻:
+load方法会在Runtime加载类、分类时调用(不管有没有用到这些类,在程序运行起来的时候都会加载进内存,并调用+load方法);
每个类、分类的+load ,在程序运行过程中只调用一次(除非开发者手动调用)。
② 调用方式: 系统自动调用+load 方式为直接通过函数地址调用,开发者手动调用+load 方式为消息机制objc_msgSend函数调用。
③ 调用顺序:
先调用类的+load ,按照编译先后顺序调用(先编译,先调用),调用子类的+load 之前会先调用父类的+load
再调用分类的+load ,按照编译先后顺序调用(先编译,先调用)(注意:分类的其它方法是:后编译,优先调用)。

initialize 方法的调用

① 调用时刻:
+initialize方法会在类第一次接收到消息时调用。
如果子类没有实现+initialize方法,会调用父类的+initialize,所以父类的+initialize方法可能会被调用多次,但不代表父类初始化多次,每个类只会初始化一次。
② 调用方式: 消息机制objc_msgSend函数调用。
③ 调用顺序: 先调用父类的+initialize,再调用子类的+initialize(先初识化父类,再初始化子类)

相关面试题

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

分类中有+load方法;
+load方法在Runtime加载类、分类的时候调用;
+load方法可以继承,但是一般情况下不会手动去调用+load方法,都是让系统自动调用。

手动调用 Student 类的 +load 方法,但是 Student 类没有实现该方法,为什么会去调用父类的 +load 方法,且是调用父类的分类的 +load 方法呢?

因为+load方法可以继承,[Student load]手动调用方式为是消息机制objc_msgSend函数的调用,会去类方法列表里找对应的方法,由于 Student 类没有实现,就会去父类的方法列表中查找,且优先调用分类的+load方法。而系统自动调用的+load方法是直接通过函数地址调用的。

Swift Objc 混编

Objc override alloc兼容问题

1
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

在 Swift 中 alloc 方法被禁用,alloc 和 init 自动合并了,内部会处理内存分配。而且在 Swift 中实例化 Objective-C,也不会调用 alloc 方法,也是直接调用构造器方法。

问题描述

在基础库中定义了一个 DefaultLoadingView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface DefaultLoadingView : UIView <LoadingViewProtocol>
@end

@interface DefaultLoadingView (sub)
+ (Class)defaultLoadingViewClass4Sub;
@end

@implementation DefaultLoadingView
+ (instancetype)alloc {
if ([self respondsToSelector:@selector(defaultLoadingViewClass4Sub)]) {
Class tempClass = [self defaultLoadingViewClass4Sub];
if (tempClass) {
return [tempClass alloc];
}
}
return [super alloc];
}
@end

业务方可以通过实现DefaultLoadingView+sub 分类来自定义 loadingView

1
2
3
4
5
@implementation DefaultLoadingView (sub)
+ (Class)defaultLoadingViewClass4Sub {
return CustomLoadingView.class;
}
@end

在 Objective-C 中我们可以直接通过 [[DefaultLoadingView alloc] init] 来创建 loadingView 实例,如果业务方有自定义的那创建的就是 CustomLoadingView 类型,否则就是 DefaultLoadingView 类型。
但在 Swift 中通过 DefaultLoadingView() 创建实例的话,都将是 DefaultLoadingView 类型,因为它不会调用 alloc 方法

解决方案一

如果要继续保留采用alloc方案的话,就在Objective-C中包个中间层。这里可以新增一个工厂方法来调用alloc和构造器方法,然后使用 NS_SWIFT_NAME 重命名该工厂方法使其作为构造器导入到Swift中以方便使用,同时使用 NS_SWIFT_UNAVAILABLE 将原先的构造器方法标记为在Swift中不可用且指示调用者使用新的构造器方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface DefaultLoadingView : UIView <LoadingViewProtocol>
+ (instancetype)loadingViewWithFrame:(CGRect)frame NS_SWIFT_NAME(init(swift_frame:));
- (instancetype)initWithFrame:(CGRect)frame NS_SWIFT_UNAVAILABLE("Use init(swift_frame:) instead");
@end

@implementation DefaultLoadingView
+ (instancetype)loadingViewWithFrame:(CGRect)frame {
return [[self alloc] initWithFrame:frame];
}
@end

// 使用新的构造器方法
let loadingView = DefaultLoadingView.init(swift_frame: frame)

这个方案的缺点是:如果原先构造器方法有多个,那么就要新增多个对应的工厂方法,并添加 NS_SWIFT_NAMENS_SWIFT_UNAVAILABLE

解决方案二

在 Swift 中直接取出想要的那个类,摒弃 alloc 方案,当然你也可以为了向后兼容对其进行保留。通过协议工厂(维护 protocolimplClass 的映射关系),在基础库中对协议 LoadingViewProtocol 注册 defaultImplClass DefaultLoadingView,业务方可以对协议注册 implClass CustomLoadingView。使用的时候通过协议去工厂中取出 implClass 或 implInstance,内部实现是如果未注册 implClass 则取默认的 defaultImplClass 或 defaultImplInstance。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation DefaultLoadingView
+ (void)load {
[ServiceFactory registerService:@protocol(LoadingViewProtocol) defaultImplClass:self.class];
}
@end

@implementation CustomLoadingView
+ (void)load {
[ServiceFactory registerService:@protocol(LoadingViewProtocol) implClass:CustomLoadingView.class];
}
@end

// 通过协议工厂创建
let loadingView = ServiceFactory.createService(LoadingViewProtocol.self, initBlock: nil) as! UIView & LoadingViewProtocol