复习一下 iOS 基础 (2)

KVO/KVC

KVO 的实现依赖于 Objective-C 强大的 Runtime
当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

// 子类实现
-(void)setName:(NSString *)newName{ 
[self willChangeValueForKey:@"name"];    //KVO在调用存取方法之前总调用 
[super setValue:newName forKey:@"name"]; //调用父类的存取方法 
[self didChangeValueForKey:@"name"];     //KVO在调用存取方法之后总调用
}

观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行KVO的回调方法,例如是否执行了setter方法、或者是否使用了KVC赋值。

①通过KVO,能观察父类的属性值。
②只要知道了keyPath,不管有没有暴露方法,依旧可以通过KVO方式观察值的变化,而且同属性一样,可以被继承。
③子类重写父类的set方法,也并不会影响KVO的观察。

KVC(键值编码),即Key-Value Coding,一个非正式的Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用Setter、Getter方法等显式的存取方式去访问。
KVO(键值监听),即Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了setter方法、或者使用了KVC赋值。

Block

Block 中,Block 表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。
修饰为 __block 的变量,在捕获时,获取的不再是瞬间值。
Block 实现方法 在编译之后变成了一个静态函数, Block 本身变成了一个结构体, 包含 函数实现, 以及数据
如果 Block 引用了外面的数据, 那么就会变成 结构体的参数保存下来
__block 修饰符其实类似于 C 语言中 static、auto、register 修饰符。用于指定将变量值设置到哪个存储域中。
被 __block 标记的 参数, 被封装成了一个结构体, 记录了参数的一些属性, 通过这些属性, 就能直接修改内容了
Block 有三种类型,分别是:

__NSConcreteStackBlock ————————栈中
__NSConcreteGlobalBlock ————————数据区域中
__NSConcreteMallocBlock ————————堆中

设置在栈上的 Block,如果所属的变量作用域结束,Block 就会被废弃。如果其中用到了 block,block 所属的变量作用域结束也会被废弃。
为了解决这个问题,Block 在必要的时候就需要从栈中移到堆中。ARC 有效时,很多情况下,编译器会帮助完成 Block 的 copy,但很多情况下,我们需要手动 copy Block。

复习一下 iOS 基础 (2)_第1张图片
Paste_Image.png

Runloop

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
Runloop 是不能被创建的 只能被获取 CFRunLoopGetMain()CFRunLoopGetCurrent()
线程和 runloop 是一一对应的, 他们的关系保存在全局字典中, 当取不到 runloop 的时候会自动创建

Runloop 种类

CFRunLoopRef
CFRunLoopModeRef // 每个 runloop 包含若干 mode, 每个 mode 又包含若干 Source/Timer/Observer
CFRunLoopSourceRef // Source有两个版本:Source0 和 Source1, source0 不基于 port, source1 基于 port 能主动唤醒 runloop
CFRunLoopTimerRef // 基于时间的触发器, 时间到了唤醒 runloop 
CFRunLoopObserverRef // runloop 的观察者 当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
};

苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode

1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
5: kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

Runloop 流程

复习一下 iOS 基础 (2)_第2张图片
Paste_Image.png

Runloop 应用

自动释放池

在 kCFRunLoopEntry 使用 _objc_autoreleasePoolPush 创建, 在kCFRunLoopBeforeWaiting 的时候 _objc_autoreleasePoolPop 释放, 并且创建新的自动释放池

事件响应

苹果注册一个 source1(基于 mach port 的) 用来接收系统事件, 当发生事件的时候, 唤醒 runloop, 苹果把事件封装成为 UIEvent 下发

手势识别

苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。

界面更新

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面

iOS 为什么必须在主线程中操作UI

因为UIKit不是线程安全的。试想下面这几种情况:
两个线程同时设置同一个背景图片,那么很有可能因为当前图片被释放了两次而导致应用崩溃。
两个线程同时设置同一个UIView的背景颜色,那么很有可能渲染显示的是颜色A,而此时在UIView逻辑树上的背景颜色属性为B。
两个线程同时操作view的树形结构:在线程A中for循环遍历并操作当前View的所有subView,然后此时线程B中将某个subView直接删除,这就导致了错乱还可能导致应用崩溃。
iOS4之后苹果将大部分绘图的方法和诸如 UIColor 和 UIFont 这样的类改写为了线程安全可用,但是仍然强烈建议讲UI操作保证在主线程中执行。

长时间保持一个后台线程

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // runloop 必须存在一个 timer/source/observer 才能 run, 这里添加了一个空的
        [runLoop run];
    }
}
+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
...
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; // 需要异步执行的时候可以调用这个方法

优化 tableview 设置 UI 卡顿

不在用户拖动的时候设置 UI, 在特定的 mode 中执行代码

UIImage *downLoadImage = ...;  
[self.avatarImageView performSelector:@selector(setImage:)  
                        withObject:downloadImage  
                        afterDelay:0  
                        inModes:@[NSDefaultRunLoopMode]];

监听 runloop 的事件

- (void)setupRunloopObserver
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        CFRunLoopRef runloop = CFRunLoopGetCurrent();
        
        CFRunLoopObserverRef enterObserver;
        enterObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                               kCFRunLoopEntry | kCFRunLoopExit,
                                               true,
                                               -0x7FFFFFFF,
                                               BBRunloopObserverCallBack, NULL);
        CFRunLoopAddObserver(runloop, enterObserver, kCFRunLoopCommonModes);
        CFRelease(enterObserver);
    });
}

static void BBRunloopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
        case kCFRunLoopEntry: {
            NSLog(@"enter runloop...");
        }
            break;
        case kCFRunLoopExit: {
            NSLog(@"leave runloop...");
        }
            break;
        default: break;
    }
}

RunLoop 涉及了自动释放池、延迟回调、触摸事件、屏幕刷新等功能的

你可能感兴趣的:(复习一下 iOS 基础 (2))