iOS 2024 整理

一、关键字
1、iOS中定义属性/变量的修饰关键字

在声明@property属性/变量时,总说要在括号中写上assign、retain、copy、weak、strong。

assign
assign 一般用来修饰基础数据类型(NSInterger、CGF lost、int、float、double、char等)。
因为assign声明的属性不会增加引用计数,属性释放后就没有了。若assign修饰的指针指向的是一个对象类型,被指向的对象dealloc后,对象的这个指针还在,被调用时就会crash。
所以一般只用来声明基本数据类型,因为他们会被分配到栈上,而栈区会由系统自动处理,不会造成野指针。

retain
与assign相对,retain声明后到对象会更改引用计数
对象被引用,引用计数+1。
释放后,引用计数-1。
即使这个对象本身释放了,只要还有对象引用它,就会被持有。
只有当引用计数为0时,才被dealloc析构函数回收。

copy
最常见的是使用copy来修饰NSString.
copy 和 retain 的区别在于 retain 的引用是拷贝指针地址,而copy是拷贝的对象本人,也就是retain 是浅复制,copy是深复制。
浅拷贝:当修改对象值时,都会被修改;
深拷贝:修改对象值时,不会引起其他使用对象;

浅拷贝:指针拷贝,对一个对象进行浅拷贝,相当于对只想这个对象的指针进行拷贝,产生了一个新的指向这个对象的指针,那么就是有两个指针同时指向同一个对象,这个对象销毁后两个指针都应该置为空。 对象引用计数+1,相当于进行了一个retain操作。
深拷贝:内容拷贝,相当于对象进行复制,产生一个新的对象,那么就有两个指针分别指向两个对象,对一个对象的操作不会影响到另一个对象。copy属性特性新的对象的引用计数为1,和旧有对象的引用计数无关,旧对象没有变化。

只有不可变对象创建不可变副本才是浅处之,其他都是深复制。

copy和mutablecopy存在的原因:尽可能的节省内存开销。

**copy和mutableCopy方法

image.png

之所以在NSString这类可有可变类型的对象上使用,是因为他们有可能和对应的可变类型(NSM unable String)之间进行赋值操作,为了防止内容改变,使用copy去深复制一份。
copy工作由copy方法执行,此属性只对那些实现了NSCo ping协议的对象类型有效。

assign 、retain、copy 可以在MRC中使用,但是weak和strong就智能在ARC中使用,也就是自动引用计数,这时就不能手动的去retain、release等操作了,ARC会帮我们完成这些工作。

weak
weak类似于assign,叫做弱引用。不会增加引用计数。在防止循环引用时使用(循环引用:父类引用了子类,子类又去引用父类)。IBOutlet、Delegate一般用的都是weak,这时因为他们在类的外部被调用,防止循环引用。

strong
相对的,strong就类似于retain了,叫强引用,会增加引用计数,类内部使用的属性一般都是strong修饰的,现在ARC已经基本代替了MRC,所以我门最常见的就是strong。

nonatomic
在定义属性时,括号内往往还会加一个nonatomic,它的名字叫做非原子访问。对应的有atomic,是原子性的访问。如果一个属性是由atomic修饰的,那么系统就会进行线程保护,防止多个写操作同时进行。也有坏处:那就是消耗系统资源。所以对于iPhone这种移动个设备,如果不是进行多线程的写操作,就可以使用nonatomic,取消线程保护来提高性能。

二、block外部weak和内部strong防止循环引用

大家都看到别人在block里面使用self或者self属性的时候要使用__weak修饰,然后才能在block里面使用,在block里面使用的时候又将weakself使用__strong 修饰进行使用,比如:

__weak __typeof(self) weakSelf<wbr> = self;
self.block = ^{
       __strong __typeof(self) strongSelf = weakSelf;
      [strongSelf doSomeThing];
      [strongSelf doOtherThing];
};

为什么要使用weakSelf修饰self

通过clang- rewrite- objc源代码文件名将代码转为c++代码(实际是c代码),可以看到block是一个结构体,它会将全局变量保存为一个属性(是__strong修饰的),而self强引用了block,这会造成循环引用。所以需要使用__weak修饰self。

为什么在block里面需要使用strongSelf

是为了保证block执行完毕之前self不会被释放,执行完毕的时候再释放。这时候会发现为什么在block外面使用了__weak修饰self,里面使用__strong修饰weak self的时候不会发生循环引用。另外,strong Self值时为了保证在block内部执行的时候不会被释放,但存在执行前self就已经被释放的情况,导致strongself=nil。注意判空处理。

不会引起循环引用的原因
因为block截获self之后,self属于block结构体中的一个由__strong修饰的属性会强引用self,所以需要使用__weak修饰self防止循环引用。block使用__strong修饰weakself是为了在block(可以理解为函数)生命周期中self不会提前释放。strong Self实质是一个局部变量(在block这个函数里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直强引用。

三、isa指针和imp指针

isa指针 是一个指向所属类的指针,它标注着一个实例对象的真实类型。
在object-c中消息机制是依靠 objc_msgSend()这个函数发送的。
在objc_msgSend中,包含两个参数:receiver、selector。即:objc_msgSend(receiver,selector);
而objc_msgSend通过isa指针,找到实例对象所属的类,也就找到了其全部的类,如下图所示:

image.png

一个方法被调用要经过的步骤是:
当我们向一个对象发送消息去调用它的方法时:
msg_send会根据该实例对象的isa指针去查找该对象的类,然后去查找该类的dispatch table中的selector。如果找不到就会一次向上查找它的父类,并在其父类的dispatch table中查找selector,直到查到基类NSObject。一旦找到selector,object_msgSend会根据dispatch table中内存地址去调用该selector。这样实现message和selector在执行阶段的动态绑定。为了提高查找转发的效率,系统会把所有的selector内存地址和调用过的selector的内存地址缓存起来,通过类的形式划分不同的缓存区域,没个类的缓存区域会包括自己的selector和继承自父类的selector,在objc_msgSend去查找dispatch table 前,会先去检查该类的缓存,如果缓存中有该selector,就直接调用。

imp指针上指向selector具体实现的指针。
在了解imp指针之前,来探讨一个问题,现在需求上在不影响层级结构和尽量少改动的情况下,让项目中所有view controller的viewdidload方法中打印自己的类型,了解Runtime 的同学可能会想到 method_exchangeImplementation(Method m1,Method m2)


image.png

这种方式很轻松的实现了我门的需求,但是作为一个优雅的程序要,就要做优雅的事。下面就要用imp指针。

首先,配置工程,进行如下配置


image.png

配置完成直接上代码


image.png

使用imp指针更改方法实现成功。这样做比上一种方式更加优雅。

接下来探讨一下method_exchangeImplementation(Method m1,Method m2)方法的实现原理:


image.png

从imp指针的地址打印来看貌似这个方法是将两个方法的imp指针做了互换。

经过这么多代码的测试,最终印证的还是那句话,imp指针是指向selector具体实现的指针。
附加下真实imp的具体定义:


image.png

SEL:方法编号,根据方法编号就可以找到对应方法实现。

KVC
KVC 的全称是KeyValueCoding, 俗称“键值编码”,可以通过一个key直接访问对象的属性,或者给对象的属性赋值。这样可以在运行时动态的访问和修改对象的属性。

常见的API

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;

key 和 keyPath 的区别
key:只能接受当前类所具有的属性,不管是自己的还是从父类继承过来的。如:view.setValue(CGRectZero,key: "frame");
keyPath:除了能接受当前类的属性,还能接受当前类属性的属性,即可以接受关系链,如:view.setVale(5,keyPath:"layer.cornerRadius");

KVC的原理:(赋值原理和取值原理)
KVC赋值原理:setValue:forKey:的原理

  • 首先会按照setKey,_setKey的顺序查找方法,找到方法,直接调用方法并赋值;
  • 未找到方法,则调用+(BOOL)accessInstanceVariablesDirectly(是否可以直接访问成员变量,默认返回YES);
  • 若accessInstanceVaariablesDirectly方法返回yes,则按照_key,_isKey,key,isKey的顺序查找成员变量,找到直接赋值,找不到则跑出NSUnknowKeyExpection异常;
  • 若accessInstanceVariableDirecctly返回NO,那么就会调用setValue:forUnderfineKey:并跑出NSUnKnowKeyExpection异常;


    image.png

KVC取值的原理:valueForKey:

  • 首先会按照getKey,Key,isKey,_key的顺序查找方法,找到直接调用取值;
    -若未找到,则查看+(BOOL)accessInstanceVariableDirectly的返回值,若返回NO,则直接抛出NSUnkonwKeyExpection的异常;
    -若返回的是YES,则按照_key,isKey的顺序查找成员变量,找到则取值;
    -若找不到则调用valueForUnderfineKey:抛出NSUnkonwKeyExpection异常;


    image.png

注意

  • key 的值必须正确,如果拼写错误,则会出现异常。
  • 当key的值时没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来。
  • 因为类可以反复嵌套,所以有个keyPath的概念,keyPath就是用 . (点语法) 来把key链接起来,这样就可以根据这个路径访问下来。
  • NSArray/ NSSet等都支持KVC
  • 可以通过KVC访问自定义类型的私有成员。
  • 如果对非对象传递一个nil值,KVC会调用setNilValueForKey方法,我们可以重写这个方法来避免传递nil出现的错误,对象并不会调用这个方法,而是会直接报错。
  • 处理非对象,setValue时,如果赋值的对象时基本类型,需要将值封装成NSMNumber或者NSValue类型,valueForKey时,返回的是id类型对象,基本数据类型也会被封装成NSNumber或者NSValue。 valueForKey可以自动将值封装成对象,但是setValue:forKey:却不行。我门必须手动将值转换成NSNumber/NSValue类型才能进行传递。
  • 利用KVC进行字典和模型的转换

** KVO **
1.简单介绍
KVO全称是KeyValueObserving, 俗称“键值监听”,可以用于监听某个对象属性值的改变。

KVO是平托提供的在套事件通知机制。KVO和NSNotificationCenter都是IOS中的观察者模式的一种实现,区别是NSNotificationCenter可以说一对多的关系,而KVO说是一对一的;
2.KVO的使用
2.1KVO的使用步骤:
注册KVO监听:通过[addObserver:forKeyPath:options:context:]方法注册KVO,这样可以接收到keyPath属性的变化事件;
监听的实现:通过[observeValueForKeyPath:ofObject:chang:context]实现kvo的监听;

移除KVO监听:在不需要监听的时候,通过方法[removeObserver:forKeyPath:]移除监听;
2.2,KVO传值
可以通过方法context传入任意类型的对象,在接受消息回调的代码中可以接收到这个对象,是KVO的一种传值方式。
可以通过KVO在Model和Controller之间进行通信。
3.KVO的本质是改变setter方法的调用:
- 1、利用Runtime API动态生成一个子类NSKVONotifying_XXX,并且让instance对象的isa指针指向这个全新的子类,NSKVONotifying_XXX的super class指针指向原来的类;
- 2、当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数:


image.png

Runtime

Runtime简称 运行时。 OC 就是运行时机制,也就是把函数的调用推迟到运行时。

Runtime的作用:

  • 发送消息。
    方法调用的本质,就是让对象发送消息。
    obc_msgSend,只有对象才能发送消息,因此以objc开头
    使用消息机制的前提,必须导入import<objc/message.h>
  • 交换方法
    开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并保持原有的功能。
    方式一:继承系统的类,重写方法。
    方式二:使用runtime,交换方法
  • 动态添加方法
    开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类添加方法解决。
  • 给分类添加属性
    原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

Runtime消息传递
一个对象的方法像这样 [obj foo], 编译器专程消息发送 objc_msgSend(obj, foo),Runtime时执行的流程时这样的:

  • 首先,通过obj的isa指针找到它的class;
  • 在class 的 method list 找到 foo;
  • 如果class 中没找到foo, 就继续往它的superclass中找;
  • 一旦找到foo这个函数,就去执行它的实现的IMP;

但这种实现有个问题,效率低。但一个class往往只有20%的函数会被经常调用。每个消息都需要遍历一次objc_method_list 并不合理,如果把经常调用的函数缓存下来,就可以大大提高函数查询的效率/这也就是objc_class中另一个重要的成员objc_cache要做的事情,在找到foo后,把foo 的method_name作为key, method_imp作为value给存起来。当再次收到foo消息的时候,可以直接在cache里找到,避免去遍历objc_method_list。

分类和扩展

1、分类主要为某个类添加方法、属性、协议(我一般为系统的类添加方法和把复杂的类按照功能拆到不同的文件中进行解耦)

2、扩展主要为某个类添加原来没有的成员变量、方法、属性。(我一般用扩展来声明私有属性,或者把.h的只读属性重写成可读)

区别:

  • 分类是运行时把类的信息合并到类中,扩展是在编译时把类的信息合并到类中。
  • 分类声明的属性,只会生成setter/getter方法的声明,不会自动生成成员变量和getter/setter方法的实现
  • 分类不可为类添加实例变量,而扩展可以
  • 分类可以为类添加方法,而扩展只能声明方法不能实现

APP优化
APP卡顿
GCD在项目中的应用
定时器问题
APP加密加固
** APP性能优化 **

性能优化:
(1) 卡顿优化
(2) 耗电优化
(3) 启动优化
卡顿优化
屏幕成像的过程:CPU计算数据 -> GPU进行渲染 -> 屏幕发出 Vsync信号 -> 成像
假如屏幕已经发出了Vsync信号,但是GPU还没有渲染完成,则只能将上一次的数据限时出来,导致当前计算的帧数丢失,这样就产生了卡顿, 当前计算好的数据只能等待下一个周期去渲染。

解决卡顿的主要思路:尽可能减少GPU和CPU资源的消耗
按照60fps的刷帧率,每隔16ms就会有一次Vsync信号
针对CPU:
1、尽量使用轻量级的对象,如:不用处理时间的UI控件可以考虑使用CALAyer
2、不要频繁的调用UIView的相关属性;例如:frame、bounds、transform等等
3、尽量提前计算好布局,在有需要的时候一次性调整对应的属性,不要多次修改
4、AutoLayout会比直接设置frame消耗更多的CPU资源
5、图片size和UIImageView的size保持一致
6、控制县城的最大并发数
7、耗时操作放入子线程,如:文本尺寸计算、绘制、图片的解码绘制等。

针对GPU
1、尽量避免短时间内显示大量的图片
2、GPU能处理的尺寸最大纹理尺寸为4096*4096,超过这个尺寸就会占用CPU的资源
3、尽量减少透明试图的数量和层次
4、减少透明的试图(alpha < 1),不透明就设置opaque为YES
5、尽量避免离屏渲染
光栅化:layer.shouldRastersize = YES
蒙版:layer.mask
圆角:layer.maskTobounds = YES,layer.cormerRadius>0
阴影:未设置layer.shadowPath

(2)耗电优化
优化I/O操作
尽可能避免频繁写入小数据,最好一次性批量写入
读写大量重要数据时,可以用dispathc_io,它提供了基于GCD的异步操作文件的API,使用该API会优化磁盘访问
数据量大时,使用数据库来管理数据

网络优化
多次网络请求接过相同,尽量使用缓存
使用断点续传,否则网络不稳定时可能多次传输相同的内容
网络不可用时,不进行网络请求
让用户可以取消长时间或者速度很慢的网络操作,设置合适的超时时间
批量传输,如下载视频,不要传输很小的数据包,直接下载整个文件或者大块下载

定位优化
如果只是需要快速确定用户位置,用CLLocatuibManager的requestLocation方法定位,定位硬件会自动断电
若不是导航应用,尽量不要实时更新位置,定位结束就关闭定位服务
尽量降低定位的精度,如不适用精度最高的KCLLocationAccuracyBest
需要后台定位时,尽量设置pauseLocationUpdateAutomatically为YES,若用户不怎么移动的时候,系统会自动暂停位置更新

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    卑微的戏子阅读 641评论 0 1
  • (答案不唯一,仅供参考,文章最后有福利)目录 一、基础知识点 设计模式是什么? 你知道哪些设计模式,并简要叙述?设...
    ios南方阅读 6,432评论 0 11
  • 一、基础知识点 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去...
    软件iOS开发阅读 1,319评论 0 26
  • UIKit 1.UIView 和 CALayer 是什么关系? UIView 继承 UIResponder,而 U...
    Sephiroth_Ma阅读 2,282评论 0 25
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    sumrain_cloud阅读 347评论 0 1