I need to make a connection to my server to get some JSON data and I have to support both iOS 6 and iOS 7.
Should I create two classes? One with NSURLSession for iOS 7 and one with NSURLConnection for iOS 6? Or should I just use NSURLConnection for both of them?
What benefit would you gain by creating two separate classes that do essentially the same thing? If you can't use NSURLSession because it's only supported in iOS 7, and you can get the same functionality using NSURLConnection, which works in both, then just use NSURLConnection. You will have less code to maintain.
Great question. In fact I had the same question and researched it quite a bit and I think this is a good place to use a protocol (a.k.a. interface in other languages). This is based off of the quote "Program to an interface, not an implementation" from the famous "Gang of Four" Patterns Book. I think it's best to try and code for the future so I never get hammered if they decide to deprecate something (which isn't the case here, but you never know).
Instead of writing classes, write a protocol that defines the methods you want to use and then create 2 different classes that implement those methods. In your app you would make a pointer that can point to any class that implements all of that protocols methods and then each implementing class can use whatever frameworks/libraries/other code they want to make that happen.
As an example, you could create a Server protocol like this:
// Server.h
#protocol Server <NSObject>
#required
- (void)callService:(NSString *)service withData:(NSData *)data;
#end
and then create a RestServer class like this:
// RestServer.h
#import "Server.h"
#interface RestServer : NSObject <Server>
#end
// RestServer.m
#import "RestServer.h"
#implementation RestServer
- (void)callService:(NSString *)service withData:(NSData *)data {
// Code using REST
}
#end
and then create another class like SoapServer:
// SoapServer.h
#import "Server.h"
#interface SoapServer : NSObject <Server>
#end
// SoapServer.m
#import “SoapServer.h"
#implementation SoapServer
- (void)callService:(NSString *)service withData:(NSData *)data {
// Code using SOAP
}
#end
Code your main app to just use a pointer to the interface and now you can swap classes without having to change your main code:
// SomeViewController.m
#import “Server.h”
#import “RestServer.h”
#import “SoapServer.h”
…
- (void)someMethod() {
id<Server> myServer;
if ([self shouldIUseSoap])
myServer = [[SoapServer alloc] init];
else
myServer = [[RestServer alloc] init];
[myServer callService:#"loginUser" withData:[self getData]];
}
Now you can change server classes whenever you want and never have to go hunt down all the places in your code where you make calls to callService:withData:. THIS IS THE BENEFIT OF PROGRAMMING TO INTERFACES!
I used Rest vs Soap because I figured people newer to Objective-C might understand that better, but in your case you’d maybe have a ConnectionServer vs SessionServer or something like that.
Another good read on programming to interfaces/protocols can be found here: https://stackoverflow.com/a/384067/504873
If you have to use NSURLCredentialPersistenceForSession if you have to get into a Windows Authentication network...then using NSURLConnection will create multiple problems for you. I'm going through the pain right now and have come to the conclusion that I need both to support iOS 7. Basically, if you use NSURLConnection and willSendRequestForAuthenticationChallenge, you will find that in iOS 7, your session will end with a mind of it's own (seems like a 30 second mind span). So if you have to persist a credential to access more SOAP or whatever, welcome to the terror dome! I will report back to you with code if I find a smooth solution.
At time of writing, NSURLConnection has been deprecated in OS X 10.11 and iOS 9.0 but my Apps need to support OS X 10.7 and iOS 6.
So now you HAVE to use NSURLSession for ongoing projects BUT also support the now deprecated NSURLConnection class for supported legacy OS releases!
I would these days vote for TenaciousJay solution with Compiler warning suppression around the NSURLConnection class implementation.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/* NSURLConnection code here */
#pragma GCC diagnostic pop
The benefit you would you gain by creating two separate classes that do essentially the same thing is that you can eventually CUT off the old, deprecated solution when you can finally drop support for legacy OS releases.
My code decision to use one class or the other would not be based upon some class property but on the result of a Macro like:
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
for iOS or for OS X:
NSString *systemVersion = nil;
if ([[NSProcessInfo processInfo] respondsToSelector:NSSelectorFromString(#"operatingSystemVersion")]) {
NSOperatingSystemVersion operatingSystemVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
systemVersion = [NSString stringWithFormat:#"%ld.%ld.%ld", (long)operatingSystemVersion.majorVersion, (long)operatingSystemVersion.minorVersion, (long)operatingSystemVersion.patchVersion];
} else {
SInt32 versionMajor=0, versionMinor=0, versionPatch=0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Gestalt(gestaltSystemVersionMajor, &versionMajor);
Gestalt(gestaltSystemVersionMinor, &versionMinor);
Gestalt(gestaltSystemVersionBugFix, &versionPatch);
#pragma GCC diagnostic pop
systemVersion = [NSString stringWithFormat:#"%ld.%ld.%ld", (long)versionMajor, (long)versionMinor, (long)versionPatch];
}
NSLog(#"[Line %d] %s OS X Runtime Version: '%#'", __LINE__, __PRETTY_FUNCTION__, systemVersion);
Related
I have a application which gets audioCDPlayList from iTunes. This app works fine up to macOS High Sierra, but does not work correctly on macOS Mojave Beta 3 (18A326h).
I have investigated the reason and then found that the following strange behavior:
GetAudioCDInfoFromiTunes.h
#import <Foundation/Foundation.h>
#import <ScriptingBridge/ScriptingBridge.h>
#import "iTunes.h"
#interface GetAudioCDInfoFromiTunes : NSObject
- (NSMutableDictionary *)getAudioCDInfoFromiTunes;
#end
GetAudioCDInfoFromiTunes.m
- (NSMutableDictionary *)getAudioCDInfoFromiTunes {
// Declear iTunes scripting bridge variable
iTunesApplication *iTunesApp = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
SBElementArray *sources = [iTunesApp sources];
NSLog(#"sources=%#", sources);
NSLog(#"count=%ld", [sources count]);
iTunesPlaylist *aAudioCDPlayList = nil;
for (iTunesSource *src in sources) {
NSLog(#"src=%#", src);
SBElementArray *playlists = [src audioCDPlaylists];
NSLog(#"playlists=%#", playlists);
for (iTunesPlaylist *aPlaylist in playlists) {
NSLog(#"aplaylist=%#", aPlaylist);
if ([aPlaylist isKindOfClass:[NSClassFromString(#"ITunesAudioCDPlaylist") class]]) {
aAudioCDPlayList = [aPlaylist get];
break;
}
}
}
... SNIP ...
}
Executing the above code, NSLog of Line.8, count of sources is 0. And therefore for loop of Line.12 don't work. Then the result [aPlaylist get] is null.
Does anyone know the reason why the count of sources is 0?
Plase let me know how can I run my ScriptingBridge code on Mojave Beta...
Mojave has tightened data security and privacy and that includes scripting. See WWDC 2018 session 702.
The first time your app tries to control iTunes, Mojave will prompt to get your confirmation to allow that. It will remember your choice so it doesn't ask again.
I guess you must have denied it permission once. After that, it just always prevented your app from controlling iTunes.
Since developers need to test their app's behavior when this prompt is displayed, when permission is denied, and when it's granted, Apple has included a command-line utility, tccutil, to reset the remembered user choices. The command to reset permissions regarding which apps may control other apps is tccutil reset AppleEvents.
I am new to Objective-C and I am trying to start right away with TestDrivenDevelopment, since I find it really assuring, when at least the tests do pass.
Before that I made some Tutorials in Java where I got a little understanding for TDD. Brett Schucherts Video-Tutorials where he goes step by step through coding a full RPNCalculator is a gold mine to learn the stuff by watching him in action.
To reduce code duplication there is for instance this nice thing where you do:
#Before
public void init() {
/* Stuff that which will be set up before the call of each test method/*
}
which is then called before each test you have in your TestClass in Java.
This I want to realize in Objective-C + Xcode. I should mention, that I am using Xcode 4.3 (latest version) and that I am using the built in TestFramework.
The only thing I found in the web that came near what I am looking for was this answer on SO.
Unfortunately I am not able to reproduce the described way of doing things. A minimal runnable example and/or a more detailed explanation for a Newcomer would would be awesome and well appreciated!
By the way, sorry for the bad english. Still learning the language. :-)
Edit:
Here is a minimal example which does not work. Perhaps someone can tell me what is wrong. Xcode seems not to be able to recognize board inside the body of the test methods.
#import <SenTestingKit/SenTestingKit.h>
#interface ABoardShould : SenTestCase
#end
#import "ABoardShould.h"
#implementation ABoardShould
- (void)setUp
{
[super setUp];
int rowCount = 6;
int columnCount = 7;
Board *board = [[Board alloc] initWithShapeRowCount:rowCount andColumnCount:columnCount];
}
- (void)tearDown
{
// Tear-down code here.
[super tearDown];
}
- (void)testHaveItsShapeSetWhenInitialised {
STAssertEquals([board rowCount], rowCount, #"");
STAssertEquals([board columnCount], columnCount, #"");
}
- (void)testHaveTheDimensionsOfItsBoardMatchTheGivenShape {
NSMutableArray *expectedFields = [[NSMutableArray alloc] initWithCapacity:columnCount*rowCount];
for(int i=0; i < (rowCount*columnCount); i++) [expectedFields addObject: [NSNumber numberWithInt: 0]];
STAssertEquals([expectedFields count], [[board fields] count], #"");
}
The setup and teardown methods of your test case class must be called setUp and tearDown. This is described in Xcode Unit Testing Guide: Writing Test Case Methods.
In your example, board is a local variable in the setUp method. You need to make it an instance variable.
I would like to create a user agent in Objective-C that listens for notifications from the default NSDistributedNotificationCenter. The agent will not have a GUI. When I create a Cocoa application (I will also be using Distributed Objects, which I think is only in Cocoa) in Xcode, however, Xcode sets the project as a GUI application.
In the main function, I remove the NSApplicationMain(...) function call to remove the GUI elements from the application. However, now I can't get the thread to wait (listen for) notifications coming in from the NSDistributedNotificationCenter. The app just starts and quits immediately.
I looked into using the NSRunLoop from the current NSThread, however, it seems that NSRunLoops only wait on NSPorts. There's no mention of waiting on NSNotifications.
NSDistributedNotificationCenter is Foundation, so you don't need to create a GUI app. You can create a command line template, for example, and run it from terminal. As a very simple example, you could create an example that just prints out every distributed notification it receives below.
To build, copy into an Xcode template for a Foundation command line app, or simply copy into a text file named something like test_note.m and build according to the comments. In this example, the application will never end (CFRunLoopRun() never returns) and you will have to kill it by hitting CTRL+C from the terminal or killing it with something like kill or the activity monitor.
// test_build.m
// to build: clang -o test_build test_build.m -framework foundation
#import <Foundation/Foundation.h>
#interface Observer : NSObject
- (void)observeNotification:(NSNotification*)note;
#end
#implementation Observer
- (void)observeNotification:(NSNotification*)note
{
NSLog(#"Got Notification: %#", note);
}
#end
int main (int argc, char const *argv[])
{
#autoreleasepool {
Observer* myObserver = [[Observer alloc] init];
[[NSDistributedNotificationCenter defaultCenter] addObserver:myObserver selector:#selector(observeNotification:) name:nil object:nil];
CFRunLoopRun();
}
return 0;
}
I'm developing an iOS application that needs to deploy to iOS 3.1.3. I need to extend some of the functionality of the NSData class and am using the following code inside NSData+Base64 (truncated to show the interesting part):
NSData+Base64.h
[...]
#interface NSData (Base64)
+ (NSData *)dataFromBase64String:(NSString *)aString;
- (NSString *)base64EncodedString;
#end
NSData+Base64.m
#implementation NSData (Base64)
[...]
//
// base64EncodedString
//
// Creates an NSString object that contains the base 64 encoding of the
// receiver's data. Lines are broken at 64 characters long.
//
// returns an autoreleased NSString being the base 64 representation of the
// receiver.
//
- (NSString *)base64EncodedString
{
size_t outputLength;
char *outputBuffer =
NewBase64Encode([self bytes], [self length], true, &outputLength);
NSString *result =
[[[NSString alloc]
initWithBytes:outputBuffer
length:outputLength
encoding:NSASCIIStringEncoding]
autorelease];
free(outputBuffer);
return result;
}
#end
However, when I try to message this selector:
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
NSString *hash = [HMAC base64EncodedString];
I get the following error:
-[NSConcreteData base64EncodedString]: unrecognized selector sent to instance 0x6146e70
2010-11-09 13:44:41.443 SpringboardApplication[21318:40b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteData base64EncodedString]: unrecognized selector sent to instance 0x6146e70'
I read a lot about iOS 3.1.x having problems with categories. I tried adding the flags -all_load and -ObjC (both separately and together) to no avail. I would really appreciate some direction of how to get this selector to work.
Thanks!
It really seems like your category isn't being compiled or linked into the same target that you're using it from. You should make sure that NSData+Base64.m is marked to be compiled by the same target that it's being used from by getting info on the two files and comparing the targets they're assigned to.
A test you can perform is to add a line with an #error error message to NSData+Base64.m, which will cause the build to fail when it gets to that file. Like this:
#error We're now compiling NSData+Base64.m
Then look and see which target fails to compile.
I had the same issue with ARC project which was linking with non-ARC module having category extension.
Fixed the issue by adding "Other Linker Flags: -all_load" in parent ARC project.
Have you #imported the header file for your category? I know it sounds simple, but I forget nearly every time.
There is a great post on The Carbon Emitter about about handling categories in iOS. It details an easy way to handle importing categories to your project.
Make a file containing all of your category imports, in this example it is Extensions.h:
#import "NSDate+Formatting.h"
#import "UIFonts+MyFonts.h"
#import "UIViewController+Tourbot.h"
Add import your file in AppName-Prefix.pch:
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
#import "Extensions.h" // Add import here
#endif
In My case when I got this error I simply added the .m file in the Compiled resources, and it get worked. This can be achieved by selecting target project->Build Phases->Compile Sources. Then you click on the + button from its bottom left. In this case you may add 'NSData+Base64.m' file to the compile sources. Then you clean your project and run. I guess this may help.
I actually have two questions regarding exception/error handling in the iPhone app that I am making:
The app uses Internet, but when there's no connection, the app just dies (during launch). How can I handle this to print some infomsg to the user, instead of just getting thrown back to the springboard?
Can someone show me an example of how to handle for instance a "page not found" or "no contact with server" error, so I can give some sort of info to the user in the same way as above?
For crashes, the first step is to use error messages and the debugger to figure out what call is causing the problem. If the problem is caused by an uncaught exception, read this Apple article on exception handling. The specific answer really depends on your code and exactly what is causing the crash, so I won't speculate about a particular solution.
As far as detecting server error response codes (such as 404), that's more specific to WebKit. I assume you're using UIWebView on iPhone, and you've probably noticed that none of the primary methods return errors. This is because it uses a delegate model to report progress or errors asynchronously. (It makes sense because you don't want your UI code to be at the mercy of a slow-loading (or non-existent) webpage. To be notified of such errors, there are a few steps.
Adopt the UIWebViewDelegate protocol, usually in the same class that will start the webpage load for convenience.
Set that object as the delegate of the UIWebView instance. (It has a delegate property, so you can use something like either uiView.delegate = self or [uiView setDelegate:self] based on what you prefer.)
Implement the webView:didFailLoadWithError: method in that class. (You can be notified when the load finishing by implementing webViewDidFinishLoad: as well.) This is where you include the logic of what should happen when an error occurs.
I didn't see any detailed documentation on the content of any particular errors handed back via this delegate method, but it's a standard NSError object, and I recommend checking out the contents by calling its methods, such as -localizedDescription and -userInfo.
Here is some sample code with #import statements excluded for brevity.
MyClass.h
#interface MyClass : NSObject <UIWebViewDelegate> {
IBOutlet UIWebView* myWebView;
}
-(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError *)error;
#end
MyClass.m
#implementation MyClass
- (id) init {
if ((self = [super init]) == nil)
return nil;
// initialize myWebView
myWebView.delegate = self;
return self;
}
- (void) webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
...
}
#end
Testing for a connection is pretty easy...
NSString * test = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]];
if (test == nil) {
//display an alertview saying you don't have an internet connection
}
Using a URL to test for a connection is not a good idea, it is not robust enough to determine if the internet connection is down, the website is down or some other network issue etc and above all it adds an overhead to the call as far as network traffic.
Look at the Reachability demo on the Apple site, it uses the correct way to determine connectivity, including whether you are on wifi etc.