study/tips collection


世界上没有什么事是一顿烧烤解决不了的,如果有,那就两顿
—尼古拉斯·赵四

从网络收集整理,资料来自 @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));.

对象的内存销毁时间表

分四个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 对象的内存销毁时间表
// 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)
* 继承关系中每一层的父类 都在调用 -dealloc
3. NSObject 调 -dealloc
* 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
4. 调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release
* 解除所有使用 runtime Associate方法关联的对象
* 解除所有 __weak 引用
* 调用 free()

objc中的类方法和实例方法有什么本质区别和联系?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
类方法:
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内部是如何实现的

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑 是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
int 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; }];
1
2
3
4
5
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];

上面这些情况不需要考虑“引用循环”。

BUT

如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:

1
2
3
4
5
6
7
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );

类似的:

1
2
3
4
5
6
7
8
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(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。

1
2
3
4
5
6
7
8
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)画了一张示意图,如下所示:

KVO 原理图

详解:

  • 1.第一次对一个对象调用 addObserver:forKeyPath:options:context: 时,框架会创建这个类的新的 KVO 子类,并将被观察对象转换为新子类的对象。

  • 2.键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:

  • 3.在这个 KVO 特殊子类中,Cocoa 创建观察属性的 setter 方法,在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后(调用 super 方法执行相关赋值操作), didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

1
2
3
4
5
- (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

具体过程可参考http://tech.glowing.com/cn/implement-kvo/

明天上班了,今天暂告一段落…


原创文章,转载请注明地址: https://kevinmky.github.io