原来的问题...............................................
如果您是drawRect的高级用户,您将知道当然"drawRect"在"所有处理完成"之前不会实际运行.
setNeedsDisplay将视图标记为无效和操作系统,并且基本上等待所有处理完成.在您想要拥有的常见情况下,这可能会令人愤怒:
当然,当你执行上面的1-6时,所有发生的事情是drawRect 仅在步骤6之后运行一次.
您的目标是在第5点刷新视图.怎么办?
解决原始问题............................................. .
总之,您可以(A)背景大画,并调用前景进行UI更新或(B)可争议地有四种"即时"方法建议不使用后台进程.为了起作用的结果,运行演示程序.它有所有五种方法的#defines.
Tom Swift介绍的真正令人震惊的替代解决方案..................
汤姆斯威夫特解释了非常简单地操纵运行循环的惊人想法.以下是触发运行循环的方法:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
这是一个真正令人惊叹的工程.当然,在操作运行循环时应该非常小心,并且许多人指出这种方法严格适用于专家.
引起的奇怪问题............................................. .
尽管许多方法都有效,但实际上并没有"工作",因为在演示中你会看到一个奇怪的渐进式减速神器.
滚动到我在下面粘贴的"答案",显示控制台输出 - 您可以看到它逐渐减慢的速度.
这是新的SO问题:
运行循环/ drawRect中的神秘"渐进式减速"问题
这是演示应用程序的V2 ...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html
你会看到它测试所有五种方法,
#ifdef TOMSWIFTMETHOD
[self setNeedsDisplay];
[[NSRunLoop currentRunLoop]
runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
[self setNeedsDisplay];
[CATransaction flush];
#endif
#ifdef LLOYDMETHOD
[CATransaction begin];
[self setNeedsDisplay];
[CATransaction commit];
#endif
#ifdef DDLONG
[self setNeedsDisplay];
[[self layer] displayIfNeeded];
#endif …Run Code Online (Sandbox Code Playgroud) 在开始注意之前,这与后台处理无关.没有"计算"涉及到背景.
只有UIKit.
view.addItemsA()
view.addItemsB()
view.addItemsC()
Run Code Online (Sandbox Code Playgroud)
让我们说6s的iPhone
这将发生:
但是,让我说我希望这种情况发生:
(注意"一秒钟"只是一个简单的例子.为了更全面的例子,请参阅本文末尾.)
你是如何在iOS中做到的?
您可以尝试以下方法.它似乎不起作用.
view.addItemsA()
view.setNeedsDisplay()
view.layoutIfNeeded()
view.addItemsB()
Run Code Online (Sandbox Code Playgroud)
你可以试试这个:
view.addItemsA()
view.setNeedsDisplay()
view.layoutIfNeeded()_b()
delay(0.1) { self._b() }
}
func _b() {
view.addItemsB()
view.setNeedsDisplay()
view.layoutIfNeeded()
delay(0.1) { self._c() }...
Run Code Online (Sandbox Code Playgroud)
请注意,如果该值太小 - 这种方法很简单,显然也没有做任何事情.UIKit将继续努力.(它还会做什么?).如果价值太大,那就没有意义了.
请注意,目前(iOS10),如果我没有弄错的话:如果你尝试使用零延迟的技巧,它最好不正常工作.(正如你可能期望的那样.)
view.addItemsA()
view.setNeedsDisplay()
view.layoutIfNeeded()
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date())
view.addItemsB()
view.setNeedsDisplay()
view.layoutIfNeeded()
Run Code Online (Sandbox Code Playgroud)
合理. 但是我们最近的实际测试显示,在许多情况下这似乎不起作用.
(也就是说,Apple的UIKit现在足够精致,可以将UIKit的工作涂抹在那个"技巧"之外.)
思考:在UIKit中,或许有一种方法可以获得一个回调,基本上,它已经绘制了你所堆积的所有视图了吗?还有其他解决方案吗?
一个解决方案似乎是......将子视图放在控制器中,因此您可以获得"didAppear"回调,并跟踪这些回调. 这似乎是婴儿,但也许这是唯一的模式?无论如何它真的会起作用吗?(仅仅是一个问题:我没有看到任何保证,确保确保所有子视图都已被绘制.)
日常用例示例:
•假设可能有七个部分.
•假设每个人通常需要0.01到0.20来构建UIKit(取决于您显示的信息).
•如果你只是"让一切都搞砸了"它通常会好或可以接受(总时间,比如0.05到0.15)......但......
•当"新屏幕出现"时,用户经常会有一个单调乏味的停顿.(.1到.5或更糟 …
首先,我的问题是:你如何管理你的iOS Run-Loop?
接下来我的理由是:我一直在研究各种原型(v.早期开发),并发现了许多令人困惑的问题.
那么有没有人围绕这些问题找到了灵丹妙药?有没有人在这个平台上有一个杀手运行循环架构?目前看起来我必须选择较少的邪恶.
我根本不明白它,但NSTimer在我的应用程序肯定是在后台运行.我有一个NSLog由计时器运行的mehod,它在后台进行记录.它位于带有iOS 4.2.1的iPhone 4上.我在Info.plist中声明了位置背景支持.
我在这里和其他地方阅读了文档和许多讨论,这是不可能的.这是一个iOS错误吗?还是没有文档的功能?我不想使用它并在不久的将来发现,例如随着iOS 4.3的出现,Apple默默地"修复"它并且应用程序无法正常工作.
有人知道更多吗?
我正在学习iPhone上的套接字通信,它的指南说了些什么CFRunloop(这是一个指南CFNetwork,可以在iOS上使用吗?)我在哪里可以了解iOS上的runloop?API参考是不够的.
我希望在当前方法通过并且UI已更新之后执行一个方法.为此,我现在正在使用[object performSelector:@selector(someSelector) withObject:someObject afterDelay:0.0].根据Apple的文档,这会创建一个NSTimer,然后触发并将选择器附加到当前的NSRunLoop.但我不认为这很优雅.有没有一种简单的方法可以直接将选择器排入当前的运行循环,而不会让Cocoa创建一个Timer等?
会performSelectorOnMainThread:withObject:waitUntilDone:(如果我在主线程),或者performSelector:onThread:withObject:waitUntilDone:用waitUntilDone:NO做我想做的开销更少?
欢呼并提前谢谢
MrMage
我正在尝试使用GCDAsyncSocket一个简单的例子,并且发现我缺少某些理解,并希望你们好,人们可以帮助解释这一点.
我在下面设置了GCDAsyncSocket:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:mainQueue];
NSString *host = @"192.168.169.132";
uint16_t port = 2112;
DDLogInfo(@"Connecting to \"%@\" on port %hu...", host, port);
self.viewController.label.text = @"Connecting...";
NSError *error = nil;
if (![asyncSocket connectToHost:host onPort:port withTimeout:5.0 error:&error])
{
DDLogError(@"Error connecting: %@", error);
self.viewController.label.text = @"Oops";
}
else
{
DDLogVerbose(@"Connecting...");
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
DDLogInfo(@"socket:%p didConnectToHost:%@ port:%hu", sock, host, port);
self.viewController.label.text = @"Connected";
// We're just going to send a test string to …Run Code Online (Sandbox Code Playgroud) 我刚刚阅读了以下帖子并尝试实施那里描述的方法:
那里描述的所有东西都可以完美运行。但!当我运行验收测试时,有一件事会破坏确定性。
这是 Github 上的 repo,帖子的作者在这里推送了他的实验(可以在页面底部的评论中找到):https : //github.com/moredip/2012-Olympics-iOS--iPad-and -iPhone--source-code/tree/kiwi-acceptance-mk1
考虑他用于点击视图的这段代码:
- (void) tapViewViaSelector:(NSString *)viewSelector{
[UIAutomationBridge tapView:[self viewViaSelector:viewSelector]];
sleepFor(0.1); //ugh
}
Run Code Online (Sandbox Code Playgroud)
...其中sleepFor有以下定义:
#define sleepFor(interval) (CFRunLoopRunInMode(kCFRunLoopDefaultMode, interval, false))
Run Code Online (Sandbox Code Playgroud)
等待一小段时间直到处理完所有动画并吸收所有可能的东西是一种幼稚的尝试(“幼稚”与作者无关,而是关于它是第一个想到的事实)已(或可能)调度到主运行循环的事件(另请参阅此评论)。
问题是这个简单的代码不能以一种确定性的方式工作。有一堆 UI 交互导致在当前编辑的文本字段的键盘消失之前按下 fx next 按钮点击,等等......
如果我只是将时间从 0.1 增加到 fx 1,所有问题都会消失,但这会导致每一次交互,例如“用文本填充文本字段...”或“点击带有标题的按钮...”都会花费一个第二!
所以我的意思不是仅仅增加这里的等待时间,而是一种使这种人为等待保证我可以继续下一步的测试用例的方法。
我希望它应该是一种更可靠的方法,可以等到所有由当前操作(所有转换/动画或任何主运行循环的东西)引起的东西都完成为止。
总结一下就是一个问题:
有没有办法排空/排空/浸泡所有调度到主线程及其运行循环的东西,以确保主线程空闲并且其运行循环是“空的”?
这是我最初的解决方案:
// DON'T like it
static inline void runLoopIfNeeded() {
// https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFRunLoopRef/Reference/reference.html
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource);
// DON'T like it
if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, …Run Code Online (Sandbox Code Playgroud) 为什么以下代码有效?这是一个小型Cocoa程序,它使用NSOpenPanel选择文件并在Emacs.app中打开它.它可以从命令行运行,并以起始目录作为参数.
NSOpenPanel如何在不调用NSApplication或NSRunLoop的情况下运行?没有显式启动NSApplication或NSRunLoop的Cocoa程序有哪些限制?我原以为其中一个是:你不能使用任何类型的GUI.也许通过调用NSOpenPanel,调用一些调用NSRunLoop的后备代码?我把断点放在+ [NSApplication alloc]和+ [NSRunLoop alloc]上并且它们没有被触发.
main.m:
#import <Cocoa/Cocoa.h>
NSString *selectFileWithStartPath(NSString *path) {
NSString *answer = nil;
NSOpenPanel* panel = [NSOpenPanel openPanel];
panel.allowsMultipleSelection = NO;
panel.canChooseFiles = YES;
panel.canChooseDirectories = NO;
panel.resolvesAliases = YES;
if([panel runModalForDirectory:path file:nil] == NSOKButton)
answer = [[[panel URLs] objectAtIndex:0] path];
return answer;
}
int main(int argc, const char * argv[]) {
NSString *startPath = argc > 1 ? [NSString stringWithUTF8String:argv[1]] : @"/Users/Me/Docs";
printf("%s\n", argv[1]);
BOOL isDir;
if([[NSFileManager defaultManager] fileExistsAtPath:startPath isDirectory:&isDir] && isDir) {
system([[NSString stringWithFormat:@"find …Run Code Online (Sandbox Code Playgroud) 我可以将调度程序指定为RunLoop.main,但我找不到提供关联RunLoop.Mode模式以从发布者接收元素的本机方式。
为什么我需要这个:我正在更新我的发布者的 tableView 单元格,但如果用户正在滚动,UI 不会更新,然后在用户交互或滚动停止后立即更新。这是滚动视图的已知行为,但我希望我的内容尽快显示,并且能够指定运行循环跟踪模式可以解决此问题。
组合 API:我认为该receive(on:options:)方法没有任何匹配选项来提供此功能。我认为在内部,如果我打电话,receive(on:RunLoop.main)那么RunLoop.main.perform { }就会被调用。此执行方法可以将模式作为参数,但这不会暴露给组合 API。
当前想法:为了解决这个问题,我可以自己执行执行操作而不使用组合 API,所以不要这样做:
cancellable = stringFuture.receive(on: RunLoop.main) // I cannot specify the mode here
.sink { string in
cell.textLabel.text = string
}
Run Code Online (Sandbox Code Playgroud)
我可以这样做:
cancellable = stringFuture.sink { string in
RunLoop.main.perform(inModes: [RunLoop.Mode.common]) { // I can specify it here
cell.textLabel.text = string
}
}
Run Code Online (Sandbox Code Playgroud)
但这并不理想。
理想的解决方案:我想知道如何将其包装到我自己的发布者函数实现中以具有如下内容:
cancellable = stringFuture.receive(on: RunLoop.main, inMode: RunLoop.Mode.common) …Run Code Online (Sandbox Code Playgroud) runloop ×10
ios ×6
iphone ×4
cocoa ×3
objective-c ×3
cocoa-touch ×2
background ×1
combine ×1
nstimer ×1
opengl-es ×1
optimization ×1
performance ×1
sockets ×1
swift ×1
uikit ×1