1、iOS代码规范
https://developer.apple.com/library/archive/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html
Tips:
1>使用instancetype而不是用id,instancetype,标注了返回类型为当前类型,帮助编译器进行类型检查,和对象方法列表中是否包含调用方法的检查
2>使用property生命属性
3>使用NS_ENUM和NS_OPTIONS代替enum,使用NS_OPTIONS当使用bitmask的时候
4>初始化时使用NS_DESIGNATED_INITIALIZER标记指定初始化方法
5>使用ARC
使用NS_DESIGNATED_INITIALIZER时要注意下面几点:
The implementation of a designated initializer must chain to a superclass init method (with [super init...]) that is a designated initializer for the superclass.
指定初始化方法必须链式调用父类的指定初始化方法,即[super init...]
The implementation of a convenience initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init...]).
便利初始化方法必须调用本类的指定初始化方法,即[self init...]
If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.
继承时指定初始化方法必须实现
==================================分割线=====================================
2、iOS沙盒机制
https://www.jianshu.com/p/b5ce47de4f89
文档找不到了,以前记得是在file system那里面,现在怎么感觉变了,主要注意沙盒里面的目录,和哪些路径会被icloud备份,哪些会被清空
3、iOS应用的生命周期
https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle?preferredLanguage=occ
Respond to system notifications when your app is in the foreground or background, and handle other significant system-related events.
生命周期方法用来响应系统通知,以及处理其他和系统相关的事件
When your app’s state changes, UIKit notifies you by calling methods of the appropriate delegate object:
当你app状态发生改变时,UIKit通过调用相应的代理对象方法来通知你
1、 In iOS 13 and later, use UISceneDelegate objects to respond to life-cycle events in a scene-based app.
iOS13之后会使用UISceneDelegate来通知生命周期事件
2、 In iOS 12 and earlier, use the UIApplicationDelegate object to respond to life-cycle events.
iOS12之前使用UIApplicationDelegate来通知应用程序生命周期事件
Respond to Scene-Based Life-Cycle Events
响应Scene-Based生命周期
If your app supports scenes, UIKit delivers separate life-cycle events for each. A scene represents one instance of your app’s UI running on a device. The user can create multiple scenes for each app, and show and hide them separately. Because each scene has its own life cycle, each can be in a different state of execution. For example, one scene might be in the foreground while others are in the background or are suspended.
如果你的应用程序支持scenes,UIKit为每个scene分发独立的生命周期事件。一个scene代表设备上其中一个AppUI实例对象,用户可以为每个app创建多个scene,并且独立的对他们进行显示或者隐藏操作。因为每一个scene有他独立的生命周期,所以他们可以运行在不同的状态下,比如,一个scene或许是在前台而其他的scene处于后台或者挂起
The following figure shows the state transitions for scenes. When the user or system requests a new scene for your app, UIKit creates it and puts it in the unattached state. User-requested scenes move quickly to the foreground, where they appear onscreen. A system-requested scene typically moves to the background so that it can process an event. For example, the system might launch the scene in the background to process a location event. When the user dismisses your app's UI, UIKit moves the associated scene to the background state and eventually to the suspended state. UIKit can disconnect a background or suspended scene at any time to reclaim its resources, returning that scene to the unattached state.
下面的图展示了scenes的状态切换。当用户或者系统为app请求一个新的scene,UIKit创建它并把它置为unattached state(不属于任何状态的状态)。对于用户请求的scene,如果scenes出现在屏幕上,用户请求的scene会迅速移动到前台(进入foreground状态)。对于系统请求的scene,会被移动到后台以使它能处理事件(进入background状态)。举个例子,系统或许会启动一个scene进入后台(background)状态来处理一个定位事件。当用户不再展示app的界面,UIKit将相应的scene移动到后台(进入background状态)并最终挂起(进入suspended状态)。当UIKit需要回收资源时,处于后台(background状态)和挂起(uspended状态)状态的scene会被切断连接,并置为unattached state。
Use scene transitions to perform the following tasks:
1、When UIKit connects a scene to your app, configure your scene’s initial UI and load the data your scene needs.
2、When transitioning to the foreground-active state, configure your UI and prepare to interact with the user. See Preparing Your UI to Run in the Foreground.
3、Upon leaving the foreground-active state, save data and quiet your app’s behavior. See Preparing Your UI to Run in the Background.
4、Upon entering the background state, finish crucial tasks, free up as much memory as possible, and prepare for your app snapshot. See Preparing Your UI to Run in the Background.
5、At scene disconnection, clean up any shared resources associated with the scene.
In addition to scene-related events, you must also respond to the launch of your app using your UIApplicationDelegate object. For information about what to do at app launch, see Responding to the Launch of Your App.
Respond to App-Based Life-Cycle Events
响应 App-Based生命周期事件
In iOS 12 and earlier, and in apps that don't support scenes, UIKit delivers all life-cycle events to the UIApplicationDelegate object. The app delegate manages all of your app’s windows, including those displayed on separate screens. As a result, app state transitions affect your app's entire UI, including content on external displays.
在iOS12之前和不支持scenes的app中,UIKit将所有的生命周期事件传递给 UIApplicationDelegate 对象。它管理着app的所有window,甚至是展示在分离的屏幕上的内容?这就导致,app状态的切换会影响你所有的用户界面,包括其他地方的显示内容。
The following figure shows the state transitions involving the app delegate object. After launch, the system puts the app in the inactive or background state, depending on whether the UI is about to appear onscreen. When launching to the foreground, the system transitions the app to the active state automatically. After that, the state fluctuates between active and background until the app terminates.
下面的图展示了状态切换调用代理对象的过程。app启动后,系统将app置为inactive或者background状态,具体的状态决定于你的UI界面是否将要展现在屏幕上。当进入前台时,系统自动切换app状态为active状态。之后app在前台和后台切换直到应用终止(进入terminates状态)
4、iOS消息转发
https://www.jianshu.com/p/97c13070ccdb
5、iOS view的生命周期
// Allows you to perform layout before the drawing cycle happens. -layoutIfNeeded forces layout early
- (void)setNeedsLayout;
- (void)layoutIfNeeded;
- (void)layoutSubviews; // override point. called by layoutIfNeeded automatically. As of iOS 6.0, when constraints-based layout is used the base implementation applies the constraints-based layout, otherwise it does nothing.
- (void)setNeedsDisplay;
- (void)drawRect;
5.1 iOS越来越多的会问内存分布,所以用命令行还原OC的C代码是个不错的操作
https://www.jianshu.com/p/30ec92f1e707
https://www.jianshu.com/p/aa7ccadeca88
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Preson+Test.m
5.1.1 OC源码
https://opensource.apple.com/tarballs/objc4/
5.2 底层管理系列文章
https://www.jianshu.com/notebooks/24110540
6、iOS Category & Extension
https://www.jianshu.com/p/c92b17a36b9e
https://www.jianshu.com/p/fa66c8be42a2
http://www.cocoachina.com/articles/19163
问: Category的实现原理,以及Category为什么只能加方法不能加属性?答:分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
extension是类的一部分,你不能为系统类添加extension,但是你可以通过继承系统类自定义的类中添加extension,extension可以添加实例变量。extension是在编译器就决定的。
category的可以为一个类扩充方法,但是它起作用的时期是运行时,所以我们不能通过category来为一个类添加实例变量,category可以进行方法覆盖,方法查找的优先级是catefory>子类>父类
category可以通过关联对象添加实例属性,也可以通过static添加类属性
7、iOS KVO & KVC
https://www.jianshu.com/p/5477cf91bb32
https://www.jianshu.com/p/d509c78c59bd
https://blog.csdn.net/qq_18505715/article/details/80205796
https://www.jianshu.com/p/1d39bc610a5b
KVO的全称 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
我们平时的使用场景如下
[p1 addObserver:self forKeyPath:@"age"options:options context:nil];
p1.age=10;
[p1 removeObserver:self forKeyPath:@"age"];
-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary
*)change context:(void*)context{ NSLog(@"监听到%@的%@改变了%@",object,keyPath,change);
}
KVO实现的机制是isa-swizzling
Automatic key-value observingisimplemented using a technique called isa-swizzling.The isa pointer,asthe name suggests,pointstotheobject’sclasswhich maintains a dispatch table.This dispatch table essentially contains pointerstothe methods theclassimplements,among otherdata.When an observerisregisteredforan attribute of anobjectthe isa pointer of the observedobjectismodified,pointingtoan intermediateclassrather than at thetrueclass.As a result the value of the isa pointer does not necessarily reflect theactualclassof the instance.You should never rely on the isa pointertodetermineclassmembership.Instead,you should use theclassmethodtodetermine theclassof anobjectinstance.
被KVO监听的对象,在运行时,会生成一个新的类,继承原来的类,如上面链接中的NSKVONotifyin_Person,那么在调用setage方法的时候,会去查找NSKVONotifyin_Person中的setage方法,这个新方法调用前会调用willchagevalueforkey,然后调用super的setage方法,最后调用结束后会调用didchangevalueforkey。另外,didchangevalueforkey方法会调用我们的回调方法observeValueForKeyPath:ofObject:change:context:
KVC是一个强大的赋值取值工具
KVC赋值原理:
1、查找是否实现setter方法,如果有,优先调用setter方法完成赋值(注意:set后面的键的第一字字母必须是大写!!)
2、当没找到setter方法,调用accessInstanceVariablesDirectly询问。
如果返回YES,顺序匹配变量名与 _
,_is , ,is ,匹配到则设定其值 如果返回NO,结束查找。并调用 setValue:forUndefinedKey:报异常
3、如果既没有setter也没有实例变量时,调用 setValue:forUndefinedKey:
KVC取值原理:
1、查找是否实现getter方法,依次匹配`-get
` 和 `- ` 和 `is `,如果找到,直接返回。 需要注意的是 :
如果返回的是对象指针类型,则返回结果。
如果返回的是NSNumber转换所支持的标量类型之一,则返回一个NSNumber
否则,将返回一个NSValue
2、当没有找到getter方法,调用accessInstanceVariablesDirectly询问
如果返回YES, _
,_is , ,is ,找到了返回对应的值 如果返回NO,结束查找。并调用 valueForUndefinedKey: 报异常
3、如果没找到getter方法和属性值,调用 valueForUndefinedKey: 报异常
8、iOS block
https://www.jianshu.com/p/c99f4974ddb5
block的方法调用是通过函数指针的方式实现的,当我们创建一个block时,系统会将其转变为一个对象,总共有三种globalblock,stackblock和mallocblock,stackblock定义在栈上,mallocblock定义在堆上,如果使用block时,栈已经被销毁,那么就会出问题,所以我们需要用copy方法将栈上的block拷贝到堆上,在ARC环境下,无论是strong还是copy修饰的block属性在赋值操作时都会自动进行copy操作,所以属性声明的时候,我们使用strong或者copy都可以,但是MRC下就必须使用copy修饰
block捕获局部变量时,如果变量是基本数据类型,那么block在生成block对象时,会加入相应的变量来存储变量的值,不过在block对象生成时,基本数据类型是通过值传递,这里应该是考虑到局部变量的生命周期,如果是传递地址,block在调用时,局部变量已经消亡,那么就会产生问题,因此这里是值传递而不是引用传递,所以在block内部改变基本数据类型的值时,值不变。对于用static修饰的基本数据类型,static保证了变量的生命周期,所以在block捕获时,采用的是引用传递,所以对于static修饰的基本数据类型,修改值时,值会改变。对于全局变量,block不会生成相应的变量来保存值或者引用,而是在方法调用时直接使用全局变量。目前我们在xcode里面如果希望测试局部变量捕获的活,对于auto类型,xcode会提示需要使用__block修饰,对于static和全局类型,xcode不会报错。
如果变量是一个对象,生成block对象时会对捕获的对象进行一个强引用,即block销毁时,变量才能得到释放,使用__weak修饰后,在生成的block对象中对变量的引用也会变为__weak引用。并且对于对象类型,block对象还会生成copy和dispose方法来进行内存管理。如果我们在block中对对象赋值,xcode会报错,提示添加__block修饰,但是如果我们是给对象的一个属性赋值,却是可以的,并且可以赋值成功。
当使用__block修饰时,__block修饰会将变量封装为一个对象,这个对象会拥有一个和变量相对应的属性,这一点和我们上面修改对象的属性可以成功是相对应的。此外在外部获取这个变量时,会直接指向,封装的新对象对应属性的地址。
block在方法内部捕获变量时,要注意不论对象方法还是类方法都会默认将self作为参数传递给方法内部,既然是作为参数传入,那么self肯定是局部变量。上面讲到局部变量肯定会被block捕获。
block循环引用,对象使用strong或者copy强引用block作为属性,block捕获了对象,block内部对对象进行一个强引用。所以通过__weak修饰,将block对对象的捕获改为弱引用。从而解除循环引用。
9、iOS关联对象
https://www.jianshu.com/p/0f9b990e8b0a
关联对象存储在全局的manager里面,我们通过对象以及相应的key,来进行set,get操作
10、RunLoop
https://www.jianshu.com/p/de752066d0ad
在app启动时我们会调用main方法,main方法会去开启主线程的runloop,runloop是一个处理事件的循环,它可以处理的事件主要包括source0(点击事件,用户事件),souce1(系统事件),以及TimeProfile(处理Timer),我们之所以必须有runloop是因为,runloop可以理解为一个死循环,让线程处于运行状态,如果没有runloop,程序启动后,执行完main方法,就会退出,这一点类似我们在写C程序时,为了看到程序执行的窗口,我们往往会在程序末尾添加一句getchar(),让程序处于等待输入状态,防止程序的立即退出。runloop的思想就是这样,当有事件需要处理时,runloop就会去处理事件,当没有事件需要处理时,runloop会进入休眠状态,等待下一次的输入。
一个runloop下面管理着多个runloopmode,每个runloopmode又管理着自己的sources,也就是说一个source事件只能在其相应的runloopmode下面进行响应,总共有五种不同的mode,常用的mode有defaultmode,trackingmode和commommode,当我们滚动视图时,如scrollview,我们便会切换为trackingmode,commommode允许我们将一个source添加到多个mode中,从而让我们在切换mode时,source事件不受影响。
当我们创建timer时,在主线程中,如果我们使用scheduledTimerWithTimeInterval的方式创建,timer可以运行,不过为了避免滑动mode切换导致timer计时出错,我们应该将其添加进commonmode,如果使用timerwith的方式创建,调用start,timer并不会运行,执行一次后退出,我们需要将其添加进runloop,在子线程中,上面两种创建方式均需要将timer加入子线程的runloop,如果runloop没有运转还需要调用,runloop的run方法。所以我们日常可以优先考虑使用GCDTimer。
不仅主线程有runloop,所有的线程都有它唯一的runloop,runloop默认是不会创建的,只有主动去获取runloop的时候,runloop才会去创建,也就是说runloop是懒加载的,它的创建时机是第一次获取runloop的时候,主线程的runloop在我们调用main方法的时候被创建和开启,如果我们要开启子线程的runloop,那我们可以在相应的线程中用Foundation的currentRunloop的方式去开启,或者Core Foundation的CFRunLoopGetCurrent()方法。
当我们创建一个子线程时,如果不开启runloop,子线程运行结束后就会退出,如果我们希望子线程常驻呢?那我们就可以开启子线程的runloop,让线程可以等待输入而不至于退出,不过这里有个地方需要注意,我们必须为runloop添加一个source,否则runloop不会进入事件循环,直接退出
1、每条线程都有唯一的一个与之对应的RunLoop对象
2、RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3、主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
4、RunLoop在第一次获取时创建,在线程结束时销毁
11、RunTime
https://www.jianshu.com/p/8acdedf9c1af
以上几个内容建议阅读5.1.1的系列文章链接,不过文集的作者写的比较细,所以建议阅读的时候,从第一篇开始,方便你的理解
12、NSCache
https://www.jianshu.com/p/239226822bc6
NSCache的使用很方便,提供了类似可变字典的使用方式,但它比可变字典更适用于实现缓存,最重要的原因为NSCache是线程安全的,使用NSMutableDictionary自定义实现缓存时需要考虑加锁和释放锁 ,NSCache已经帮我们做好了这一步。其次,在内存不足时NSCache会自动释放存储的对象,不需要手动干预,如果是自定义实现需要监听内存状态然后做进一步的删除对象的操作。还有一点就是NSCache的键key不会被复制,所以key不需要实现NSCopying协议。
NSCache删除缓存中的对象会在以下情形中发生:
1、NSCache缓存对象自身被释放
2、手动调用removeObjectForKey:方法
3、手动调用removeAllObjects
4、缓存中对象的个数大于countLimit,或,缓存中对象的总cost值大于totalCostLimit
5、程序进入后台后
12.1、SDWebImage的缓存策略
https://www.jianshu.com/p/239226822bc6
SDWebImage,内存缓存使用NSCache,继承NSCache,添加内存警告时释放内存的操作,硬盘缓存使用文件缓存。
下面是SDWebImage的磁盘缓存的LRU思路,这里是通过设置最大缓存时间和最大缓存大小来做清除操作。
SDWebImage判断我们config里面是希望使用创建日期还是最近修改日期来对最长生存期进行判断,根据我们的判断条件,SD去缓存路径下读取文件信息,一个是取每个文件的创建日期或者最近修改日期,以及文件的大小。首先根据我们选定的判断生存期条件来对过期缓存进行一次删除。同时累加计算剩余的缓存文件的总的大小,如果超过限定大小的二分之一的话,则按最近修改日期去删除缓存文件,直到达到我们的缓存大小的要求。
LRU算法的实现
https://www.cnblogs.com/Dhouse/p/8615481.html
很多面试中会问SD的cache的LRU的思路,但是,SD的Cache是继承NSCache的,在NSCache的基础上添加了监听内存警告清空的逻辑,所以SD本身应该是没有Cache层的LRU的,有的话应该也是苹果在NSCache中实现的,不过LRU的确实也需要我们了解。
LRU有多种实现方式,最简单的方式就是维护一个cache的链表,FIFO,队尾入队,队首出队,如果cache命中了,则将该记录移动到队尾,如果未命中,则在队尾添加新纪录,队首的记录被pop出队。
还有一种思路叫LRU-K,我们上面说的情况就是LRU-1的情况,如果我们维护一个历史记录表,只要访问过的记录都将其放入该表中,同时维护该记录的访问次数,当访问次数达到K时,我们将访问记录中对应的记录清空,并将这条记录放入缓存表,缓存表进行FIFO的逻辑。不过这样的方式,历史记录需要被访问K次才能清空,这会让我们维护一个庞大的历史记录表,所以我们可以在此基础上稍作改进。
这种思路是多队列,先说两个队列的情况,上面说了历史记录清空太慢了,那我们将历史记录也执行FIFO的逻辑就可以了呀,所以当历史记录FIFO队列中的记录没达到K次时,对于两个队列的情况,K一般取2,当记录只被访问过一次时,那它在一次访问的队列中执行FIFO的逻辑,当其被第二次访问时,则进入第二级的缓存队列,在这个新的队列中执行FIFO逻辑,如果再次被访问,则将其移动到队首。
我们也可以使用三个或者更多的队列,一般厚一层队列的访问次数比前一层多一。
我们在实际使用中,由于第一种的LRU最容易实现,所以很多情况是使用第一种思路来实现的,不过具体应该根据不同的需求选择不同的方式来实现。
13、NSDictionary
https://www.jianshu.com/p/c815b4247340
要求Key不能修改,并且需要key支持NSCopying协议,所以只适合字符串做key
13.1、NSHashTable
可变的,可以持有元素的弱引用,功能消防NSSet
13.2、NSMapTable
支持使用对象做key,实现了对象到对象的映射,允许持有对象的弱引用,可变的。
15、iOS锁机制
https://www.jianshu.com/p/b1edc6b0937a
临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。
自旋锁:是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。
读写锁:是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。
信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
互斥锁和递归锁
https://www.cnblogs.com/bigberg/p/7910024.html
递归锁可以加锁多次而不会死锁,互斥锁加锁多次会造成死锁,递归锁会采用计数的方式,在加锁和解锁次数相等时,会释放锁
自旋锁:OSSpinLock
https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/
https://baike.baidu.com/item/自旋锁/9137985?fr=aladdin
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
OSSpinLock为什么不再安全?自旋锁是不停的访问锁的释放,遇到优先级反转(当一个低优先级的线程持有锁,而这时遇到一个高优先级的线程来访问锁),高优先级的线程在不停的访问锁是否释放,所以是忙等,在时间片调度上不会让出时间片,低优先级的线程因为在线程调度的层面上要让出时间片给高优先级的线程,没办法去执行完任务然后让出锁,所以没有别的操作的情况下就会出现死锁。
iOS10之后,苹果使用os_unfair_lock来取代OSSpinLock,os_unfair_lock是一种互斥锁,它不会向自旋锁那样忙等,而是等待线程会休眠。
互斥锁mutex
1.NSLock:是Foundation框架中以对象形式暴露给开发者的一种锁,(Foundation框架同时提供了NSConditionLock,NSRecursiveLock,NSCondition)
2.pthread_mutex:
3.@synchronized:
读写锁
读写锁又称共享-互斥锁,
pthread_rwlock:
递归锁
递归锁有一个特点,就是同一个线程可以加锁N次而不会引发死锁。
1.NSRecursiveLock:
2.pthread_mutex(recursive):
pthread_mutex锁也支持递归,只需要设置PTHREAD_MUTEX_RECURSIVE即可
条件锁
1. NSCondition:
2.NSConditionLock:
信号量
dispatch_semaphore:
16、iOS多线程
16.2、NSThread
https://www.jianshu.com/p/973f0a5e0ec3
NSThread是对内核mach kernel中的mach thread的封装,所以,每一个NSThread的对象其实就是一个线程,我们创建一个NSThread对象也就意味着我们创建了一个新的线程。
NSThread线程结束时会立即退出。我们可以通过setname方法给NSThread命名。
使用NSThread来编写多线程程序有不少问题,线程在执行完成后就会退出,每次执行任务都需要创建一个线程很浪费资源,其次是需要我们自行进行同步操作,自行管理线程的生命周期
16.1、 GCD
https://www.jianshu.com/p/e9d8a087f6c0
多线程编写方式GCD,GCD全称Grand Central Dispatch是苹果提供的一个多核编程的解决方案,在真正意义上实现了并行操作,而不是并发。
GCD使用线程池模型来执行用户提交的任务,所以它比较节约资源,不需要为每个任务都重新创建一个新的线程,GCD不需要自行编写并行代码,而是自动进行多核的并行计算,自动管理线程的生命周期,如:使用线程池管理线程的创建和销毁,线程的调度,任务的调度等,用户只需要编写任务代码并提交即可。
GCD中有两个比较重要的概念:
任务和队列。
GCD的任务
任务顾名思义就是我们需要执行的代码块,可以是一个方法也可以是一个block,就是我们需要线程为我们完成的工作,编写完成的任务只需提交给GCD的队列,即可自动帮我们完成任务的调度,以及线程的调度,可以很方便的以多线程的方式执行。
GCD的队列
队列用于管理用户提交的任务,GCD的队列有两种形式,串行队列和并发队列:
串行队列: GCD底层只维护一个线程,任务只能串行依次执行。
并发队列: GCD底层使用线程池维护多个线程,任务可并发执行。
不论是串行队列还是并发队列都使用FIFO 先进先出的方式来管理用户提交的任务。
对于串行队列来说,GCD每次从串行队列的队首取一个任务交给唯一的一个线程来处理,直到前一个任务完成后,才继续从队列中取下一个任务来执行,因此,串行队列中的任务执行严格按照提交顺序,并且后一个任务必须等前一个任务执行完成后才可以执行。
对于并发队列来说,GCD每次从并发队列的队首取一个任务,并将这个任务按照任务调度分发给多个线程中的某一个线程,此时不需要等待其完成,如果队列中还有其他任务继续从队列中取出并分发给某一个线程来执行,由于底层由线程池管理多个线程,每个任务的时间复杂度不同再加上线程调度的影响,后提交的任务可能先执行完成。但对于单个线程来说,只能按顺序执行,比如某个线程被安排了多个任务,那这个线程就只能按提交顺序依次执行任务。
所以,我们在使用GCD时也就很简单了,只需要
创建或获取系统队列、编写任务并提交任务到队列即可
注意GCD的死锁
常用接口:
1、获取队列,获取主线程队列,获取全局异步队列,创建队列(选择串行队列还是并行队列)
2、提交任务,同步提交任务,异步提交任务
3、只执行一次的任务 dispatch_once
4、栅栏函数
5、GCD timer
6、按组提交 dispatch_group_async dispatch_group_notify
7、信号量,使用计数的方式实现同步
https://www.jianshu.com/p/0dc231328ece
1.dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
2.dispatch_semaphore_signal:发送一个信号,让信号总量加1
3.dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
其中,串行队列,栅栏函数,信号量都可以实现同步操作
https://www.jianshu.com/p/72aa2ee5ee8e
16.3、NSOperation
https://www.jianshu.com/p/bf0916ee1492
NSOperation和GCD类似,强调操作和操作队列两个概念,任务即NSOperation,队列即NSOperationQueue。
使用NSOperation和NSOperationQueue来编写多线程程序非常简单,只需要创建一个任务对象,创建一个执行队列或者和获取主线程一样获取一个主任务队列,然后将任务提交给队列即可实现并发,如过你想要串行只需要将队列的并发数设置为一即可。
我们需要自定义继承NSOperation类并重写相关方法,OC也提供了两个子类供我们使用NSBlockOperation和NSInvocationOperation。
/*
对于并发Operation需要重写该方法
也可以不把operation加入到队列中,手动触发执行,与调用普通方法一样
*/-(void)start;/*
非并发Operation需要重写该方法
*/-(void)main;
start和main中放置要执行的任务,同时并发的任务需要实现isExecuting,isFinished,isAsynchronous等状态方法
NSOperationQueue,我们通过设置maxConcurrentOperationCount限制并发任务数,使队列成为并发队列或者串行队列。我们可以一次性向队列添加过个任务,并设置是否阻塞当前队列,我们可以通过waitUntilAllOperationsAreFinished阻塞当前队列。我们可以为操作添加依赖,使其按顺序执行。
17、iOS静态库与动态库
https://www.cnblogs.com/wangyf-iOS/p/8968595.html
https://blog.csdn.net/weixin_33842328/article/details/92130530
静态库:.a库、.framework(mach-o type 选择静态库)
静态库注意:-Objc https://www.jianshu.com/p/183196c56022
动态库:.framework(mach-o 选择动态库) iOS8之后才开始支持
.a库需要暴露所有的头文件。.framwork可以选择性暴露头文件。
静态库在引入时直接link binary就可以了。动态库在引入时需要embedded。
对于静态库,在链接时,Xcode会自动筛选出静态库中的不同architecture合并到对应处理器架构的主可执行二进制文件中;而在打包时,Xcode会自动忽略掉静态库中未用到的architecture,eg. i386、x86_64等模拟器架构;
对于动态库,在编译打包时,Xcode会直接拷贝整个动态framework文件到最终的ipa包中,只有在App真正运行的时候才会动态链接。 但是苹果是不允许最终上传到App Store Connect后台的ipa文件包含模拟器架构的,会报Invalid Architectures。
18、iOS NSTimer循环引用和内存泄漏以及weakTimer
https://www.jianshu.com/p/823ef4fb63bc
https://www.jianshu.com/p/7dfb42b7c4a3
使用NSTimer时
+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;
这两个方法本身会使timer强引用target,而如果我们的timer又恰巧是target的一个强引用的属性我们就会导致循环引用。那我们把target对timer改成weak属性可以么,确实,这样就解决了target对timer的引用。可是单单 timer对target对象强引用就已经是个很重要的问题了,因为存在着timer的引用,target就不会被释放,那他的delloc在引用结束前都不会调用。当timer中repeat设置为NO时,timer到时间触发执行action后即对target不再引用;当timer中repeat设置为YES时,timer会持续持有target,直到手动invalidate timer后会释放。并且注意哪怕repeat是NO,但是如果你设置一个无限长的触发时间,timer就会一直持有target不会释放。如果要手动打破这个引用,那我们需要主动调用timer invalidate。但是如果你是在delloc中调用的delloc中去调用这个方法,那你就中招了,timer引用了target,target不会调用dealloc,所以invalidate不会调用,而invalidate不会调用,timer就不会停止,timer不停止则对target的引用就不会停止。
所以解决这个问题最重要的就是打破timer对target的引用。
苹果新的不使用target而是用block的方式的API本身就解决了这个问题,不过iOS10以后才能使用。但是这为我们提供了一种思路,使用Block。我们可以创建一个NSTimer的分类,然后在里面实现一个block方式的timer,这种方式的思路是借助了第三者来充当target,从而绕开了timer实例对真实target的引用。
+ (NSTimer*)tt_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeIntervalblock:(void(^)(void))inBlockrepeats:(BOOL)inRepeats {
void(^block)(void) = [inBlock copy];
NSTimer*timer = [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(__executeTimerBlock:) userInfo:block repeats:inRepeats];
return timer;
}
+ (NSTimer*)tt_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void(^)(void))inBlock repeats:(BOOL)inRepeats {
void(^block)(void) = [inBlockcopy];
NSTimer*timer = [self timerWithTimeInterval:inTimeIntervaltarget:selfselector:@selector(__executeTimerBlock:)userInfo:blockrepeats:inRepeats];
return timer;
}
+ (void)__executeTimerBlock:(NSTimer *)inTimer {
if([inTimer userInfo]) {
void(^block)(void) = (void(^)(void))[inTimeruserInfo];
if(block) {
block();
}
}
}
上面这个方法是通过寻找一个代理来作为timer的target,还有一种思路是通过消息转发。这些上面的文章链接里面讲的很清楚。
https://www.jianshu.com/p/823ef4fb63bc
17、iOS网络编程
17.1、NSURLSession
https://blog.csdn.net/majiakun1/article/details/38133433
https://blog.csdn.net/majiakun1/article/details/38133703
https://blog.csdn.net/majiakun1/article/details/38133789
https://developer.apple.com/documentation/foundation/nsurlsession?language=objc
NSURLSession has a singleton sharedSession session (which has no configuration object) for basic requests. It’s not as customizable as sessions you create, but it serves as a good starting point if you have very limited requirements. You access this session by calling the shared class method. For other kinds of sessions, you instantiate a NSURLSession with one of three kinds of configurations:A default session behaves much like the shared session, but allows more configuration, and allows you to obtain data incrementally with a delegate.Ephemeral sessions are similar to shared sessions, but don’t write caches, cookies, or credentials to disk.Background sessions let you perform uploads and downloads of content in the background while your app isn't running.See Creating a Session Configuration Object in the NSURLSessionConfiguration class for details on creating each type of configuration.
Types of URL Session Tasks
Within a session, you create tasks that optionally upload data to a server and then retrieve data from the server either as a file on disk or as one or more NSData objects in memory. The NSURLSession API provides three types of tasks:
Data tasks send and receive data using NSData objects. Data tasks are intended for short, often interactive requests to a server.
Upload tasks are similar to data tasks, but they also send data (often in the form of a file), and support background uploads while the app isn’t running.
Download tasks retrieve data in the form of a file, and support background downloads and uploads while the app isn’t running.
Using a Session Delegate
The tasks in a session also share a common delegate that lets you provide and obtain information when various events occur—when authentication fails, when data arrives from the server, when data is ready to be cached, and so on. If you don’t need any of the features provided by a delegate, you can use this API without providing one by passing nil when you create a session.
Important
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until it exits.
Asynchronicity and URL Sessions
Like most networking APIs, the NSURLSession API is highly asynchronous. It returns data to your app in one of two ways, depending on the methods you call:
By calling a completion handler block when a transfer finishes successfully or with an error.
By calling methods on the session’s delegate as data is received and when the transfer is complete.
In addition to delivering this information to delegates, the URLSession API provides status and progress properties that you can query if you need to make programmatic decisions based on the current state of the task (with the caveat that its state can change at any time).
URL sessions also support canceling, restarting, resuming, and suspending tasks, and provide the ability to resume suspended, canceled, or failed downloads where they left off.
Protocol Support
The NSURLSession class natively supports the data, file, ftp, http, and https URL schemes, with transparent support for proxy servers and SOCKS gateways, as configured in the user’s system preferences.
NSURLSession supports the HTTP/1.1 and HTTP/2 protocols. HTTP/2 support, as described by RFC 7540, requires a server supporting Application-Layer Protocol Negotiation (ALPN).
You can also add support for your own custom networking protocols and URL schemes (for your app’s private use) by subclassing NSURLProtocol.
App Transport Security (ATS)
Starting in iOS 9.0 and OS X 10.11, a new security feature called App Transport Security (ATS) is enabled by default for all HTTP connections made with NSURLSession. ATS requires that HTTP connections use HTTPS (RFC 2818).
For more information, see NSAppTransportSecurity in the Information Property List Key Reference.
NSCopying Behavior
Session and task objects conform to the NSCopying protocol as follows:
When your app copies a session or task object, you get the same object back.
When your app copies a configuration object, you get a new copy that you can independently modify.
Thread Safety
The URL session API itself is fully thread-safe. You can freely create sessions and tasks in any thread context. When your delegate methods call the provided completion handlers, the work is automatically scheduled on the correct delegate queue.
Warning
The system may call the URLSessionDidFinishEventsForBackgroundURLSession: session delegate method on a secondary thread. However, in iOS, your implementation of that method may need to call a completion handler provided to you in your application:handleEventsForBackgroundURLSession:completionHandler: app delegate method. You must call that completion handler on the main thread.
NSURLSession 是NSURLSessionTask的工厂,同时管理着NSURLSessionTask的生命周期,NSURLSession将NSURLSessionConfigration与NSURLSessionDelegate组装起来,在生产NSURLSessionTask时将config和Delegate与task关联起来。NSURLSession从网络分层来说,更像是会话层,管理着我们的会话的生命周期,允许我们取消会话,或者恢复会话。如果我们要实现get,post等请求,或者如果我们要实现对header的自定义,那我们需要对NSURLRequest(或NSURLMutableRequest)对象进行进一步详细的配置。
17.2、AF的流程
前面我们说过NSURLSession将NSURLSessionConfigration与NSURLSessionDelegate组装起来,在生产NSURLSessionTask时将config和Delegate与task关联起来,那在AF中AFURLSessionManager类将这一过程包装了起来,它管理着代理并将其转化成block的形式,同时维护着completionQueue来处理delegate的返回,它还将生成的tasks保存起来方便管理,AFHTTPResponseSerializer对请求返回做了序列化工作,它封装了Json、XML、Plist、Image、Compound的解析,这个解析过程也是发生在AFURLSessionManager的方法返回中。AFSecurityPolicy规定了SSL通信的一些规则,这些也是在AFURLSessionManager证书挑战等逻辑中做处理。AFNetworkReachabilityManager管理着网络连接状态,AFURLSessionManager保留了一个AFNetworkReachabilityManager的实例。
AFHTTPSessionManager是AFURLSessionManager的子类,在这个类中最主要的就是生成了http的get、post、put、delete等http请求,而这些请求最主要的是通过AFURLRequestSerialization类对NSMutableRequest进行封装来达到目的。
17.1、YYModel、MJExtension
17.2、Protocol Buffer
https://www.jianshu.com/p/25baebc411fe
17.8、YYModel是如何进行性能测试的
18、iOS优化
instrument使用
超参微调
20、iOS动画
其他
1、迭代和递归的区别
https://baike.baidu.com/item/迭代/8415523
https://baike.baidu.com/item/递归/1740695
迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。
重复执行一系列运算步骤,从前面的量依次求出后面的量的过程。此过程的每一次结果,都是由对前一次所得结果施行相同的运算步骤得到的。例如利用迭代法*求某一数学问题的解。
对计算机特定程序中需要反复执行的子程序*(一组指令),进行一次重复,即重复执行程序中的循环,直到满足某条件为止,亦称为迭代。
程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
我觉得百科下面这段话挺好的。
递归,就是在运行的过程中调用自己。
构成递归需具备的条件:
函数嵌套调用过程示例
1. 子问题须与原始问题为同样的事,且更为简单;
2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
在数学和计算机科学中,递归指由一种(或多种)简单的基本情况定义的一类对象或方法,并规定其他所有情况都能被还原为其基本情况。
总的来说,递归是种技巧,通过不断的调用自身,把一个大问题,简化成为一个小问题。
所以,我们在构造递归的时候要先去发掘子问题,找到最小的子问题或者说最小的集合或者最小的组合?这是构造递归的关键
2、工厂模式
https://www.runoob.com/design-pattern/factory-pattern.html //工厂模式
https://www.runoob.com/design-pattern/abstract-factory-pattern.html //抽象工厂
工厂模式就是生产具体的类的,最常见的就是我们写一个协议,一堆遵循这个协议的类都可以实现协议的方法,拥有同样的一些特性,就像是从工厂生产出来的同一批次的产品,这就属于工厂模式
抽象工厂是生产工厂的工厂
1、说一下OC的反射机制;
9、遇到过BAD_ACCESS的错误吗?你是怎样调试的?
13、如何去设计一个方案去应对后端频繁更改的字段接口?
说一下autoreleasePool的实现原理。
AutoreleasePool 与 runloop 与线程是一一对应的关系
AutoreleasePool 在 runloop 在开始时被push,在runloop休眠时(beforewaiting状态)pop
20、如何设计一个网络请求库?
22、说一下UITableViewCell的卡顿你是怎么优化的?
设计一个检测主线和卡顿的方案。
https://www.jianshu.com/p/b04b72ea5e83
FPS 降低
CPU 占用率很高
主线程 Runloop 执行了很久
load和initialize方法分别在什么时候调用的?
https://www.jianshu.com/p/b9b60b3e4bbd
initialize方法调用
+initialize方法会在类第一次接收到消息时调用
本质通过objc_msgSend(cls, SEL_initialize)进行调用
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点:
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用
NSNotificationCenter是在哪个线程发送的通知?
https://www.jianshu.com/p/cc3536a48cbb
NSNotificationCenter消息的接受线程是基于发送消息的线程的