# 运行时
```
1、程序中任何代码都会被转化成runtime的C代码执行,如[target doSomeThing]会转化成objc_msgSend(tagert,@selector(doSomeThing))
2、OC中一切都是对象,实例,类本质也是对象,在runtime中结构体表示。
3、相关定义
* typedef struct objc_method *Method; 方法
* typedef struct objc_ivar *Ivar;实例变量
* typedef struct objc_category *Category;实例Category
* typedef struct objc_property *objc_property_t;类中声明的属性
4、基于以上,可以实时获取当前类中所有属性,方法,变量
* 获取属性:objc_property_t *propertyList = class_copyPropertyList([self class], &count);
* 获取方法:Method *methodList = class_copyMethodList([self class], &count);
* 获取变量:Ivar *ivarList = class_copyIvarList([self class], &count);
* 获取协议:__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
5、方法调用
* 如果用实例对象调用实例方法,会到实例的isa指针指向的对象(类对象)中操作;
* 如果调用的是类方法,会到类对象的isa指针指向的对象(元类对象)中操作。
* 寻找过程:
* 首先在操作对象中的缓存方法列表中寻找
* 若无,在操作对象中的方法列表中寻找
* 若无,去父类指针指向对象中执行1,2
* 依次遍历,若遍历到根类还没有,转向拦截调用(若没有实现,程序报错)
* 拦截调用:
* + (BOOL)resolveClassMethod:(SEL)sel;当调用一个不存在的类方法的时候,会调用此方法,默认返回NO
* + (BOOL)resolveInstanceMethod:(SEL)sel;当调用不存在的实例方法时,会调用次方法。
* - (id)forwardingTargetForSelector:(SEL)aSelector;将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
* - (void)forwardInvocation:(NSInvocation *)anInvocation; 将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
6、关联对象
//首先定义一个全局变量,用它的地址作为关联对象的key
static char associatedObjectKey;
//设置关联对象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//获取关联对象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey); NSLog(@"AssociatedObject = %@", string);
7、方法交换
* 通常写在load方法中,保证执行时间靠前,且只执行一次
* // swizzing method for instance
#define swizzing_sel_instance(_cls_, _sel_1, _sel_2) {\
Method method1 = class_getInstanceMethod(_cls_, _sel_1);\
Method method2 = class_getInstanceMethod(_cls_, _sel_2);\
ASSERT(method1 != NULL);\
ASSERT(method2 != NULL);\
LOG(@"swizzing (%@), -%@(0x%08lx) ==> -%@(0x%08lx), ", NSStringFromClass(_cls_), NSStringFromSelector(_sel_1), (long)method1, NSStringFromSelector(_sel_2), (long)method2);\
if (method1 && method2) {\
method_exchangeImplementations(method1, method2);\
}\
}
```
# 内存管理
```
1、只有OC对象才需要管理,非OC对象不需要内存管理(OC对象放在堆内存里,非OC对象放在栈内存里,栈内存里的东西系统会自动管理)
2、自动释放池底层实现:
3、string的内存管理
* NSString *str1 = @"abcde";字符串“abcde”在常量区(有且只有一份),str1指针在栈区。
* NSString *str2 = [NSString stringWithFormat:@"hijkl"];
* 通过stringWithFormat创建的字符串放在堆中(即使字符串内容相同,还是会再次开辟新空间),str2在栈中。
* 通过stringWithFormat创建的字符串,会返回一个autorelease的实例,会在自动释放池自动释放。
* 通过initWithFormt创建的字符串,则需要自己进行手动管理内存
4、copy和Mutablecopy
* copy返回immutable对象,mutableCopy返回mutable对象(所以不可以对mutabe对象使用copy操作)
* NSmutableArray *mArray = [array mutableCopy];(此处的内容拷贝,仅仅是拷贝array这个对象,array集合内部的元素仍然是指针拷贝)
* [immutableObject copy] // 浅复制
* [immutableObject mutableCopy] //单层深复制
* [mutableObject copy] //单层深复制
* [mutableObject mutableCopy] //单层深复制
* 使用总结:
* 修饰可变类型的属性时,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong
* 修饰不可变类型的属性时,如NSArray、NSDictionary、NSString,用copy。
5、Block内存管理
* 默认情况下,Block的内存是在栈中(程序自动管理)
* 如果对block做了copy操作,block的内存会搬到堆中,对引用对象做一次+1操作
* block内部可以一直引用__block修饰的变量,static修饰的变量,全局变量(Block不可以修改外部变量的值,指的是栈中指针的内存地址,__block修饰的变量之所以可以被修改,是因为__block会将栈中的内存地址放到堆中,所以可以修改【static和全局变量在全局初始化区】)
* unsafe_unretained和weak的区别:两者都不持有对象,当引用计数为0时,weak会被置为nil,unsafe_unretained不置为nil(有野指针风险)
```
# 多线程# 动画
```
1、GCD
* 同步:阻塞当前线程
* 异步:不会阻塞当前线程
* 串行:FIFO 依次取出,一个一个执行(主队列)DISPATCH_QUEUE_SERIAL
* 并行:也是FIFO,但是放到不同线程去执行(全局队列)DISPATCH_QUEUE_CONCURRENT
* dispatch_barrier_async:当你传入的 queue 是通过 DISPATCH_QUEUE_CONCURRENT 参数自己创建的 queue 时,这个方法会阻塞这个 queue(注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个 queue 中排在它前面的任务都执行完成后才会开始执行自己
* dispatch_barrier_sync:自定义的并发队列(DISPATCH_QUEUE_CONCURRENT),它和上一个方法一样的阻塞 queue,不同的是 这个方法还会 阻塞当前线程
2、NSOperation
* NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation ( 默认在当前队列同步执行)
* NSOperationQueue可以设置maxConcurrentOperationCount,可以添加依赖
3、线程同步:防止多个线程抢夺同一块资源
* 互斥锁:@synchronized(self)
* NSLock
取消GCD任务 ?
```
# 常见设计模式
# Runloop
```
1、Runloop的寄生于线程:一个线程只能有唯一对应的runloop;但这个根runloop里可以嵌套子runloops;
2、同一时间一个runloop只能在一个mode,切换mode只能退出runloop,再重进指定mode(隔离modeItems使之互不干扰);
3、timerWithTimeInterval:需要手动加到runloop的mode中
scheduledTimerWithTimeInterval:默认已经添加到主线程的runLoop的DefaultMode中
4、mode类型
* kCFRunLoopDefaultMode: 默认 mode,通常主线程在这个 Mode 下运行。
* UITrackingRunLoopMode: 追踪mode,保证Scrollview滑动顺畅不受其他 mode 影响。
* UIInitializationRunLoopMode: 启动程序后的过渡mode,启动完成后就不再使用。
* GSEventReceiveRunLoopMode: Graphic相关事件的mode,通常用不到。
* kCFRunLoopCommonModes: 占位mode,作为标记DefaultMode和CommonMode用。
5、应用
* 当tableview的cell上有需要从网络获取的图片的时候,滚动tableView,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,但是会造成卡顿。可以让设置图片的任务在CFRunLoopDefaultMode下进行,当滚动tableView的时候,RunLoop是在 UITrackingRunLoopMode 下进行,不去设置图片,而是当停止的时候,再去设置图片。
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
* 常驻子线程,保持子线程一直处理事件
为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出。
self.downloadRunloop = CFRunLoopGetCurrent();
* 当调用 NSObject 的 performSelecter:afterDelay:后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
```
# 事件响应链条
```
UIApplication-->UIWindow-->递归找到最合适处理的控件-->控件调用touches方法-->判断是否实现touches方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者-->找不到方法作废
1、根据main函数的参数加载UIApplication->AppDelegate->UIWindow->UIViewController->superView->subViews
关系为:UIApplication.keyWindow.rootViewController.view.subView
事件传递机制:
1.当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
2.UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
3.UIWindow将事件向下分发,即UIView。
4.UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
5.遍历子控件,重复以上两步。
6.如果没有找到,那么自己就是事件处理者。如果
7.如果自己不能处理,那么不做任何处理。
其中 UIView不接受事件处理的情况主要有以下三种
1)alpha <0.01
2)userInteractionEnabled = NO
3.hidden = YES
2、hittest
UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。
```
# 性能优化 (内存占用,耗电,UI渲染)
1、例如一个黑色半透明的可以设置为一个灰色不透明的View替代.原因是这会使系统用一个最优的方式渲染这些views:如果一个图层是完全不透明的,则系统直接显示该图层的颜色即可。而如果图层是带透明效果的,则会引入更多的计算,因为需要把下面的图层也包括进来,进行混合后颜色的计算
2、永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成
3、如果要在UIImageView中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同,尽可能的先在子线程把图片缩放后再进行赋值。
4、尽量使用懒加载,不要一次性创建所有subview,等用到的时候再进行加载。(NSDateFormatter和NSCalendar初始化就比较慢)
5、处理内存警告
在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
在你的自定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning
注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
6、如果你用小图平铺来创建背景,你就需要用UIColor的colorWithPatternImage来做了,它会更快地渲染也不会花费很多内存:
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
7、如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用imageWithContentsOfFile足矣,这样不会浪费内存来缓存它。
然而,在图片反复重用的情况下imageNamed是一个好得多的选择。
# tableView CollectionView 性能调优
提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
滑动时按需加载[快速滑动只展示cell,不加载图片],这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。
除了上面最主要的三个方面外,还有很多几乎大伙都很熟知的优化点:
正确使用reuseIdentifier来重用Cells
尽量使所有的view opaque,包括Cell自身
尽量少用或不用透明图层
如果Cell内现实的内容来自web,使用异步加载,缓存请求结果
减少subviews的数量
在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果
尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示
NSLog向输出添加时间戳和标识符,而println则不会;
NSLog同步日志语句,以便如果您同时发出来自不同线程的日志,则它们将不会彼此重叠; println可能会导致混乱的输出,如果同时从单独的线程执行,而不做一些同步
iOS启动做了哪些事?
1.main 函数
2.UIApplicationMain
创建UIApplication对象
创建UIApplication的delegate对象
delegate对象开始处理(监听)系统事件(没有storyboard)
程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法
在application:didFinishLaunchingWithOptions:中创建UIWindow
创建和设置UIWindow的rootViewController
显示窗口
3.根据Info.plist获得最主要storyboard的文件名,加载最主要的storyboard(有storyboard)
创建UIWindow
创建和设置UIWindow的rootViewController
显示窗口
mas_equalTo有自动包装的功能,equalTo没有自动包装功能。
用mas_equalTo可以把基本数据类型转换为对象类型,这个过程叫装箱,比如自动将1包装成@1。
load和initialize
load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。
autoreleasePool
自动释放池可以延长对象的声明周期,如果一个事件周期很长,比如有一个很长的循环逻辑,那么一个临时变量可能很长时间都不会被释放,一直在内存中保留,那么内存的峰值就会一直增加,但是其实这个临时变量是我们不再需要的。这个时候就通过创建新的自动释放池来缩短临时变量的生命周期来降低内存的峰值。