I have a simple app which loads text from a RSS when there is no internet it displays a empty tableView. I would like to make it so that when there is no internet it gives some text saying there is no internet available and a button to try to reconnect.
In the attempt to make this I used Tony Million's Reachability class as in this question.
I set a boolean to YES and NO in the functions like this :
- (void)testInternetConnection
{
internetReachableFoo = [Reachability reachabilityWithHostname:#"www.google.com"];
// Internet is reachable
internetReachableFoo.reachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
NSLog(#"Yayyy, we have the interwebs!");
internetConnect = YES;
});
};
// Internet is not reachable
internetReachableFoo.unreachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^
{
NSLog(#"Someone broke the internet :(");
internetConnect = NO;
});
};
[internetReachableFoo startNotifier];
}
Now when I try to check the boolean in the viewDidLoad function it will always return before the function is finished. This is because the Reachability class is in a background thread. I don't know how I can let my code wait for the result before proceeding.
So I should let my code wait for result and then depending on the result make the tableView disappear and change it to text with a button.
I want to know :
How to make my code wait for result of the background thread.
How to reconnect. (with a loading bar or something to let the user
know it is searching for connection).
You could also check the reachability as
NSInteger reachabilityStatus = 0;
reachabilityStatus = [self checkNetworkReachability];
if (reachabilityStatus) {
//network is available so perform network oriented task;
} else {
// show an alert saying network is unavailable;
}
- (NSInteger)checkNetworkReachability {
networkReachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];
if (networkStatus == NotReachable) {
NSLog(#"There IS NO internet connection");
} else {
NSLog(#"There IS internet connection");
}
return (NSInteger)networkStatus;
}
Err... ...why don't put the code with you want to go on with into the block? Do you really need that BOOL?
You may wrap your network requests into a reachability block as well anyway, return with an error when there is no connection, wire up that specific error to the UI.
A bit pseudo, but you'd get the idea. The point is that you need connection only when you want to request something, no need for monitoring every time I think. Using eppz!reachability you can get reachability status on demand, with blocks those gets called once, not every time when reachability changes.
Also you're probably interested in the host you're trying to reach, not google.com. Your feed can be unreachable while google.com works fine.
-(void)fetchFeed
{
[EPPZReachability reachHost:#"your.rss.host.com"
completion:^(EPPZReachability *reachability)
{
if (reachability.reachable)
{
[self hideRetryUI]; // UI
[self fetchFeed:^(Feed *feed) // Networking
{ [self showFeed:feed]; }]; // UI (probably table view reload)
}
else
{
[self showRetryUI]; // UI
}
}];
}
-(IBAction)retryTouchedUp
{ [self fetchFeed]; }
Related
I'm having this wierd problem with the app freezing at a certain point. I'm guessing its got to do with how I'm using NSConditionLock.
Theres a library I have been given to use, which consists of a series of survey questions, but it works in such a way that it races directly to the last question without accepting answers, hence the need to pause the thread and accept input from the user.
I haven't used it before so maybe someone could help if I'm implementing it wrongly?
Please let me know if the code provided is insufficient.
- (void)viewDidLoad
{
[super viewDidLoad];
//INITIALISE CONDITION LOCK WITH CONDITION 0
condition=[[NSConditionLock alloc]initWithCondition: 0];
}
- (IBAction)startPressed:(UIButton*)sender {
if (sender.tag == 1) {
//START BACKGROUND THREAD
surveyThread = [[NSThread alloc] initWithTarget:self selector:#selector(runProjecttest) object:nil];
[surveyThread start];
}
else
{
//DO SOME STUFF AND THEN UNLOCK
[condition unlockWithCondition:1];
}
}
- (void) runProjecttest:(AbstractTask *)rendertask
{
// DO STUFF AND SHOW UI ON MAIN THREAD, THEN LOCK
[self performSelectorOnMainThread:#selector(showUI:) withObject:task waitUntilDone:YES];
[condition lockWhenCondition: 1];
}
EDIT: In short, I want the Objc equivalent of this java snippet...
this.runOnUiThread(showUI);
try
{
//SLEEP
Thread.sleep(1000*60*60*24*365*10);
}
catch (InterruptedException e)
{
//WAKE
setResponse(at,showUI);
}
EDIT 2: ShowUI method on Paul's request.
[self removePreviousSubViews];
switch ([task getType]) {
case SingleChoiceType:
{
NSLog(#"SingleChoiceType");
isMultipleChoice = NO;
[self addSingleChoiceView:nil];
break;
}
case TextType:
{
NSLog(#"TextType");
self.txtTextType.keyboardType=UIKeyboardTypeDefault;
[self addTextTypeView:nil];
break;
}
...more cases
}
-(void)addTextTypeView:(NSSet *)objects
{
self.txtTextType.text = #"";
CGRect frame = self.txtQuestionType.frame;
// frame.size = [self.txtQuestionType sizeThatFits: CGSizeMake(self.txtQuestionType.frame.size.width, FLT_MAX)];
frame.size.height = [self textViewHeightForAttributedText:self.txtQuestionType.text andWidth:self.txtQuestionType.frame.size.width andTextView:self.txtQuestionType];
self.txtQuestionType.frame=frame;
self.textTypeView.frame = CGRectMake((self.view.frame.size.width - self.textTypeView.frame.size.width)/2, ( self.txtQuestionType.frame.origin.y+self.txtQuestionType.frame.size.height), self.textTypeView.frame.size.width, self.textTypeView.frame.size.height);
[self.view addSubview: self.textTypeView];
}
I agree with BryanChen, I think you may have another issue. Without details on the survey library, it is impossible to confirm, but assuming that it is a UIViewController than accepts touch inputs to progress through a series of questions, it is hard to see why it is a threading issue - it simply shouldn't advance without user interaction.
That aside, your use of NSCondtionLock doesn't look right either.
Essentially an NSConditionLock has an NSInteger that represents the current 'condition', but just think of it of a number. There are then two basic operations you can perform -
lockWhenCondition:x will block the current thread until the 'condition' is 'x' and the lock is available. It will then claim the lock.
unlockWithCondition:y releases the lock and sets the condition to 'y'
There are also methods to set timeouts (lockBeforeDate) and try to claim the lock without blocking (tryLock, tryLockWhenCondition).
To synchronise two threads, the general pattern is
Initialise Lock to condition 'x'
Thread 1 lockWhenCondition:x -This thread can claim the lock because it is x
Thread 2 lockWhenCondition:y - This thread will block because the lock is x
Thread 1 completes work, unlockWithCondition:y - This will enable Thread 2 to claim the lock and unblock that thread
Your code looks strange, because you are starting a thread in your if clause but unlocking in an else clause. I would have thought you would have something like -
-(IBAction)startPressed:(UIButton*)sender {
if (sender.tag == 1) {
//START BACKGROUND THREAD
surveyThread = [[NSThread alloc] initWithTarget:self selector:#selector(runProjecttest) object:nil];
[surveyThread start];
[condition:lockWithCondition:1]; // This will block until survey thread completes
[condition:unlockWithCondition:0]; // Unlock and ready for next time
}
}
- (void) runProjecttest:(AbstractTask *)rendertask
{
// DO STUFF AND SHOW UI ON MAIN THREAD, THEN LOCK
[condition lockWhenCondition: 0];
[self performSelectorOnMainThread:#selector(showUI:) withObject:task waitUntilDone:YES];
[condition unlockWithCondition:1];
}
BUT This looks like a recipe for deadlock to me, because you are performing the showUI selector on the main thread that is blocked waiting for the survey thread to complete.
Which brings us back to the question, what does showUI do and why is it skipping directly to the end?
If I leave my device untouched for a while, the radios sleep to conserve power. If I check reachability in this state, I learn that the host I am checking for is unreachable. How can I wake the radio back up? Do I need to explicitly make a network call? Can I detect that the reason the network is unreachable is that the radio is asleep?
The basic check I'm doing works like this (taken from Apple's Reachability):
// The setup
- (id)init
{
self = [super init];
if (self)
{
_reachabilityRef = SCNetworkReachabilityCreateWithName(NULL, [#"myserver.example.com" UTF8String]);
}
return self;
}
// The check
- (BOOL)isReachable
{
return [self currentReachabilityStatus] != NotReachable;
}
// For reference
- (NetworkStatus)currentReachabilityStatus
{
NSAssert(_reachabilityRef != NULL, #"currentNetworkStatus called with NULL SCNetworkReachabilityRef");
NetworkStatus returnValue = NotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
{
returnValue = [self networkStatusForFlags:flags];
}
return returnValue;
}
You don't manage the device's radio. iOS does that for you.
Reachability says if the internet is reachable or not, not what radios are on or off. You can be reachable by cell, even if wifi is on (or just as easily, reachable by wifi, even if cell is available).
For the most part, radio power usage will be dwarfed by the screen's power usage. NSURLSessionConfiguration has a discretionary property that can be a hint to the OS that you don't need this request to start right away, but, that property is already ignored by background session tasks, to opt people into helping to save power.
I've gotten in a few cases when something receives multiple refresh calls in quick succession, eg:
- ViewController receives multiple KVO notifications.
- Datamanger class that is called from setters to refresh when multiple settings change.
Ideally I would like to execute only the last refresh call from a series (drop all the intermediate ones).
Right now I'm using an isRefreshing property and a needRefresh to block excessive refreshes, eg:
- (id)init {
...
[self observeValueForKeyPath:#"isRefreshing" ....];
}
- (void)setParameter:(NSInteger)parameter {
....
[self refresh];
}
/* and many more kinds of updates require a refresh */
- (void)setAnotherProperty:(NSArray*)array {
....
[self refresh];
}
- (void)refresh {
if (self.isRefreshing) {
self.needRefresh = YES;
return;
}
self.isRefreshing = YES;
...
self.isRefreshing = NO;
}
- observeValueForKeyPath..... {
if (!self.isRefreshing && self.needsRefresh) {
self.needsRefresh = NO;
[self refresh];
}
}
Is there a better solution for this kind of problem?
You can create a NSOperationQueue with concurrency set to one and only submit a new operation to it when its operation count is zero. (Or use cancellation logic to remove pending jobs so that only one new one is queued if there's a job in progress.)
What you're doing is reasonable for a single-threaded system but would become fairly complicated for multiple threads.
Looks like you should delay refreshing for a while.
You can use different techniques to do so. It is enough only one flag.
For example you may use async block to make a delay for a one main run-loop cycle
- (void)setParameter:(NSInteger)parameter {
....
[self requestRefrhesh];
}
- (void)setAnotherProperty:(NSArray*)array {
....
[self requestRefrhesh];
}
...
-(void) requestRefrhesh {
if (self.refreshRequested) {
return;
} else {
self.refreshRequested = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run in main UI thread
//make your UI changes here
self.refreshRequested = NO;
});
}
}
I test internet connection with Reachability and dispatch_async(dispatch_get_main_queue()
when I test the following code it works but it is called multiple times.
Parent:
#protocol RootViewDelegate <NSObject>
#optional
-(void)internetIsDownGoToRoot;
#end
- (void)testInternetConnection
{
internetReachableFoo = [ReachabilityTony reachabilityWithHostname:#"www.google.com"];
__weak typeof(self) weakSelf = self;
// Internet is reachable
internetReachableFoo.reachableBlock = ^(ReachabilityTony *reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Yayyy, we have the interwebs!");
[weakSelf sendLoginRequest];
});
};
// Internet is not reachable
internetReachableFoo.unreachableBlock = ^(ReachabilityTony *reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Someone broke the internet :(");
CloudConnection *sharedInstance=[CloudConnection sharedInstance];
sharedInstance.isUserLoggedIN=NO;
//update login button
[weakSelf updateButtons];
[weakSelf notifyChild];
});
};
[internetReachableFoo startNotifier];
}
-(void)viewDidAppear:(BOOL)animated
{
[self testInternetConnection];
}
-(void)viewWillDisappear:(BOOL)animated
{
internetReachableFoo= nil;
}
//notify childs no connection come back to root
-(void) notifyChild
{
[delegate internetIsDownGoToRoot];
}
Child:
-(void)viewDidAppear:(BOOL)animated
{
NSArray *viewControllers = self.navigationController.viewControllers;
int count = [viewControllers count];
id previousController = [viewControllers objectAtIndex:count - 2];
RootViewController *rvc= previousController;
rvc.delegate=self;
}
-(void)internetIsDownGoToRoot
{
//internet connection is no avaliable go to root
[self.navigationController popToRootViewControllerAnimated:YES];
}
So this is parentview lets say I push-pop childview 5 times and shutdown internet. I see on nslog that
Someone broke the internet :(
Someone broke the internet :(
Someone broke the internet :(
Someone broke the internet :(
Someone broke the internet :(
as you can see I have added internetReachableFoo= nil; but I doesnt change anything.
Whats going on with above code, why it is called multiple times?
What is the possible dangers of using this block?
It's called multiple times because every time you pop the child, the root gets -viewDidAppear: and calls -testInternetConnection, which re-runs the reachability test.
Update: Ok you've changed your question slightly. The reason why you're getting 5 "did disappear" messages is because you never stop the notifier. Reachability keeps itself alive as long as it's running, so nilling out your reference isn't killing it. You need to explicitly say [internetReachableFoo stopNotifier] before nilling it out.
I'm trying to create an app that transfers data between 2+ phones using GKSession. Thing is there are two options:
First: using the GKPeerPicker.. However here I get stuck at the point where I have to implement my own WIFI interface.. apple provides no instructions on how to do that:
- (void)peerPickerController:(GKPeerPickerController *)picker didSelectConnectionType: (GKPeerPickerConnectionType)type {
if (type == GKPeerPickerConnectionTypeOnline) {
picker.delegate = nil;
[picker dismiss];
[picker autorelease];
// Implement your own internet user interface here.
}
}
Second: Skipping GKPeerPicker and doing the whole thing my self, like in this example. However the app dev documentation doesn't provide any instructions on how to send/receive data without using GKPeerPicker.. (nor could I find any example of that on thew web)
I just figured out how to connect devices without the peerpicker. It was a bit of a guessing game because the documentation is pretty unclear and I've looked for so long on the internet for any info about this. I'll try to explain everything here to clear up any questions anyone in the future might have.
From the documentation:
A GKSession object provides the ability to discover and connect to
nearby iOS devices using Bluetooth or Wi-fi.
This was the first step to understand it for me. I thought the GKPeerPickerController was responsible of the advertising and connecting but GKSession actually does all that.
The second thing to understand is that what is referred to as peers are not necessarily connected to you. They can just be nearby waiting to be discovered and connected to. All peers have a state
GKPeerStateAvailable (this is what's useful!)
GKPeerStateUnavailable
GKPeerStateConnected
GKPeerStateDisconnected
GKPeerStateConnecting
So how do we actually connect? Well first we have to create a GKSession object to be able to find peers around us and see when they become available:
// nil will become the device name
GKSession *gkSession = [[GKSession alloc] initWithSessionID:#"something.unique.i.use.my.bundle.name" displayName:nil sessionMode:GKSessionModePeer];
[gkSession setDataReceiveHandler:self withContext:nil];
gkSession.delegate = self;
gkSession.available = YES; // I'm not sure this if this is the default value, this might not be needed
Now we have some delegate calls to respond to. session:didReceiveConnectionRequestFromPeer: and session:peer:didChangeState (you should also handle the calls of GKSessionDelegate for disconnection and failure appropriately)
-(void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
if(state == GKPeerStateDisconnected)
{
// A peer disconnected
}
else if(state == GKPeerStateConnected)
{
// You can now send messages to the connected peer(s)
int number = 1337;
[session sendDataToAllPeers:[NSData dataWithBytes:&number length:4] withDataMode:GKSendDataReliable error:nil];
}
else if (state == GKPeerStateAvailable)
{
// A device became available, meaning we can connect to it. Lets do it! (or at least try and make a request)
/*
Notice: This will connect to every iphone that's nearby you directly.
You would maybe want to make an interface similar to peerpicker instead
In that case, you should just save this peer in a availablePeers array and
call this method later on. For your UI, the name of the peer can be
retrived with [session displayNameForPeer:peerId]
*/
[session connectToPeer:peerID withTimeout:10];
}
}
The other peer now received a request that he should respond to.
-(void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
// We can now decide to deny or accept
bool shouldAccept = YES;
if(shouldAccept)
{
[session acceptConnectionFromPeer:peerID error:nil];
}
else
{
[session denyConnectionFromPeer:peerID];
}
}
Finally to receive our little 1337 message
-(void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession*)session context:(void *)context
{
int number = 1337;
if([data isEqualToData:[NSData dataWithBytes:&number length:4]])
{
NSLog(#"Yey!");
}
}