世界上没有什么事是一顿烧烤解决不了的,如果有,那就两顿
—尼古拉斯·赵四
序
从网络收集整理,资料来自 @sunnyxx , @程序猿(chenyilong)等先驱,此处只作个人备份使用
PS:今年9月有幸和 @程序猿(chenyilong) 见了一面,当时人很多,估计 chenyilong 老师不记得我,不过我记得 ta 的诙谐幽默,😄,谁说 coder 都是闷骚的,明明就是明骚
lldb(gdb)常用的调试命令?
- po(打印)
- bt(显示最近一次调用的堆栈)
- next(在不单步执行进入其他函数的情况下,向前执行一行源代码)
- continue(继续执行正在调试的程序)
如何调试 BAD_ACCESS 错误
设置 xcode 环境变量环境变量 NSZombieEnabled 来查看具体位置等
IBOutlet 连出来的视图属性为什么可以被设置成 weak?
使用storyboard(xib不行)创建的vc,会有一个叫
_topLevelObjectsToKeepAliveFromStoryboard
的私有数组强引用所有top level的对象,所以这时即便outlet
声明成weak也没关系
IB中User Defined Runtime Attributes如何使用?
它能够通过KVC的方式配置一些你在interface builder 中不能配置的属性。当你希望在IB中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的viewcontroller
为什么对字符串等赋值的时候默认是 copy 的
用
@property
声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
weak 和 assign 有什么不同
- assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象
- assigin 引用的对象不会增加引用计数,也不会自动置为 nil,如果该对象销毁,assign 依然访问的话,会造成野指针错误
- weak 引用的对象如果释放会自动置为 nil, 访问时给 nil 发消息不会崩溃
@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
- @property = ivar + getter + setter;
- 属性定义后,编译器在编译期自动编写访问这些属性所需的方法(getter + setter),向类中添加适当类型的实例变量(ivar),并且在属性名前面加下划线
runtime 如何实现 weak 属性
- runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key
- 当此对象的引用计数为0的时候会
dealloc
,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
对应基本数据类型默认关键字是:
atomic,readwrite,assign
对于普通的OC对象:
atomic,readwrite,strong
objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
- 方法编译之后就是objc_msgSend()函数调用.大概是这样的:
((void()(id,SEL))(void)objc_msgSend)((id)obj,sel_registerName("foo"));
- 也就是说:
[obj foo];
在objc动态编译时,会被转意为:objc_msgSend(obj, @selector(foo));
.
对象的内存销毁时间表
分四个步骤:
123456789101112131415161718192021 // 对象的内存销毁时间表// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)// https://github.com/ChenYilong// 根据 WWDC 2011, Session 322 (36分22秒)中发布的内存销毁时间表1. 调用 -release :引用计数变为零* 对象正在被销毁,生命周期即将结束.* 不能再有新的 __weak 弱引用, 否则将指向 nil.* 调用 [self dealloc]2. 父类 调用 -dealloc* 继承关系中最底层的父类 在调用 -dealloc* 如果是 MRC 代码 则会手动释放实例变量们(iVars)* 继承关系中每一层的父类 都在调用 -dealloc3. NSObject 调 -dealloc* 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法4. 调用 object_dispose()* 为 C++ 的实例变量们(iVars)调用 destructors* 为 ARC 状态下的 实例变量们(iVars) 调用 -release* 解除所有使用 runtime Associate方法关联的对象* 解除所有 __weak 引用* 调用 free()
objc中的类方法和实例方法有什么本质区别和联系?
1234567891011121314151617 类方法:1. 类方法是属于类对象的2. 类方法只能通过类对象调用3. 类方法中的self是类对象4. 类方法可以调用其他的类方法5. 类方法中不能访问成员变量6. 类方法中不定直接调用对象方法实例方法:1. 实例方法是属于实例对象的2. 实例方法只能通过实例对象调用3. 实例方法中的self是实例对象4. 实例方法中可以访问成员变量5. 实例方法中直接调用实例方法6. 实例方法中也可以调用类方法(通过类名)
runloop和线程有什么关系?
runloop 详解:http://blog.csdn.net/wzzvictory/article/details/9237973
- 主线程的 run loop 默认是启动的
- 对其它线程来说,run loop 默认是没有启动的
可以通过以下代码来获取到当前线程的 run loop
1 NSRunLoop *runloop = [NSRunLoop currentRunLoop];NSRunLoop 类并不是线程安全的
- Run loop 同时也负责 autorelease pool 的创建和释放
runloop的mode作用是什么?
model 主要是用来指定事件在运行循环中的优先级的,分为:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
- UITrackingRunLoopMode:ScrollView滑动时
- UIInitializationRunLoopMode:启动时
- NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
每个 runloop 只能运行在一种 model 下,所以遇到ScrollView滑动时 timer 不响应的问题,将 timer 加入到 NSRunLoopCommonModes 即可
以+ scheduledTimerWithTimeInterval…的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
- ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动
- NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度
- 可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决
runloop内部是如何实现的
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑 是这样的:
123456789101112131415 //// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)// https://github.com/ChenYilongint main(int argc, char * argv[]) {//程序一直运行状态while (AppIsRunning) {//睡眠状态,等待唤醒事件id whoWakesMe = SleepForWakingUp();//得到唤醒事件id event = GetEvent(whoWakesMe);//开始处理事件HandleEvent(event);}return 0;}
不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?
分两种情况:手动干预释放时机、系统自动去释放。
- 手动干预释放时机–指定autoreleasepool 就是所谓的:当前作用域大括号结束时释放。
- 系统自动去释放–不手动指定autoreleasepool
Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。释放的时机总结起来,可以用下图来表示
runloop下面对这张图进行详细的解释:
从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件。
我们都是知道: 所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。
但是如果每次都放进应用程序的 main.m 中的 autoreleasepool 中,迟早有被撑满的一刻。这个过程中必定有一个释放的动作。何时?
在一次完整的运行循环结束之前,会被销毁。
那什么时间会创建自动释放池?运行循环检测到事件并启动后,就会创建自动释放池。
子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。
自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。
@autoreleasepool
当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。如果在一个vc的
viewDidLoad
中创建一个Autorelease
对象,那么该对象会在viewDidAppear
方法执行前就被销毁了。
使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:
1 [UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
1 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
12345 [[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"object:nilqueue:[NSOperationQueue mainQueue]usingBlock:^(NSNotification * notification) {self.someProperty = xyz; }];上面这些情况不需要考虑“引用循环”。
BUT
如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:
1234567 __weak __typeof__(self) weakSelf = self;dispatch_group_async(_operationsGroup, _operationsQueue, ^{__typeof__(self) strongSelf = weakSelf;[strongSelf doSomething];[strongSelf doSomethingElse];} );类似的:
12345678 __weak __typeof__(self) weakSelf = self;_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"object:nilqueue:nilusingBlock:^(NSNotification *note) {__typeof__(self) strongSelf = weakSelf;[strongSelf dismissModalViewControllerAnimated:YES];}];self –> _observer –> block –> self 显然这也是一个循环引用。
如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
12345678 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, queue, ^{ /*加载图片1 */ });dispatch_group_async(group, queue, ^{ /*加载图片2 */ });dispatch_group_async(group, queue, ^{ /*加载图片3 */ });dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 合并图片});
若一个类有实例变量 NSString *_foo ,调用setValue:forKey:时,可以以foo还是 _foo 作为key?
都可以。
KVC和KVO的keyPath一定是属性么?
KVO支持实例变量
apple用什么方式实现对一个对象的KVO?
KVO 原理:
当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我(chenyilong)画了一张示意图,如下所示:
详解:
1.第一次对一个对象调用 addObserver:forKeyPath:options:context: 时,框架会创建这个类的新的 KVO 子类,并将被观察对象转换为新子类的对象。
2.键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:
3.在这个 KVO 特殊子类中,Cocoa 创建观察属性的 setter 方法,在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后(调用 super 方法执行相关赋值操作), didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
12345 - (void)setNow:(NSDate *)aDate {[self willChangeValueForKey:@"now"];[super setValue:aDate forKey:@"now"];[self didChangeValueForKey:@"now"];}这种继承和方法注入是在运行时而不是编译时实现的。这就是正确命名如此重要的原因。只有在使用KVC命名约定时,KVO才能做到这一点。
Apple 还重写、覆盖了 -class 方法并返回原来的类,具体过程是:KVO 在实现中通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。
具体探究过程可参考 https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html
如何自己动手实现 KVO
明天上班了,今天暂告一段落…
原创文章,转载请注明地址: https://kevinmky.github.io