I am using background threads with following pattern:
// Defined in .h-file as ivar
BOOL backgroundThreadIsAlive;
// .m file
static NSThread *backgroundThread = nil;
- (id)init
{
if (self = [super init])
{
if (backgroundThread == nil)
{
backgroundThreadIsAlive = YES;
backgroundThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[backgroundThread start];
}
}
return self;
}
- (void)threadMain:(id)data
{
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (backgroundThreadIsAlive)
{
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
My application sometimes crash with SIGSEGV in the line
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
I am using ARC. The crash is not reproducible. Just found it, when diving into testflight and saw this is the most often crash I got. It seems to appear independently from iOS version (I support iOS5+) and device type.
May someone have a hint for me, what I do wrong?
Is there a better solution doing background-threads? (maybe using GCD)
Is there a way to reproduce those issues?
Thank you for your time and guidiance. :)
Related
I have a function which takes a block as parameter:
typedef void (^ MyBlock)(int);
-(void)doTask:(MyBlock)theBlock{
...
}
I need to run above function on another thread, I want to use - performSelector:onThread:withObject:waitUntilDone: , my current code:
NSThread *workerThread = [[NSThread alloc] init];
[workerThread start];
[self performSelector:#selector(doTask:)
onThread:workerThread
withObject:???
waitUntilDone:NO];
BUT, How can I pass MyBlock parameter with this approach? (Please don't suggest GCD, I am wondering how can I do with my current code, is it possible?)
This answer assumes you are using ARC. If you are not then you need to do a little more, but overall the answer is the same.
BUT, How can I pass MyBlock parameter with this approach?
A block is an object, you don't need to do anything special. E.g.:
[self performSelector:#selector(doTask:)
onThread:workerThread
withObject:^(int arg){ NSLog(#"block passed: %d", arg); }
waitUntilDone:NO];
HTH
[self performSelector:#selector(someMethod)
onThread:[Your thread]
withObject:[your object]
waitUntilDone:NO];
-(void)someMethod
{
[self doTask:^(int intValue) {
// Do your task
}];
}
First create thread like this so that it is alive and you can call methods from that thread
-(void) myThreadMainMethod: (id) sender {
#autoreleasepool {
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (true) { // add your condition for keeping the thread alive
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
}
NSThread* workerThread = [[NSThread alloc] initWithTarget:self
selector:#selector(myThreadMainMethod:)
object:nil];
[workerThread start];
Then write something like this
-(void)doTask:(MyBlock)theBlock{
NSLog(#"do task called now execute the block");
theBlock(1);
}
MyBlock block1 = ^(int a) {
NSLog(#"integer %i", a);
};
[self performSelector:#selector(doTask:)
onThread:[NSThread mainThread]
withObject:block1
waitUntilDone:NO];
I want to write a launch daemon in iOS which connects to networks programmatically. For connecting to networks, I'm using Cykey's (David Murray) WifiManager app. Before writing the daemon it worked perfectly (I have used it in a springboard tweak) but now it doesn't scan for available networks (the method - (void)_scanDidFinishWithError:(int)error isn't been called). By logging, I found that the method _scan in DMNetWorksManager is called but the static void DMScanCallback(WiFiDeviceClientRef device, CFArrayRef results, int error, const void *token) isn't called. I think it's because of the runloop I have in the main.m file. Here is my code:
main.m:
int main (int argc, const char * argv[])
{
#autoreleasepool
{
// insert code here...
MSManager *server = [[MSManager alloc] init];
//start a timer so that the process does not exit.
/*NSDate *now = [[NSDate alloc] init];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:now
interval:.01
target:server
selector:#selector(start)
userInfo:nil
repeats:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[runLoop run];*/
[server start];
CFRunLoopRun();
}
return 0;
}
and the start method in MSManager.m:
- (void)start
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managerDidFinishScanning)
name:kDMNetworksManagerDidFinishScanning
object:nil];
//Some other code here....
}
- (void)managerDidFinishScanning
{
//Do some work here
}
NOTE: Iv'e used both CFRunloop and NSRunLoop (commented in the code) but none of them worked.
I have an application that creates a background thread for the network messages. The application works nearly perfectly unless the server it connects to closes the connection. I am unsure of why this happens but any advice is greatly appreciated. I've included the snippets of code that can be followed to the problem. If something is vague or more detail is needed please let me know.
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventErrorOccurred:
{
NSLog(#"NSStreamEventErrorOccurred");
[self Disconnect:self];
}
}
}
- (void)Disconnect:(id)sender {
[self performSelector:#selector(closeThread) onThread:[[self class]networkThread] withObject:nil waitUntilDone:YES];
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
}
+ (NSThread*)networkThread
{
// networkThread needs to be static otherwise I get an error about a missing block type specifier
static NSThread* networkThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkThread = [[NSThread alloc] initWithTarget:self selector:#selector(networkThreadMain:) object:nil];
[networkThread start];
});
return networkThread;
}
The hang up occurs on the return networkThread line. After executing that line the application seems to hang and freeze and I can't put my finger on why.
Thanks in advance.
EDIT
Here is the snippet of code for CloseThread for those interested
- (void)closeThread
{
/*if(!inputStream)
return;
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream release];
inputStream = nil;*/
}
I suggest changing:
[self performSelector:#selector(closeThread) onThread:[[self class]networkThread] withObject:nil waitUntilDone:YES];
to:
[self performSelector:#selector(closeThread) onThread:[[self class]networkThread] withObject:nil waitUntilDone:NO];
That is, don't wait.
I have an application that connects to a server using NSStream on another thread. The application also closes the connection should the user decide to log out. The problem is that I am never able to successfully close the stream or the thread upon having the user disconnect. Below is my code sample on how I approach creating a thread for my network and trying to close the stream:
+ (NSThread*)networkThread
{
static NSThread *networkThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkThread = [[NSThread alloc] initWithTarget:self selector:#selector(networkThreadMain:) object:nil];
[networkThread start];
});
return networkThread;
}
+ (void)networkThreadMain:(id)sender
{
while (YES)
{
#autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
}
}
- (void)scheduleInThread:(id)sender
{
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream open];
}
- (void)closeThread
{
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream release];
inputStream = nil;
}
Call made when trying to connect the inputstream:
[self performSelector:#selector(scheduleInThread:) onThread:[[self class] networkThread] withObject:nil waitUntilDone:YES];
Any advice is greatly appreciated.
The way you're mixing static and instance variables is confusing. Are you married to doing it that way? If you put this inside an NSOperation and ran it using an NSOperationQueue I think you'd get much cleaner encapsulation. The operation will manage its own async thread so you don't have to. Also, I highly recommend using ARC if you can.
A few notes:
Make sure to set the stream's delegate and handle delegate events. You should probably do that inside the operation (make the operation the delegate) and close the stream and finish the operation when necessary.
There may be other failure conditions for the stream besides NSStreamStatusClosed, such as NSStreamStatusNotOpen, etc. You will probably need to add additional handling, which can be done by listening to the delegate methods.
Your code is probably not working right mainly because your while loop runs the runloop forever. You have to have conditions in which to break out. NSOperation gives you some pretty good standardized ways of doing this.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface AsyncStreamOperation : NSOperation
#end
NS_ASSUME_NONNULL_END
#import "AsyncStreamOperation.h"
#interface AsyncStreamOperation ()
#property (atomic, strong) AsyncStreamOperation *config;
#property (atomic, strong) NSInputStream *stream;
#property (atomic, assign, getter=isExecuting) BOOL executing;
#property (atomic, assign, getter=isFinished) BOOL finished;
#end
#implementation AsyncStreamOperation
#synthesize executing = _executing;
#synthesize finished = _finished;
- (instancetype)initWithStream:(NSInputStream *)stream
{
self = [super init];
if(self) {
_stream = stream;
}
return self;
}
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
#synchronized (self) {
return _executing;
}
}
- (void)setExecuting:(BOOL)executing
{
#synchronized (self) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (BOOL)isFinished
{
#synchronized (self) {
return _finished;
}
}
- (void)setFinished:(BOOL)finished
{
#synchronized (self) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)start
{
// Get runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// Schedule stream
[self.stream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[self.stream open];
// Loop until finished
// NOTE: If -cancel is not called, you need to add your own logic to close the stream so this loop ends and the operation completes
while(self.executing && !self.finished && !self.cancelled && self.stream.streamStatus != NSStreamStatusClosed) {
#autoreleasepool {
// Maximum speed once per second or CPU goes through the roof
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
}
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[super cancel];
[self.stream close];
self.executing = NO;
self.finished = YES;
}
#end
How to make queue of API requests? So i've got some functions:
...
- (void) getUserBasicInfo:(NSString *)stringWithContestOfId;
- (void) getMyFriends;
- (void) getMyEducation;
...
I would like to make a function which call all this functions. How to take a pause until first functions finish? I tried to do something like this:
...
[currentUser getMyCurentCity];
while ([currentUser getFlag] != TRUE) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[currentUser getMyCurentCity];
...
getFlag - functions which returns success-flag, which becomes true in:
- (void)request:(FBRequest *) request didReceiveResponse:(NSURLResponse *)response{
flag = TRUE;
NSLog(#"I have some information");
}
But finally when i used this method there was only one success request of many. Have you got more ideas how to do it? It will be perfect to do like : NSOperationQueue.
Thanks everybody...
[currentUser getMyCurentCity];
while ([currentUser getFlag] != TRUE) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[currentUser setFlagFalse];
[currentUser getUserBasicInfo:#"me"];
while ([currentUser getFlag] != TRUE) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[currentUser setFlagFalse];