Objective-C
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C相关的知识,希望对你有一定的参考价值。
参考技术A 分类就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。在分类中添加了一个属性时,只是声明了对应的set方法和get方法,并没有为我们在分类当中添加了实例变量。
如果要为分类添加实例变量,是通过关联对象来添加的。
分类结构体:
category_t实际上就是我们创建的分类文件
这里没有实例变量的结构
从类别的结构体我们可以看到,分类可以添加属性,不能添加成员变量
当我们程序启动之后,在运行时会调用_objc_init方法,实际上是在runtime的初始化方法,然后会调用一系列方法,最后加载分类。
例如,调用_objc_init初始化方法后,会调用map_2_images方法,然后调用map_images_nolock方法,然后再调用_read_images,最后调用remethodizeClass:,分类的加载的逻辑都在remethodizeClass:方法的内部开始。
调用runtime的_objc_init方法,进行初始化操作,注册镜像状态改变时的回调函数,调用内存镜像相关处理的map_2_images函数,map_2_images主要是加锁并调用map_images_nolock,map_images_nolock完成所有 class 的注册、fixup等工作,还有初始化自动释放池、初始化 side table 等工作并在函数后端调用读取镜像函数_read_images,读取镜像函数_read_images加载可执行文件,比如加载类、Protocol、Category,最后调用remethodizeClass函数,分类的内部实现都在remethodizeClass函数里面。,
协议中可以定义:属性,方法
问题:我们在协议中声明的方法或者属性,代理方都必须实现吗?
不一定,在协议中被声明为require,是必须实现的,如果是optional的,可以不实现。
问题:代理方和委托方之间是是以什么样的关系存在的?
代理方用strong关键字来强持有委托方,委托方用weak关键字来声明代理方,弱引用代理方,这样的目的是以规避循环引用。
数据层,网络层,业务逻辑层,UI层
通知是怎样实现一对多的传递方式的
通知一对多的流程:
在通知中心(NSNotificationCenter)这个系统类当中,可能内部会维护一个Notification_Map表,或者说字典,这个字典当中的key是notificationName,即监听的通知名称,值就是就是我们添加的Observers_List,对于同一个名称的通知,添加多个Observer,所以Observer对应的值,应该是一个数组列表,这个列表中的每个成员,都包含通知接收的观察者和这个观察者调用的方法,比如说,我们收到这个通知之后,观察者的回调方法是哪个,那么在这个列表当中的每个元素里面也会体现关于这个通知回调方法的一些相关数据信息。
KVO是Key-value observing的缩写。
KVO是Objective-C对观察者设计模式的又一实现。
Apple使用了isa 混写(isa-swizzling)来实现KVO。
当我们调用了addObserver:forKeyPath:options:之后,系统在运行时动态创建NSKVONotifying_A这么一个子类,同时将原来的类A的isa指针指向新创建的类,重写set方法,来实现kvo的机制的。
NSKVONotifying_A是原来的类A的一个子类,之所以做这个继承关系,是为了重写A类的setter方法,然后这个子类对setter方法的重写来达到可以通知观察者的目的。
didChangeValueForKeyceiling这个方法就会触发 observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context 回调方法来通知我们的观察者value发生了变化
KVC:是Key-value coding的缩写,键值编码技术,和键值编码技术相关的两个方法:
当调用value:forKey的时候,首先系统会判断我们通过这个key所访问的对应的实例变量是否有相应的getter方法,如果有,直接调用,然后结束value:forKey的调用流程;
如果对应的getter方法不存在,就会通过系统的+ (BOOL)accessInstanceVariablesDirectly判断实例变量是否存在,如果和我们这个key相同或者相似的成员变量存在的话,那么直接获取这个实例变量的值,然后结束value:forKey流程;
如果这个实例变量不存在,就会调用当前实例的valueForUndefinedKey:方法,然后会抛出NSUndefinedKeyException未定义key的异常,然后结束value:forKey调用流程。
我们在用value for key 获取一个key同名或者相似名称的成员变量的时候,访问器定义方法的定义,实际上也涉及到一个相似的概念,比如说,如果我们实现了get方法,叫getKey,同时满足驼峰命名方法,那么value for key的调用流程,也会认为这个key所对应的成员变量是存在访问器方法的。最常见的属性名称,也就是我们get方法的名称。除了<getKey>和<key>,还有<isKey>,如果说,我们传递参数的key,那么和它对应的成员变量,如果实现了一个叫isKey的get方法,那么在value for key调用流程当中也会认为它的访问器方法是存在的。
问题:实例变量是否存在的判断规则
只要存在_key、_isKey、key、isKey,就可以获取到对应的值。
调用setValue:forKey的时候,首先会判断是否有和这个key相关的Setter方法的存在,如果有,直接调用,然后结束setValue:forKey流程;
如果没有,就会通过系统的+ (BOOL)accessInstanceVariablesDirectly判断实例变量是否存在,如果这个实例变量存在的话,那么对这个key所对应的成员变量进行赋值,然后结束setValue:forKey流程;
如果这个实例变量不存在,就会调用当前实例的setValue:forUndefinedKey:方法,然后会抛出NSUndefinedKeyException未定义key的异常,然后结束setValue:forKey流程。
1)读写权限:readonly,readwrite(默认)
2)原子性:
atomic(默认):赋值和获取,是线性安全的,但对于操作是不能保证线性安全的。
nonatomic
3)引用计数器
retain(MRC):修饰对象
strong(ARC):修饰对象
assign(修饰基本数据类型和对象类型)
unsafe_unretained(MRC中使用比较频繁)
weak
copy
修饰基本数据类型,如int、BOOL等。
修饰对象类型时,不改变其引用计数。
会产生悬垂指针。
悬垂指针会造成内存泄露
assign所修饰的对象,在被释放之后,assign指针仍然指向原对象内存地址,这个时候,如果通过assign指针继续访问原对象的话,可能就会由于悬垂指针的原因造成内存泄露或者程序异常。
空指针:指针指向的地址为空的指针叫空指针(NULL指针)
野指针:是指向“垃圾”内存(不可用内存)的指针
产生原因:指针创建时未初始化。指针变量刚被创建时不会自动成为NULL指针,它会随机指向一个内存地址。
悬垂指针:指针所指向的对象已经被释放或者回收了,但是指向该对象的指针没有作任何的修改,仍旧指向已经回收的内存地址。 此类指针称为垂悬指针。
weak和assign都不改变对象的引用计数
浅拷贝的特点:
1、引用计数器增加。
2、不会开辟新的内存空间,不会发生新的内存分配。
深拷贝的特点:
1、不会增加被拷贝对象的引用计数
2、深拷贝产生新的内存分配
copy关键字
Block是将函数及其执行上下文封装起来的对象。
block本质上也是一个oc对象
Block调用即是函数的调用。
原因:__block修饰的变量变成了对象
栈上的__block的__forwarding指针是指向__block自身的。
impl.isa = &_NSConcreteStackBlock;(isa:标识当前block的类型)
1、_NSConcreteGlobalBlock
2、_NSConcreteStackBlock
3、_NSConcreteMallocBlock
不同类型的block在内存上面的分布:
1、全局类型的block:已初始化数据区
2、栈上面的block:栈区
3、堆上面的block:堆区
Block的copy操作
__block变量中是有一个__forwarding指针,栈上的__forwarding是指向block自身的,前提是栈上的。
我们在栈上创建了一个变量multiplier,如果通过__block修饰符修饰之后,multiplier就变成了一个对象,所以说multiplier=6 的赋值,实际上不是对变量赋值,而是通过multiplier这个对象的__forwarding指针,然后对其成员变量multiplier进行赋值,_blk实际上是某一个对象的成员变量,当对_blk进行赋值操作的时候,实际上就会对_blk进行copy操作,那么_blk就会被拷贝到堆上面去,然后我们对block进行执行,multiplier=6 代表的含义就是通过栈上的multiplier的__forwarding指针找到堆上面所对应的__block变量的copy副本,然后对其副本进行值的修改,右边block执行逻辑,我们调用了堆上面的block,入参为4,在我们调用的时候,这个时候我们在block的执行体当中所使用的multiplier __block变量,实际上使用的是堆上面的__block变量,那么我们在这里,实际上经过copy之后,multiplier=6 ,它是对堆上面的block变量的修改,所以我们在右边调用之后的结果是4和6的乘积为24。
当前对象用copy属性关键字声明了_strBlk,所以当前对象对_strBlk有一个强引用的,而_strBlk的表达式当中又使用到了_array成员变量,block截获变量的时候,对于对象类型的局部变量或者成员变量,实际上会连同属性关键字一起截获的,而array一般用strong属性关键字修饰的,所以在这个block中就有一个strong类型的指针指向当前对象,由此就产生了一个自循环引用。
会造成自循环引用,属于自循环。
解决方案:我们可以通过在当前栈上面创建一个__weak修饰符修饰的一个weakArray变量,来指向原对象的array成员变量,然后在block当中使用我们创建的weakArray,由此,我们就可以解除这个自循环引用。
在栈上面,我们通过__block修饰符修饰的变量来指向当前对象,同时当前对象的成员变量_blk在这里进行创建,block的表达式当中有使用到blockSelf的var变量。
在MRC下,不会产生循环引用,在ARC下,会产生循环引用,引起内存泄漏
ARC下的引用循环
ARC下的解决方案
MRC下同样没问题
这种解决方案有一个弊端:就是说如果我们很长一段时间或者说永远都不会调用_blk的话,那么这个引用循环的环就会一直存在。
Objective-C 和 C++ 的区别有哪些?
Objective-C开发语言是扩充C的面向对象编程语言,iOS开发是用的Objective-C语言,但是最近微软透露Objective C新技术,其最新IDE Visual Studio 2015能将Objective-C代码编译运行在Windows 10上。据说,这一技术可以将将Objective-C代码尽可能多的转变成Java或C++代码,挂钩进原生平台的 API。那么同样是“C”,Objective-C和C++之间有何不同?
Objective-C,通常写作ObjC和较少用的Objective C或Obj-C,是扩充C的面向对象编程语言。它主要使用于Mac OS X和GNUstep这两个使用OpenStep标准的系统,而在NeXTSTEP和OpenStep中它更是基本语言。Objective-C可以在GCC以及Clang运作的系统上编写和编译,因为GCC与Clang含Objective-C的编译器。
Objective-C是非常实际的语言。它是一个用C写成、很小的运行库,只会令应用程序的尺寸增加很小,和大部分OO系统使用极大的VM执行时间会取代了整个系统的运作相反。
Objective-C写成的程序通常不会比其原始码大很多。而其函式库(通常没附在软件发行本)亦和Smalltalk系统要使用极大的内存来开启一个窗口的情况相反。
所以,理论上将,Objective-C它完全兼容标准C语言;相比之下,C++对C语言的兼容主要体现在语法上,Objective-C在此基础上增加了面向对象编程语言的特性以及Smalltalk消息机制。
和C++不同,Objective-C不支持运算子重载。
和Java相同,Objective-C只容许对象继承一个类别(不设多重继承),这一点也和C++不同。
Objective-C不支持多重继承,而C++语言支持多重继承。
Objective-C是动态定型(dynamicaly typed)所以它的类库比C++要容易操作。Objective-C 在运行时可以允许根据字符串名字来访问方法和类,还可以动态连接和添加类。
在C++里,对象的静态类型决定你是否可以发送消息给它,而对Objective-C来说,由动态类型来决定。
以上是关于Objective-C的主要内容,如果未能解决你的问题,请参考以下文章