一些常见的OC语言基础的万博最新官网是多少
iOS OC 中 load & initialize 调用时机与规则
+load是在整个APP运行期间只调用一次,在main之前,有分类先本类,再分类,不做任何事情就会被调用
+initialize是在某个类第一次创建引用被调用,之后的引用不会被调用,也只是一次,有分类先本类,再分类
-init是每次类创建都调用
instancetype 和 id 的区别
id实际上是指向objc_object结构体的指针,runtime源码中可以看到id的定义代码如下:
也就是说,id类型的指针指向的是objc_object结构体,而objc_object结构体表示的就是类对象的实例对象,所以id可以表示所有类型的实例。
而instancetype也是一个可以指向所有类型的实例变量指针。
不同点在于:在编译期,编译器会检索对象指针instancetype的真实类型;而id类型的实例变量是在运行期才会确定其真实类型,所以编译期即便是类型不匹配也不会报警告。因此,使用instancetype作为返回值,实际上也是提高程序健壮性的一个手段。
静态库和动态库
动态库形式:.dylib和.framework
静态库形式:.a和.framework
静态库:链接时,静态库会被完整地复制到可执行文件中,被多次使用就有多份冗余拷贝(图1所示)
系统动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存(图2所示)
KVC和KVO区别
*“KVC”:key value Coding(键值编码)
*目的:间接的修改或获取对象的属性,降低程序(类与类)之间的耦合度.
*“KVO”:key value Observer(键值观察),观察者模式.(检测模型变化用的多)
*目的:通常用于观察某个对象的某个属性发生变化时,及时做出相应.
KVO原理
当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
①NSKVONotifying_A 类剖析:在这个过程,被观察对象的 isa 指针从指向原来的 A 类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_A 类,来实现当前类属性值改变的监听;
②Setter方法。
在Foundation/NSKeyValueObserving.h可以找到下面的方法,
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key控制是否自动发送通知
如果set方法是自己实现的,那KVO是无法运作的,但是,如果把上面的前两个方法加到set方法里面,如下,KVO也会跑起来,哪怕方法名不是正规的set方法。
这里有个注意点:
尽管声明name属性的时候,声明的是readOnly,即不生成set方法,但是如果自己重定义了set方法,还是会被Observer覆盖掉set方法,KVO还是会走,但是如果定义了一个别名的set方法,在里面修改属性的值,就不会走了。
泛型
OC中经常用到的典型的泛型使用案例如下:
可以直接打开NSArray.h,看看NSArray是怎么使用泛型的。
其中ObjectType为提前声明好的占位类型名称,可自定义,需注意的是该ObjectType的作用域只限于@interface到@end之间,至于泛型占位名称之前的修饰符则可分为两种:__covariant(协变)和__contravariant(逆变)
两者的区别如下:
__covariant意为协变,意思是指子类可以强制转转换为(超类)父类,遵从的是SOLID中的L即里氏替换原则,大概可以描述为: 程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的
__contravariant意为逆变,意思是指父类可以强制转为子类。
默认不指定泛型类型的情况下,不同类型的泛型可以互相转换。这个时候就可以在占位泛型名称前加入修饰符__covariant或__contravariant来控制转换关系,像NSArray就使用了__covariant修饰符。
补充:
其实在这个声明属性时,还可以设置一种转换方式,在泛型前添加__kindof关键词,表示其中的类型为该类型或者其子类,如:
block 原理
Block的本质
Block本质上是一个OC对象,是一个系统定义好的比较特殊的一个类似自定义对象的OC对象。它内部也有isa指针,这个对象封装了函数调用地址以及函数调用环境(函数参数、返回值、捕获的外部变量等)。
从上面的Block的结构体中的几个变量可以看出Block的本质
impl的isa变量:就是isa指针,可见它就是一个OC对象。
impl的FuncPtr变量:是一个函数指针,也就是底层将block中要执行的代码封装成了一个函数,然后用这个指针指向那个函数。
Desc的Block_size变量:block占用的内存大小。
age变量:捕获的外部变量age,可见block会捕获外部变量并将其存储在block的底层结构体中。
当我们调用block时(block()),实际上就是通过函数指针FuncPtr找到封装的函数并将block的地址作为参数传给这个函数进行执行,把block传给函数是因为函数执行中需要用到的某些数据是存在block的结构体中的(比如捕获的外部变量)。如果定义的是带参数的block,调用block时是将block地址和block的参数一起传给封装好的函数。
注:这里查看编译后的代码的指令有两个,一个是利用GCC的编译指令,进行预编译
一个是运行命令行指令
Block的参数捕获机制
看一段代码:
最终的打印结果为:
在编译期间,Block会被转换为一个函数,并把用到的参数捕获到函数内部,这里捕获也分三种类型
1 全局变量,不会捕获。
因为全局变量在哪里都能访问
2 局部变量,捕获的是变量的值。
局部变量的作用域是在它所属的大括号内,出了大括号就会被释放掉。block里面的代码在底层是被封装成了一个函数,这个函数是在b所在的大括号外面,如果这个时候Block再调用这个局部变量就会有问题,所以,对于局部变量,是捕获变量的值
3 静态局部变量,捕获的是变量的地址
虽然静态局部变量的作用域依旧是它所属的大括号内,但是他的生命周期是和整个程序的生命周期一样的,整个程序运行过程中都不会被释放,那么保存他的地址就可以了。
Block的修饰符
_block修饰完变量后,会在Block的结构体中生成一个__Block_byref[变量名]_0的结构体指针,这个新的结构体包含isa指针,是一个对象,要捕获的变量也包含在这个结构体中。
所以,__block不管是修饰基础数据类型还是修饰对象数据类型,底层都是将它包装成一个对象(我这里取个名字叫__blockObj),然后block结构体中有个指针指向这个对象。
这个捕获的对象的内存管理为:
1 当block在栈上时,block内部并不会对__blockObj产生强引用。
2 当block调用copy函数从栈拷贝到堆中时,它同时会将__blockObj也拷贝到堆上,并对__blockObj产生强引用。
3 当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部又会调用_Block_object_dispose函数来释放__blockObj。
iOS事件传递、响应者链
响应者对象:继承自UIResponder的对象称之为响应者对象。UIApplication、UIWindow、UIViewController和所有继承UIView的UIKit类都直接或间接的继承自UIResponder
响应者链:由多个响应者组合起来的链条,就叫做响应者链。它表示了每个响应者之间的联系,并且可以使得一个事件可选择多个对象处理
hitTest:withEvent:决定由哪个视图来响应这次交互
pointInside:决定点击的点是否在该视图内
响应者链:假设触摸了红色View,
1.第一响应者就是红色View即红色View首先响应touchesBegan:withEvent:方法,接着传递给蓝色view
2.蓝色view开始响应touchesBegan:withEvent:方法,接着传递给橘色view
3.橘色view响应touchesBegan:withEvent:方法,接着传递给控制器的view
4.控制器view响应touchesBegan:withEvent:方法,控制器传递给了Window
5.Window再传递给application
如果上述响应者都不处理该事件,那么这次时间响应会被丢弃
事件传递:假设触摸了红色View,
1.首先接收事件的是UIWindow的根视图,首先对根视图进行hitTest:withEvent:
2.判断根视图的userInteractionEnabled,如果为NO,A的hitTest:withEvent返回nil;
3.调用pointInside:withEvent:方法判断用户点击是否在根视图的范围内,显然返回YES
4.遍历根视图的子视图橘色View
5.同样判断橘色View的userInteractionEnabled,如果为NO,A的hitTest:withEvent返回nil;
6.调用pointInside:withEvent:方法判断用户点击是否在橘色View的范围内,此处也返回YES
7 再重复一遍和4、5、6一样的操作查看橘色View的子视图。
8 再重复一遍和4、5、6一样的操作查看蓝色View的子视图,在子视图调用pointInside:withEvent:方法判断用户点击是否在子视图View的范围内,此处绿色View返回NO,红色View返回YES。
9 红色View没有子视图。
至此,点击事件的第一响应者就找到了。
weak和assign的区别
1.修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是”unsafe_”。
2.是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
weak实现原理
1、weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
2、weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
3、对象释放时,调用 clearDeallocating 函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
关于Weak的实现原理,这一篇就很深入细致
NSCache和NSDictionary区别
NSCache 是一个容器类,类似于NSDIctionary,通过key-value 形式存储和查询值,用于临时存储对象,它是可变且线程安全的。
它和NSDictionary区别就是,NSCache 中的key不必实现copy,NSDictionary中的key必须实现copy,NSCache中键是被retain的。
NSCache中存储的对象也不必实现NSCoding协议,因为毕竟是临时存储,类似于内存缓存,程序退出后就被释放了。
NSMutableArray的机制,Capacity的机制
NSMutable的结构体定义为:
Capacity是设置循环Buff的大小,也是上面结构体_size的大小
NSMutableArray的循环Buff原理演示
NSMutableDictionary setValue:forKey: setObject:forKey:的区别
一般情况下,如果给NSMutableDictionary 发送setValue仍然是调用了 setObject方法, 如果参数 value 为 nil,则会调用removeObject 移除这个键值对;
setObjectForKey 是 NSMutableDictionary特有的, value 不能为 nil,否则会崩溃
setValueForKey 是KVC的,key 必须是字符串类型, setObject 的 key 可以是任意类型
NSArray、NSMutableArray的copy、mutableCopy
总结如下(前提,数组中内容是NSString等基本数据类型,如果是自定义类型,则需要看是否实现NSCopy协议以及具体实现方法):
对于不可变数组:copy后仍然不可变、只复制指针(浅拷贝)、mutableCopy后变为可变数组并且复制内存(深拷贝)
对于可变数组:copy后不可变、复制内存(深拷贝)、mutableCopy后仍为可变数组并且复制内存(深拷贝)
OC中Bridge是什么
__bridge可以实现Objective-C与C语言变量和Objective-C与Core Foundation对象之间的互相转换
__bridge不会改变对象的持有状况,既不会retain,也不会release
__bridge转换需要慎重分析对象的持有情况,稍不注意就会内存泄漏
__bridge_retained用于将OC变量转换为C语言变量 或 将OC对象转换为Core Foundation对象
__bridge_retained类似于retain,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后持有该对象
__bridge_transfer用于将C语言变量转换为OC变量 或 将Core Foundation对象转换为OC对象
__bridge_transfer类似于release,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放
参考资料:iOS __bridge那些事