iOS Runloop td
顾名思义:运行循环:在程序运行过程中循环做一些事情
应用范畴:
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
命令行项目默认是没有runloop的。
如果没有runloop,执行完第13行代码后,会即将退出程序
如果有runloop:程序并不会马上退出,而是保持运行状态
下面是伪代码
RunLoop的基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
......
RunLoop对象
iOS中有2套API来访问和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/
RunLoop与线程
每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef (平时的事情,点击事件 、刷新ui、滚动等)
- CFRunLoopTimerRef (定时器)
- CFRunLoopObserverRef (监听器)
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; // 线程对象
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; // 当前是什么模式
CFMutableSetRef _modes; // set是一个无序集合:一堆CFRunLoopModeRef对象
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // 模式的名字UITrackingRunLoopMode或者kCFRunLoopDefaultMode或者是系统不常用的模式
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0; // 装CFRunLoopSourceRef对象
CFMutableSetRef _sources1; // 装CFRunLoopSourceRef对象
CFMutableArrayRef _observers; // 装CFRunLoopObserverRef对象
CFMutableArrayRef _timers; // 装CFRunLoopTimerRef对象
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
上述关系相当于是
runloop中有很多模式,但是在运行中,只会选择一种模式来运行,比如上图中可能是左边 也可能是右边,运行在那种模式下取决于currentModel.
CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop的运行模式
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
RunLoop启动时只能选择其中一个Mode,作为currentMode。如果需要切换Mode,只能退出当前Loop(切换模式,不会导致程序退出,而是在循环内部做的切换,模式起隔离的作用,比方说滚动下,只执行滚动模式下的逻辑,那默认模式下的逻辑就不会处理,这样滚动起来就比较流畅),再重新选择一个Mode进入。不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种Mode(模式)其他的都是不常见的 系统的
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行(应用普通状态下的)
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响(当一旦滚动屏幕,就会自动切换到这个模式)
我们可以查看一下代码
NSLog(@"%@", [NSRunLoop mainRunLoop]);
<CFRunLoop 0x6040001f8700 [0x1069d4c80]>{wakeup port = 0x2703, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x604000042af0 [0x1069d4c80]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x107d44e88 [0x1069d4c80]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x1069aa818 [0x1069d4c80]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x608000244950 [0x1069d4c80]>{type = mutable set, count = 14,
entries =>
0 : <CFRunLoopSource 0x60800017bd80 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10b87f75a)}}
1 : <CFRunLoopSource 0x60800017cbc0 [0x1069d4c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 14099, subsystem = 0x107cfbfe8, context = 0x0}}
6 : <CFRunLoopSource 0x60000017bfc0 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x10b881bf7)}}
7 : <CFRunLoopObserver 0x60c000133ec0 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10c6b04ce), context = <CFRunLoopObserver context 0x0>}
11 : <CFRunLoopSource 0x60c00017c080 [0x1069d4c80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x60c0000a6e40, callout = FBSSerialQueueRunLoopSourceHandler (0x10afea82f)}}
12 : <CFRunLoopSource 0x60800017ce00 [0x1069d4c80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x6080002454f0, callout = __handleHIDEventFetcherDrain (0x1074dfbbe)}}
13 : <CFRunLoopObserver 0x608000134500 [0x1069d4c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1071686b3), context = <CFRunLoopObserver context 0x6040000d1100>}
14 : <CFRunLoopObserver 0x608000134320 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x106bb1da1), context = <CFRunLoopObserver context 0x7fe618e00b90>}
15 : <CFRunLoopObserver 0x608000134280 [0x1069d4c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x106b82d92), context = <CFArray 0x608000245f10 [0x1069d4c80]>{type = mutable-small, count = 1, values = (
0 : <0x7fe61a002048>
)}}
16 : <CFRunLoopObserver 0x6080001341e0 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x106bb1e1c), context = <CFRunLoopObserver context 0x7fe618e00b90>}
17 : <CFRunLoopSource 0x60800017d1c0 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60400015c460, callout = __handleEventQueue (0x1074dfbb2)}}
18 : <CFRunLoopObserver 0x600000135360 [0x1069d4c80]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x1066ae960), context = <CFRunLoopObserver context 0x60000004e970>}
19 : <CFRunLoopObserver 0x608000134000 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x106b82d92), context = <CFArray 0x608000245f10 [0x1069d4c80]>{type = mutable-small, count = 1, values = (
0 : <0x7fe61a002048>
)}}
20 : <CFRunLoopSource 0x60800017c680 [0x1069d4c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23043, subsystem = 0x107d16668, context = 0x600000025b60}}
}
,
modes = <CFBasicHash 0x604000042b80 [0x1069d4c80]>{type = mutable set, count = 4,
entries =>
2 : <CFRunLoopMode 0x608000196a60 [0x1069d4c80]>{name = UITrackingRunLoopMode, port set = 0x1f03, queue = 0x60800015c3b0, source = 0x608000196b30 (not fired), timer port = 0x1d03,
sources0 = <CFBasicHash 0x6080002448f0 [0x1069d4c80]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x60800017bd80 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10b87f75a)}}
1 : <CFRunLoopSource 0x60c00017c080 [0x1069d4c80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x60c0000a6e40, callout = FBSSerialQueueRunLoopSourceHandler (0x10afea82f)}}
2 : <CFRunLoopSource 0x60800017ce00 [0x1069d4c80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x6080002454f0, callout = __handleHIDEventFetcherDrain (0x1074dfbbe)}}
5 : <CFRunLoopSource 0x60800017d1c0 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60400015c460, callout = __handleEventQueue (0x1074dfbb2)}}
}
,
sources1 = <CFBasicHash 0x6080002444d0 [0x1069d4c80]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopSource 0x60800017cbc0 [0x1069d4c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 14099, subsystem = 0x107cfbfe8, context = 0x0}}
1 : <CFRunLoopSource 0x60800017c680 [0x1069d4c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23043, subsystem = 0x107d16668, context = 0x600000025b60}}
2 : <CFRunLoopSource 0x60000017bfc0 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x10b881bf7)}}
}
,
observers = (
"<CFRunLoopObserver 0x608000134280 [0x1069d4c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x106b82d92), context = <CFArray 0x608000245f10 [0x1069d4c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fe61a002048>\n)}}",
"<CFRunLoopObserver 0x608000134500 [0x1069d4c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1071686b3), context = <CFRunLoopObserver context 0x6040000d1100>}",
"<CFRunLoopObserver 0x600000135360 [0x1069d4c80]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x1066ae960), context = <CFRunLoopObserver context 0x60000004e970>}",
"<CFRunLoopObserver 0x608000134320 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x106bb1da1), context = <CFRunLoopObserver context 0x7fe618e00b90>}",
"<CFRunLoopObserver 0x60c000133ec0 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10c6b04ce), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6080001341e0 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x106bb1e1c), context = <CFRunLoopObserver context 0x7fe618e00b90>}",
"<CFRunLoopObserver 0x608000134000 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x106b82d92), context = <CFArray 0x608000245f10 [0x1069d4c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fe61a002048>\n)}}"
),
timers = (null),
currently 559646742 (30436416494659) / soft deadline in: 1.84467136e+10 sec (@ -1) / hard deadline in: 1.84467136e+10 sec (@ -1)
},
3 : <CFRunLoopMode 0x608000196c00 [0x1069d4c80]>{name = GSEventReceiveRunLoopMode, port set = 0x2c03, queue = 0x60800015c460, source = 0x608000196cd0 (not fired), timer port = 0x2e03,
sources0 = <CFBasicHash 0x6080002448c0 [0x1069d4c80]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x60800017bd80 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10b87f75a)}}
}
,
sources1 = <CFBasicHash 0x608000244890 [0x1069d4c80]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopSource 0x60000017c080 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x10b881bf7)}}
}
,
observers = (null),
timers = (null),
currently 559646742 (30436417513777) / soft deadline in: 1.84467136e+10 sec (@ -1) / hard deadline in: 1.84467136e+10 sec (@ -1)
},
4 : <CFRunLoopMode 0x6040001964b0 [0x1069d4c80]>{name = kCFRunLoopDefaultMode, port set = 0x1a03, queue = 0x60400015c250, source = 0x604000196580 (not fired), timer port = 0x2503,
sources0 = <CFBasicHash 0x6080002444a0 [0x1069d4c80]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x60800017bd80 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10b87f75a)}}
1 : <CFRunLoopSource 0x60c00017c080 [0x1069d4c80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x60c0000a6e40, callout = FBSSerialQueueRunLoopSourceHandler (0x10afea82f)}}
2 : <CFRunLoopSource 0x60800017ce00 [0x1069d4c80]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x6080002454f0, callout = __handleHIDEventFetcherDrain (0x1074dfbbe)}}
5 : <CFRunLoopSource 0x60800017d1c0 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60400015c460, callout = __handleEventQueue (0x1074dfbb2)}}
}
,
sources1 = <CFBasicHash 0x608000244470 [0x1069d4c80]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopSource 0x60800017cbc0 [0x1069d4c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 14099, subsystem = 0x107cfbfe8, context = 0x0}}
1 : <CFRunLoopSource 0x60800017c680 [0x1069d4c80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23043, subsystem = 0x107d16668, context = 0x600000025b60}}
2 : <CFRunLoopSource 0x60000017bfc0 [0x1069d4c80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x10b881bf7)}}
}
,
observers = (
"<CFRunLoopObserver 0x608000134280 [0x1069d4c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x106b82d92), context = <CFArray 0x608000245f10 [0x1069d4c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fe61a002048>\n)}}",
"<CFRunLoopObserver 0x608000134500 [0x1069d4c80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1071686b3), context = <CFRunLoopObserver context 0x6040000d1100>}",
"<CFRunLoopObserver 0x600000135360 [0x1069d4c80]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x1066ae960), context = <CFRunLoopObserver context 0x60000004e970>}",
"<CFRunLoopObserver 0x608000134320 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x106bb1da1), context = <CFRunLoopObserver context 0x7fe618e00b90>}",
"<CFRunLoopObserver 0x60c000133ec0 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10c6b04ce), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6080001341e0 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x106bb1e1c), context = <CFRunLoopObserver context 0x7fe618e00b90>}",
"<CFRunLoopObserver 0x608000134000 [0x1069d4c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x106b82d92), context = <CFArray 0x608000245f10 [0x1069d4c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fe61a002048>\n)}}"
),
timers = <CFArray 0x6040000a5760 [0x1069d4c80]>{type = mutable-small, count = 2, values = (
0 : <CFRunLoopTimer 0x60000017f980 [0x1069d4c80]>{valid = Yes, firing = No, interval = 0.5, tolerance = 0, next fire date = 559646742 (0.49159193 @ 30436910466673), callout = (NSTimer) [UITextSelectionView caretBlinkTimerFired:] (0x10580b48a / 0x10554337c) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x60000002c540>}
1 : <CFRunLoopTimer 0x60400017bf00 [0x1069d4c80]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 559646743 (1.25698304 @ 30437676026131), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x1057f6849 / 0x10707331b) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x604000066840>}
)},
currently 559646742 (30436417545494) / soft deadline in: 0.492921161 sec (@ 30436910466673) / hard deadline in: 0.492921134 sec (@ 30436910466673)
},
5 : <CFRunLoopMode 0x600000196650 [0x1069d4c80]>{name = kCFRunLoopCommonModes, port set = 0x480b, queue = 0x60000015c460, source = 0x6000001963e0 (not fired), timer port = 0x410b,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 559646742 (30436419063687) / soft deadline in: 1.84467136e+10 sec (@ -1) / hard deadline in: 1.84467136e+10 sec (@ -1)
},
}
}
可以看到上面的模式有四种:UITrackingRunLoopMode、GSEventReceiveRunLoopMode、kCFRunLoopDefaultMode、kCFRunLoopCommonModes。而前面设置的current mode = kCFRunLoopDefaultMode,所以,会执行kCFRunLoopDefaultMode。
看下面的小问题
RunLoop的运行逻辑
bt:打印函数调用栈
每个模式做的事情:
- Source0
触摸事件处理
performSelector:onThread:
- Source1
基于Port的线程间通信
系统事件捕捉
- Timers
NSTimer
performSelector:withObject:afterDelay:
- Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
(在线程睡觉之前 刷新UI(这些不是立刻刷新的), 设置颜色这些)
runloop在循环处理的事情,就是某种模式下的source0、source1、timers、observers,外循环中在不断的处理这些事情,一旦发现就处理。
runloop的状态
监听运行状态
// kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
- (void)viewDidLoad {
// 1
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
NSMutableDictionary *runloops;
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
2018-09-26 14:21:02.217837+0800 Interview03-RunLoop[4744:336769] kCFRunLoopAfterWaiting
2018-09-26 14:21:02.218071+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeTimers
2018-09-26 14:21:02.218648+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeSources
2018-09-26 14:21:02.220133+0800 Interview03-RunLoop[4744:336769] --===-[ViewController touchesBegan:withEvent:]
2018-09-26 14:21:02.221260+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeTimers
2018-09-26 14:21:02.221541+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeSources
2018-09-26 14:21:02.221793+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeTimers
2018-09-26 14:21:02.222085+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeSources
2018-09-26 14:21:02.222190+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeTimers
2018-09-26 14:21:02.222517+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeSources
2018-09-26 14:21:02.223391+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeWaiting
2018-09-26 14:21:02.318849+0800 Interview03-RunLoop[4744:336769] kCFRunLoopAfterWaiting
2018-09-26 14:21:02.318979+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeTimers
2018-09-26 14:21:02.319341+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeSources
2018-09-26 14:21:02.319715+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeTimers
2018-09-26 14:21:02.319782+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeSources
2018-09-26 14:21:02.319871+0800 Interview03-RunLoop[4744:336769] kCFRunLoopBeforeWaiting
证明模式切换 第二种方式
- (void)viewDidLoad
{
// 第二种方式
// // 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"定时器-----------");
}];
}
2018-09-26 14:23:00.886113+0800 Interview03-RunLoop[4789:343545] 定时器-----------
2018-09-26 14:23:06.659947+0800 Interview03-RunLoop[4789:343545] kCFRunLoopExit - kCFRunLoopDefaultMode
2018-09-26 14:23:06.660184+0800 Interview03-RunLoop[4789:343545] kCFRunLoopEntry - UITrackingRunLoopMode
2018-09-26 14:23:07.075695+0800 Interview03-RunLoop[4789:343545] kCFRunLoopExit - UITrackingRunLoopMode
2018-09-26 14:23:07.075877+0800 Interview03-RunLoop[4789:343545] kCFRunLoopEntry - kCFRunLoopDefaultMode
一开始 oberver内部是有的
RunLoop的运行逻辑
请看代码执行流程 及源码流程
打断点看这里 下面的函数栈执行流程就是下面源码的流程过程。请对比
2018-09-26 15:21:45.901445+0800 Interview01-runloop流程[5242:400150] 11111111111
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001003ed69d Interview01-runloop流程`-[ViewController touchesBegan:withEvent:](self=0x00007fdc3ff02e90, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000608000104a40) at ViewController.m:39
frame #1: 0x0000000101d03767 UIKit`forwardTouchMethod + 340
frame #2: 0x0000000101d03602 UIKit`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x0000000101b4be1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052
frame #4: 0x0000000101b4d7c1 UIKit`-[UIWindow sendEvent:] + 4086
frame #5: 0x0000000101af1310 UIKit`-[UIApplication sendEvent:] + 352
frame #6: 0x00000001024326af UIKit`__dispatchPreprocessedEventFromEventQueue + 2796
frame #7: 0x00000001024352c4 UIKit`__handleEventQueueInternal + 5949
frame #8: 0x00000001015fbbb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x00000001015e04af CoreFoundation`__CFRunLoopDoSources0 + 271
frame #10: 0x00000001015dfa6f CoreFoundation`__CFRunLoopRun + 1263
frame #11: 0x00000001015df30b CoreFoundation`CFRunLoopRunSpecific + 635
frame #12: 0x00000001067cda73 GraphicsServices`GSEventRunModal + 62
frame #13: 0x0000000101ad6057 UIKit`UIApplicationMain + 159
frame #14: 0x00000001003ed74f Interview01-runloop流程`main(argc=1, argv=0x00007ffeef811fb0) at main.m:14
frame #15: 0x00000001050b6955 libdyld.dylib`start + 1
(lldb)
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
// 来保存do while循环的结果
int32_t retVal = 0;
// 从这里开始
do {
// 通知obervers:即将处理timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知obervers:即将处理sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 处理blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 判断有无source1:端口相关的东西
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 如果有source1 就跳转到handle_msg
goto handle_msg;
}
didDispatchPortLastTime = false;
// 通知obervers即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 休眠
__CFRunLoopSetSleeping(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
do { // 循环在做一件事情
// 做这件事情 :在等待别的消息 来唤醒当前线程。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof((mach_msg_header_t *)msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 不睡觉了
__CFRunLoopUnsetSleeping(rl);
// 通知obervers:结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 去判断怎么醒来的 下面是很多种情况
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING();
} else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP();
} // 前面两种是什么事都不干 下面这两种就是:被timer唤醒 就会执行__CFRunLoopDoTimers
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
else if (livePort == dispatchPort) { // 被GCD唤醒
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
// 处理GCD相关的事情
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else { // 剩下的是被source1唤醒
// 处理source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 处理blocks
__CFRunLoopDoBlocks(rl, rlm);
// 设置返回值
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);// 条件成立,又会执行里面的东西
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
// runloop入口
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// kCFRunLoopEntry通知oberver进入loop 已经传入一种模式:modeName
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 最主要的逻辑,具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知obervers 退出loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
// 返回结果
return result;
}
- 01、通知Observers:进入Loop
- 02、通知Observers:即将处理Timers
- 03、通知Observers:即将处理Sources
- 04、处理Blocks
- 05、处理Source0(可能会再次处理Blocks)
- 06、如果存在Source1,就跳转到第8步
- 07、通知Observers:开始休眠(等待消息唤醒)
- 08、通知Observers:结束休眠(被某个消息唤醒)
- 01> 处理Timer
- 02> 处理GCD Async To Main Queue
- 03> 处理Source1
- 09、处理Blocks
- 10、根据前面的执行结果,决定如何操作
- 01> 回到第02步
- 02> 退出Loop
- 11、通知Observers:退出Loop
GCD很多东西是不依赖于runloop的,只是GCD有一种情况会交给runloop去处理:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理一些子线程的逻辑
// 回到主线程去刷新UI界面 :依赖runloop
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"11111111111");
});
});
休眠的细节
开始睡觉 就是休眠 意味着:线程阻塞,不会往下走。cpu就不会给给资源。
实现方式 while(1)也可以实现阻塞,但是当前线程没有休息,一直在执行代码,这种不叫休眠。而runloop的那种是什么事情都不会做,真正的休息了。那如何做到呢
__CFRunLoopServiceMachPort
要达到睡觉,只有内核(操作系统)层面的才能达到。
// 内核层面API(一般不开放) 应用层面API(我们程序员用的)
休眠的 实现原理
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
...
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
// mach_msg 比较底层的函数
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
// Take care of all voucher-related work right after mach_msg.
// If we don't release the previous voucher we're going to leak it.
voucher_mach_msg_revert(*voucherState);
// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
*voucherState = voucher_mach_msg_adopt(msg);
...
}
HALT;
return false;
}
RunLoop在实际开中的应用
a、控制线程生命周期(线程保活,耗时操作,子线程的事情做完了就会自动销毁,不希望早挂掉,就可以用runloop控制声明周期)
b、解决NSTimer在滑动时停止工作的问题(在滚动时,nstimer会失效)
c、监控应用卡顿
d、性能优化
b、首先讲定时器失效的问题
因为定时器是在默认模式下工作,不是在UITrackingRunLoopMode模式下工作
可以自己写一个定时器 然后滚动页面 可以看到定时器停止了。
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
带有scheduledTimer的方法,就会默认直接将nstimer对象添加到默认模式,如果自己想添加的话可以用下面的这种
- (void)viewDidLoad {
[super viewDidLoad];
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
如果填写的是NSRunLoopCommonModes,就是告诉runloop会运行到下面结构体的_commonModes这个数组中的模式,而这个数组恰好装着NSDefaultRunLoopMode和UITrackingRunLoopMode两个,也就是标记为通用的模式,所以就是可以运行到两种模式下。平时runloop会切换到你传入两种模式的任何一个模式,但是你传入common,也就是会到commonModes数组下的模式,数组中有两个模式,所以两个模式下都可以运行。而common并不是一种模式。
timer被标记为NSRunLoopCommonModes,那么能在commonModes模式工作,那这个timer就会被放到commonModeItems里面去(因为平常的模式都有对应的modes)。
从流程上说,timer可以唤醒runloop,这样就开始处理timer了。
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; // 线程对象
uint32_t _winthread;
CFMutableSetRef _commonModes; // 这是有common标记的
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; // 当前是什么模式
CFMutableSetRef _modes; // set是一个无序集合:一堆CFRunLoopModeRef对象
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
a、控制线程生命周期(线程保活,AFNetWorking)
控制子线程声明周期,让子线程一直在内存中,这样的好处是 经常要在子线程中处理事情。
子线程的特点:做完自己的事情,直接挂掉,我们现在不希望它立刻挂掉。
在当前线程获取runloop就自动创建好runloop了。[NSRunLoop currentRunLoop]
还需要注意:如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
port就是source1。port对象可以随便写。所以可以下面方式做, 在run中就不会死掉,真正想让做的事情是在某个时刻做test方法。
#import <Foundation/Foundation.h>
@interface MJThread : NSThread
@end
#import "MJThread.h"
@implementation MJThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
// 这个方法的目的:线程保活
- (void)run {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s ----end----", __func__); // 所以一直不会执行这一行
}
@end
2018-09-27 10:34:56.829966+0800 Interview03-线程保活[1888:120690] -[ViewController run] <MJThread: 0x6040002793c0>{number = 3, name = (null)}
2018-09-27 10:35:16.747305+0800 Interview03-线程保活[1888:120690] -[ViewController test] <MJThread: 0x6040002793c0>{number = 3, name = (null)}
2018-09-27 10:35:17.561778+0800 Interview03-线程保活[1888:120690] -[ViewController test] <MJThread: 0x6040002793c0>{number = 3, name = (null)}
2018-09-27 10:35:19.930440+0800 Interview03-线程保活[1888:120690] -[ViewController test] <MJThread: 0x6040002793c0>{number = 3, name = (null)}
上面有一个问题就是:target强引用这控制器,控制器不会销毁,线程也不会销毁,因为rubloop的任务一直没结束。
实现
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread; // 假设希望这个线程跟随控制器
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
// beforeData:超时 distantFuture:遥远的未来
// 一旦执行完任务,runloop就退出了。
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@----end----", [NSThread currentThread]);
// NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
// [[NSRunLoop currentRunLoop] run];
/*
it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
In other words, this method effectively begins an infinite(无限的) loop that processes data from the run loop’s input sources and timers
*/
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"如果上面是NO,则不会等上面的子线程执行完,就先执行这个打印");
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
// 在子线程调用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
// [self stop];
}
@end
2018-09-27 11:19:28.051542+0800 Interview03-线程保活[2280:165391] <MJThread: 0x600000074780>{number = 5, name = (null)}----begin----
2018-09-27 11:19:30.032698+0800 Interview03-线程保活[2280:165391] -[ViewController test] <MJThread: 0x600000074780>{number = 5, name = (null)}
2018-09-27 11:19:30.584124+0800 Interview03-线程保活[2280:165391] -[ViewController test] <MJThread: 0x600000074780>{number = 5, name = (null)}
2018-09-27 11:19:31.045202+0800 Interview03-线程保活[2280:165391] -[ViewController test] <MJThread: 0x600000074780>{number = 5, name = (null)}
2018-09-27 11:19:31.922878+0800 Interview03-线程保活[2280:165391] -[ViewController test] <MJThread: 0x600000074780>{number = 5, name = (null)}
2018-09-27 11:19:32.901617+0800 Interview03-线程保活[2280:165391] -[ViewController stopThread] <MJThread: 0x600000074780>{number = 5, name = (null)}
2018-09-27 11:19:32.901958+0800 Interview03-线程保活[2280:165391] <MJThread: 0x600000074780>{number = 5, name = (null)}----end----
如果想让控制器销毁 runloop也销毁,则在dealloc里打开stop方法: 但是打开后就崩溃了,原因:waitUntilDone:NO 这个参数
这个代表不等了,不等这个子线程执行方法,而主线程依然往下走,是两条路。而当主线程直接走下去,而这个stop函数就结束了,所以dealloc就结束了,控制器就挂掉了,而与此同时,这个子线程在执行stropThread方法,这个方法是控制器的方法,还要执行控制器的属性啥的,引用是runloop在处理这个子线程的访问。
performSelector:onThread:底层就是通过子线程的runloop去执行的。[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];这个perform是执行stopThread方法到self.thread这个线程,那就是给这个线程发一个消息,就相当于是基于port的概念,要基于runloop去处理的,所以也就是说这个runloop在处理这个perform的时候出问题了,因为runloop内部在处理performselector的时候需要拿到控制器对象来调用stopThread做事情,但是控制器已经销毁了,所以就发生了坏内存访问。所以这个坏内存指的是控制器坏掉了。所以就需要设置为YES,代表让子线程代码执行完毕后才会往下走。但是仅仅设置为YES,又会发生另外一个问题,也就是weakSelf提前释放的问题。当控制器退出的时候,weakself指向的对象销毁了,所以weakself就会被释放掉,这个时候
while ( !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
这个就是空的也就是NO,取反就是YES,所以就又会进入while循环重新runloop,所以会看到一直打印不了end,所以线程也释放不了。
看解决方案,判断一下weakself,判断必须有值,
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while ( !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@----end----", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
@end
但是又发现一个问题,就是点击页面,停止,会崩溃看下图
发生上述的原因是因为:进来 点击 然后停止之后,就已经把runloop停止掉了,而且线程已经是空的了,当退出控制器的时候,会再次调用stop,再调用这个方法,因为线程已经是空的了,所以就会爆坏内存访问。所以修改如下:把线程置位空,并且判断一下
//
// ViewController.m
// Interview03-线程保活
//
// Created by MJ Lee on 2018/6/3.
// Copyright © 2018年 MJ Lee. All rights reserved.
//
#import "ViewController.h"
#import "MJThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@----end----", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (!self.thread) return;
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
if (!self.thread) return;
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 清空线程
self.thread = nil;
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
@end
用起来比较麻烦,现在封装起来
runloop如何响应用户操作的,具体流程是什么样的
首先是由source1来把系统事件捕捉,也就是你一点击屏幕,这个就是一个source1事件,source1会把它包装成一个事件队列EventQueue, 这个事件队列又在source0里面处理,是先由source1捕捉,再由source0去处理。