xmppstream delegate methods not getting called on my custom class - objective-c

I started with xmppframework recently,but i am stuck with an issue.i am able to connect to my server on my local network but the xmppstreamdelegate methods are not getting called on my custom class,but works absolutely fine on appdelegate class .Can anyone pls help me on this.Is the delegate only supported on the appdelegate class ?
Header:
#interface XmppClass : NSObject<XMPPStreamDelegate>{
XMPPStream *xmppStream;
Login * loginDetail;
BOOL allowSelfSignedCertificates;
BOOL allowSSLHostNameMismatch;
}
#property (nonatomic, strong, readonly) XMPPStream *xmppStream;
#property (nonatomic, strong) Login *loginDetail;
- (id)initWithLogin:(Login *) loginrefernce;
- (BOOL)connect;
- (void)disconnect;
- (void)setupStream;
#end
Implementation:
#implementation XmppClass
#synthesize xmppStream;
#synthesize loginDetail;
- (id)initWithLogin:(Login *) loginrefernce
{
self = [super init];
if (self) {
self.loginDetail=loginrefernce;
[DDLog addLogger:[DDTTYLogger sharedInstance]];
[self setupStream];
[self connect];
}
return self;
}
- (void)setupStream
{
NSAssert(xmppStream == nil, #"Method setupStream invoked multiple times");
// Setup xmpp stream
//
// The XMPPStream is the base class for all activity.
// Everything else plugs into the xmppStream, such as modules/extensions and delegates.
xmppStream = [[XMPPStream alloc] init];
#if !TARGET_IPHONE_SIMULATOR
{
// Want xmpp to run in the background?
//
// P.S. - The simulator doesn't support backgrounding yet.
// When you try to set the associated property on the simulator, it simply fails.
// And when you background an app on the simulator,
// it just queues network traffic til the app is foregrounded again.
// We are patiently waiting for a fix from Apple.
// If you do enableBackgroundingOnSocket on the simulator,
// you will simply see an error message from the xmpp stack when it fails to set the property.
xmppStream.enableBackgroundingOnSocket = YES;
}
#endif
NSLog(#"setup stream");
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
[xmppStream setHostName:#"10.68.202.123"];
//[xmppStream setHostPort:8070];
allowSelfSignedCertificates = NO;
allowSSLHostNameMismatch = NO;
// You may need to alter these settings depending on the server you're connecting to
}
- (BOOL)connect
{
NSLog(#"connect");
if (![xmppStream isDisconnected]) {
return YES;
}
//
// If you don't want to use the Settings view to set the JID,
// uncomment the section below to hard code a JID and password.
//
// myJID = #"user#gmail.com/xmppframework";
// myPassword = #"";
if (self.loginDetail.emailId == nil || self.loginDetail.password == nil) {
return NO;
}
[xmppStream setMyJID:[XMPPJID jidWithString:[self.loginDetail.emailId stringByAppendingString:#"/pc"]]];
NSError *error = nil;
if (![xmppStream connect:&error])
{
NSLog(#"Error connecting: %#", error);
return NO;
}
return YES;
}
- (void)disconnect
{
[xmppStream disconnect];
}
- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
}
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
NSLog(#"some security thing");
if (allowSelfSignedCertificates)
{
[settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
}
if (allowSSLHostNameMismatch)
{
[settings setObject:[NSNull null] forKey:(NSString *)kCFStreamSSLPeerName];
}
else
{
// Google does things incorrectly (does not conform to RFC).
// Because so many people ask questions about this (assume xmpp framework is broken),
// I've explicitly added code that shows how other xmpp clients "do the right thing"
// when connecting to a google server (gmail, or google apps for domains).
NSString *expectedCertName = nil;
NSString *serverDomain = xmppStream.hostName;
NSString *virtualDomain = [xmppStream.myJID domain];
if ([serverDomain isEqualToString:#"talk.google.com"])
{
if ([virtualDomain isEqualToString:#"gmail.com"])
{
expectedCertName = virtualDomain;
}
else
{
expectedCertName = serverDomain;
}
}
else if (serverDomain == nil)
{
expectedCertName = virtualDomain;
}
else
{
expectedCertName = serverDomain;
}
if (expectedCertName)
{
[settings setObject:expectedCertName forKey:(NSString *)kCFStreamSSLPeerName];
}
}
}
- (void)xmppStreamDidSecure:(XMPPStream *)sender
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
}
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
NSLog(#"connected");
NSError *error = nil;
if (![[self xmppStream] authenticateWithPassword:self.loginDetail.password error:&error])
{
DDLogError(#"Error authenticating: %#", error);
}
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
NSLog(#"authenticated");
}
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
NSLog(#"did not authenticate");
}
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
return NO;
}
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
// A simple example of inbound message handling.
}
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
DDLogVerbose(#"%#: %# - %#", THIS_FILE, THIS_METHOD, [presence fromStr]);
}
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(id)error
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
}
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
NSLog(#"%#",error);
}
#end

Most probably the problem is that your class instance, that is a delegate for your XMPPStream is released before the delegate method is called on it. Make it more persistent by making this class a property or instance variable of other class or by using dispatch_once. For example,
Change
YourClass *instance = [[YourClass alloc] init];
instance.xmppStream = ....
by
#property(nonatomic, strong) YourClass *instance;
self.instance = [[YourClass alloc] init];
self.instance.xmppStream = ....
Here YourClass contains XMPPStream and is delegate for it.
I've written a big blog post on this problem. This is pretty common situation.
http://blog.alwawee.com/2013/07/31/on-xmppframework-delegate-method-not-being-called/

you can use the multicast,sth like this
[[self appDelegate].xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

I had same problem... this helped me. Hope it works for you, just make following Changes in your code.
Static XmppClass *selfRef = nil;
#implementation XmppClass
#synthesize xmppStream;
- (void)setupStream
{
// Setup xmpp stream
selfRef = [[XmppClass alloc] init];
xmppStream = [[XMPPStream alloc] init];
[xmppStream addDelegate:selfRef delegateQueue:dispatch_get_main_queue()];

Related

Delegate method is not called on dispatch_async

I have a framework where I have to send the captured image back to the callee. So I have to wait for the delegate to finish
I am using dispatch_asyc to wait for the async operation of the delegate method.
But the delegate method is not called and the application is stuck NSLog(#"dispatch_get_global_queue"); here.
I have added my code below. Please help
NoPreviewCamera.h
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface NoPreviewCamera : NSObject <AVCapturePhotoCaptureDelegate>
#end
static NoPreviewCamera *noPreviewCamera = nil;
static NSString *imageDataBase64 = nil;
static dispatch_group_t group = nil;
NS_ASSUME_NONNULL_END
NoPreviewCamera.m
#import "NoPreviewCamera.h"
AVCaptureSession *captureSession;
AVCapturePhotoOutput *photoOutput;
AVCapturePhotoSettings *photoSetting;
AVCaptureConnection *captureConnection;
id<AVCapturePhotoCaptureDelegate> avCaptureDelegate;
#interface NoPreviewCamera ()
#end
#implementation NoPreviewCamera
+ (void) initCaptureSession {
captureSession = [[AVCaptureSession alloc] init];
if([captureSession canSetSessionPreset: AVCaptureSessionPresetPhoto] ) {
[captureSession setSessionPreset:AVCaptureSessionPresetPhoto];
}
[captureSession startRunning];
}
+ (void) setNewPhotoSetting {
photoSetting = [AVCapturePhotoSettings photoSettingsWithFormat:#{AVVideoCodecKey : AVVideoCodecTypeJPEG}];
[photoOutput setPhotoSettingsForSceneMonitoring:photoSetting];
}
+ (void) initInputDevice: (AVCaptureDevice *) inputDevice {
AVCaptureDeviceInput *deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputDevice error:nil];
if ([captureSession canAddInput:deviceInput]) {
[captureSession addInput:deviceInput];
}
}
+ (void) initOuput {
photoOutput = [[AVCapturePhotoOutput alloc] init];
if ([captureSession canAddOutput:photoOutput]) {
[captureSession addOutput:photoOutput];
}
}
+ (AVCaptureDevice *) frontFacingCameraIfAvailable {
AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:#[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionFront];
NSArray *captureDevices = [captureDeviceDiscoverySession devices];
if (!captureDevices || !captureDevices[0]){
return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
} else {
return captureDevices[0];
}
}
+ (NSString *) takePictureWithNoPreviewFrontFacingCamera {
NSLog(#"NoPreviewCameraNoPreviewCameraNoPreviewCameraNoPreviewCamera");
noPreviewCamera = [[NoPreviewCamera alloc]init];
[NoPreviewCamera initCaptureSession];
[NoPreviewCamera initInputDevice:[NoPreviewCamera frontFacingCameraIfAvailable]];
[NoPreviewCamera initOuput];
[NoPreviewCamera setNewPhotoSetting];
captureConnection = nil;
for (AVCaptureConnection *connection in photoOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual: AVMediaTypeVideo]) {
captureConnection = connection;
break;
}
}
if (captureConnection) {
break;
}
}
[NoPreviewCamera getOutputPhoto:^(BOOL success) {
NSLog(#"FINNNNNENENEENENENEN %o", success );
}];
return imageDataBase64;
}
+ (void)getOutputPhoto:(void (^) (BOOL success))completion
{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
noPreviewCamera = [[NoPreviewCamera alloc]init];
[photoOutput capturePhotoWithSettings:photoSetting delegate:noPreviewCamera];
NSLog(#"dispatch_get_global_queue");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"dispatch_get_main_queue");
completion(true);
dispatch_group_leave(group);
});
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error
{
NSLog(#"image delegate");
NSData *imageData = [photo fileDataRepresentation];
imageDataBase64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSLog(#"image captured");
}
- (void)captureOutput:(AVCapturePhotoOutput *)output didCapturePhotoForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings {
NSLog(#"Finised");
}
#end
As I see you creating instance of NoPreviewCamera inside a static method of this class. And setting this instance as delegate. And this "instance" is static property as well. And after you expecting this to call instance methods.
Try to drop it to heap instead.
Make it an object with some reference to.

IOS GCDAsyncSocket didReadData not called when implement in NSObject class

Im implementing GCDAsyncSocket delegate in NSObject class, then call it in viewController.
When running it, i found 'didConnectToHost' and 'didWriteDataWithTag' is called, but i didn't see 'didReadData' called. Here is my code:
SocketUtils.h
#interface SocketUtils : NSObject <GCDAsyncSocketDelegate>
#property (nonatomic) void (^SocketCallback)(id data);
#property (nonatomic) NSString *message;
#property (nonatomic) UIViewController *vc;
+ (SocketUtils *) createSocket:(NSString *)message inViewController:(UIViewController *)vc;
- (void) connectToSocket;
#end
SocketUtils.m
#implementation SocketUtils{
GCDAsyncSocket *socket;
}
+ (SocketUtils *) createSocket:(NSString *)message inViewController:(UIViewController *)vc{
SocketUtils *this = [[self alloc] init];
this.message = message;
[this connectToSocket];
return this;
}
- (void)connectToSocket{
socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *err = nil;
if (![socket connectToHost:SOCKET_IP onPort:[SOCKET_PORT intValue] error:&err])
{
NSLog(#"err: %#", [err localizedDescription]);
if(err){
[Utils showErrorDialog:[err localizedDescription] atViewController:_vc];
}
return;
}
}
#pragma mark - Socket delegate
- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port
{
NSLog(#"Cool,didConnectToHost: %#", host);
[socket writeData:[_message dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
[socket readDataWithTimeout:-1 tag:0];
NSLog(#"didWriteDataWithTag: %ld", tag);
}
- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
NSLog(#"didReadData : %#", data);
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length])];
NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
_SocketCallback(msg);
}
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock{
NSLog(#"socketDidCloseReadStream");
}
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
NSLog(#"disconnected to socket: %#", err);
[Utils showErrorDialog:[err localizedDescription] atViewController:_vc];
}
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
NSLog(#"Accepted new socket from %#:%hu", [newSocket connectedHost], [newSocket connectedPort]);
}
Then in viewController i called:
socketUtils = [SocketUtils createSocket:#"GET_BOARD_CODE" inViewController:self];
socketUtils.SocketCallback = ^(id data) {
doSth();
};
The first thing that I predict is the destroying of this class, but i try to keep all #property = strong, but not work.
Can anyone help?
after many times to search, i realized that i forgot to append "\r\n" to message. Okey just change 1 line of code to:
this.message = [message stringByAppendingString:#"\r\n"];
And it work as expect.

Objective-C calling instance method within Singleton

I created a Singleton class named DataManager. I've set up some caching. However, when attempting to call the cache instance methods from within another instance method of DataManager, I'm getting EXC_BAD_ACCESS error on the line:
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
DataManager.h
#interface DataManager : NSObject {
FMDatabase *db;
NSMutableDictionary *cache;
}
+ (DataManager *)sharedInstance;
// ... more methods
- (id)getCacheForKey:(NSString *)key;
- (void)setCacheForKey:(NSString *)key withValue:(id)value;
- (void)clearCache;
DataManager.m
- (NSArray *)getStops:(NSInteger)archived {
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
if (stops != nil) {
NSLog(#"Stops: %#", stops);
return stops;
}
stops = [NSMutableArray array];
// set stops...
[[DataManager sharedInstance] setCacheForKey:#"stops" withValue:stops];
return stops;
}
It seems to occur when called from another view controller. That is the first view controller no error, second view controller, error.
This is my first attempt at a Singleton, so I'm sure I am making a simple mistake. But I am failing to see it myself.
Note: I've tried [self getCache...] with the same result.
UPDATE
Here is my Singleton implementation. Adapted from http://www.galloway.me.uk/tutorials/singleton-classes/
+ (DataManager *)sharedInstance {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
return instance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX;
}
- (oneway void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
if (db == nil){
BourbonAppDelegate *appDelegate = (BourbonAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate createEditableCopyOfDatabaseIfNeeded];
db = [[FMDatabase alloc] initWithPath:[appDelegate getDBPath]];
}
if (![db open]) {
NSAssert(0, #"Failed to open database.");
[db release];
return nil;
}
[db setTraceExecution:YES];
[db setLogsErrors:TRUE];
cache = [NSMutableDictionary dictionary];
NSLog(#"cache: %#", cache);
}
return self;
}
Your cache object is autoreleased, so it's no longer in memory when you try to access it.
Use [NSMutableDictionary alloc] init] instead of [NSMutableDictionary dictionary] to get an instance that is retained.
Not a direct answer to your question, which was already answered.
However I'd just like to point out that your singleton implementation is sub-optimal. #synchronized is very expensive, and you can avoid using it every time you access the singleton:
if (!instance) {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
}
An even better way to initialize a singleton would be:
+ (DataManager *)sharedInstance {
static DataManager *instance;
static dispatch_once_t donce;
dispatch_once(&donce, ^{
instance = [[self alloc] init];
});
return instance;
}

Facebook Connect Class with Singleton : Access token issue

I have created a singleton class called "LoginFacebook" which is meant to connect user to Facebook and to perform the different request. The problem is that I get an error about the access_token. Here it is :
3 : <CFString 0x4c552f0 [0xe50400]>{contents = "message"} = <CFString 0x4c55250 [0xe50400]>{contents = "An active access token must be used to query information about the current user."}
First I connect to Facebook by making the following request from another View Controller :
[[LoginFacebook loginFacebook] launchFacebook:self]
Then I make that second request from the same other View Controller but from another method :
[[LoginFacebook loginFacebook] requireName:self]
Here is my singleton class "LoginFacebook" :
LoginFacebook.h :
#import <UIKit/UIKit.h>
#import "LoginFacebook.h"
#interface FirstViewController : UIViewController {
}
-(IBAction)performConnect:(id)sender;
-(IBAction)performName:(id)sender;
#end
LoginFacebook.m :
#import "LoginFacebook.h"
static LoginFacebook *loginFacebook = nil;
#implementation LoginFacebook
#synthesize name;
#synthesize facebook;
-(void)launchFacebook:(id)sender
{
permissions = [[NSArray arrayWithObjects:
#"read_stream", #"publish_stream", #"offline_access",nil] retain];
Facebook* facebookbis = [[Facebook alloc] initWithAppId:#"168938499825684"];
facebook = facebookbis;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"]) {
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
if (![facebook isSessionValid]) {
[facebook authorize:nil delegate:self];
}
}
-(NSString *)requireName:(id)sender
{
NSLog(#"requireName asked");
[facebook requestWithGraphPath:#"me" andDelegate:self];
return name;
NSLog(#"%#",[facebook accessToken]);
}
+ (LoginFacebook *)loginFacebook
{
if (loginFacebook == nil) {
loginFacebook = [[super allocWithZone:NULL] init];
}
return loginFacebook;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self loginFacebook] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
// FBRequestDelegate
/**
* Called when the Facebook API request has returned a response. This callback
* gives you access to the raw response. It's called before
* (void)request:(FBRequest *)request didLoad:(id)result,
* which is passed the parsed response object.
*/
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response {
NSLog(#"received response");
}
/**
* Called when a request returns and its response has been parsed into
* an object. The resulting object may be a dictionary, an array, a string,
* or a number, depending on the format of the API response. If you need access
* to the raw response, use:
*
* (void)request:(FBRequest *)request
* didReceiveResponse:(NSURLResponse *)response
*/
- (void)request:(FBRequest *)request didLoad:(id)result {
if ([result isKindOfClass:[NSArray class]]) {
result = [result objectAtIndex:0];
}
name = [result objectForKey:#"name"];
NSLog(#"request didLoad");
};
/**
* Called when an error prevents the Facebook API request from completing
* successfully.
*/
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
name = [error localizedDescription];
NSLog(#"----request didFailWithError");
NSLog(#"%#", [error localizedDescription]);
NSLog(#"%#", [error description]);
};
////////////////////////////////////////////////////////////////////////////////
// FBDialogDelegate
/**
* Called when a UIServer Dialog successfully return.
*/
- (void)dialogDidComplete:(FBDialog *)dialog {
name = #"publish successfully";
}
#end
Please also note that I added the following method (with the corresponding FacebookLogin *facebook in the .h) to my App Delegate :
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
return [facebook handleOpenURL:url];
}
Does one of you know what is going wrong there ? I have been struggling with the code for 2 days now...
From the error message it seems your access token isn't valid anymore or you didn't even have an access token yet. Does your app actually open a web browser the first time a user is trying to access Facebook? If not, then you probably failed to configure the project properly.
Perhaps it'd be a good idea to share the code of my Facebook singleton - I believe the code is pretty clean and easy to understand & expand. Since my needs are currently very modest I only have a method to authorize (login) and another method to post to wall. I'm using a stack so I can perform some operations in the correct order (for example login before posting message to wall, if user isn't logged in yet).
SDFacebookController.h
#import <Foundation/Foundation.h>
#import "FBConnect.h"
#interface SDFacebookController : NSObject
<FBSessionDelegate,
FBRequestDelegate>
#property (nonatomic, retain) Facebook *facebook;
+ (SDFacebookController *)sharedController;
- (void)authorize;
- (void)postMessageToWall:(NSString *)message;
#end
SDFacebookController.m
#import "SDFacebookController.h"
#import "Constants+Macros.h"
#import "SDOperationStack.h"
#interface SDFacebookController ()
#property (nonatomic, retain) SDOperationStack *operationStack;
- (void)performAuthorization;
- (void)performPostMessageToWall:(NSString *)message;
- (void)runOperation;
#end
#implementation SDFacebookController
#synthesize facebook, operationStack;
#pragma mark - Instance methods
- (void)authorize
{
NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(performAuthorization) object:nil] autorelease];
[operationStack push:operation];
[self runOperation];
}
- (void)postMessageToWall:(NSString *)message
{
NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(performPostMessageToWall:) object:message] autorelease];
[operationStack push:operation];
if (![facebook isSessionValid])
{
NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(performAuthorization) object:nil] autorelease];
[operationStack push:operation];
}
[self runOperation];
}
#pragma mark - Private methods
- (void)runOperation
{
NSOperation *operation = [operationStack pop];
[[NSOperationQueue currentQueue] addOperation:operation];
}
- (void)performAuthorization
{
if (![facebook isSessionValid])
{
NSArray *permissions = [NSArray arrayWithObject:#"publish_stream"];
[facebook authorize:permissions delegate:self];
}
}
- (void)performPostMessageToWall:(NSString *)message
{
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:message, #"message", nil];
[facebook requestWithGraphPath:#"me/feed" andParams:params andHttpMethod:#"POST" andDelegate:self];
}
#pragma mark - FBRequestDelegate
/**
* Called just before the request is sent to the server.
*/
- (void)requestLoading:(FBRequest *)request
{
DLog(#"%#", request);
}
/**
* Called when the server responds and begins to send back data.
*/
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response
{
DLog(#"%# %#", request, response);
}
/**
* Called when an error prevents the request from completing successfully.
*/
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error
{
DLog(#"%# %#", request, error);
[[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Error", nil)
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil]
autorelease] show];
[operationStack empty];
}
/**
* Called when a request returns and its response has been parsed into
* an object.
*
* The resulting object may be a dictionary, an array, a string, or a number,
* depending on thee format of the API response.
*/
- (void)request:(FBRequest *)request didLoad:(id)result
{
DLog(#"%# %#", request, result);
if ([operationStack isEmpty] == NO)
[self runOperation];
else if ([operationStack.lastOperation.invocation selector] == #selector(performPostMessageToWall:))
[[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"MessagePosted", nil)
message:NSLocalizedString(#"Successfully posted message on Facebook.", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil]
autorelease] show];
}
/**
* Called when a request returns a response.
*
* The result object is the raw response from the server of type NSData
*/
- (void)request:(FBRequest *)request didLoadRawResponse:(NSData *)data
{
DLog(#"%# %#", request, data);
}
#pragma mark - FBSessionDelegate
/**
* Called when the user successfully logged in.
*/
- (void)fbDidLogin
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
/**
* Called when the user dismissed the dialog without logging in.
*/
- (void)fbDidNotLogin:(BOOL)cancelled
{
}
/**
* Called when the user logged out.
*/
- (void)fbDidLogout
{
}
#pragma mark - Memory management
- (id)init
{
self = [super init];
if (self)
{
facebook = [[Facebook alloc] initWithAppId:kFacebookAppIdentifier];
operationStack = [[SDOperationStack alloc] init];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"] && [defaults objectForKey:#"FBExpirationDateKey"])
{
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
}
return self;
}
- (void)dealloc
{
[operationStack release];
[facebook release];
[super dealloc];
}
#pragma mark - Singleton
+ (SDFacebookController *)sharedController
{
static SDFacebookController *controller = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
controller = [[self alloc] init];
});
return controller;
}
#end
SDOperationStack.h
#import <Foundation/Foundation.h>
#interface SDOperationStack : NSObject
#property (nonatomic, retain) NSInvocationOperation *lastOperation;
- (void)push:(NSOperation *)operation;
- (NSOperation *)pop;
- (BOOL)isEmpty;
- (void)empty;
#end
SDOperationStack.m
#import "SDOperationStack.h"
#interface SDOperationStack ()
#property (nonatomic, retain) NSMutableArray *array;
#end
#implementation SDOperationStack
#synthesize array, lastOperation;
- (void)dealloc
{
[lastOperation release];
[array release];
[super dealloc];
}
- (id)init
{
self = [super init];
if (self)
{
array = [[NSMutableArray alloc] init];
}
return self;
}
- (void)push:(NSInvocationOperation *)operation
{
[array addObject:operation];
}
- (NSInvocationOperation *)pop
{
if ([self isEmpty])
return nil;
self.lastOperation = (NSInvocationOperation *)[array lastObject];
[array removeLastObject];
return lastOperation;
}
- (BOOL)isEmpty
{
return [array count] == 0;
}
- (void)empty
{
[array removeAllObjects];
}
#end

The document “Untitled” could not be saved as "Untitled"

I am trying to develop a document based mac app using this Apple walkthrough and I am having issues saving the file (the final step).  The error that I am getting after I try to save a file is: The document "Untitled" could not be saved as "- the new filename is I'm trying to use -"
I've googled around and not found any results for this error.  I've rechecked the code and everything seems pretty solid to the tutorial.  I wondered if anybody has any intuition as to what might be going wrong here.
The code of my main class is:
#import "MyDocument.h"
#implementation MyDocument
- (id)init
{
self = [super init];
if (self) {
if (mString == nil) {
mString = [[NSAttributedString alloc] initWithString:#""];
}
}
return self;
}
- (NSAttributedString *) string { return [[mString retain] autorelease]; }
- (void) setString: (NSAttributedString *) newValue {
if (mString != newValue) {
if (mString) [mString release];
mString = [newValue copy];
}
}
- (void) textDidChange: (NSNotification *)notification {
[self setString: [textView textStorage]];
}
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return #"MyDocument";
}
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
if ([self string] != nil) {
[[textView textStorage] setAttributedString: [self string]];
}
}
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
BOOL readSuccess = NO;
NSAttributedString *fileContents = [[NSAttributedString alloc]
initWithData:data options:NULL documentAttributes:NULL
error:outError];
if (fileContents) {
readSuccess = YES;
[self setString:fileContents];
[fileContents release];
}
return readSuccess;
}
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
NSData *data;
[self setString:[textView textStorage]];
NSMutableDictionary *dict = [NSDictionary dictionaryWithObject:NSRTFTextDocumentType
forKey:NSDocumentTypeDocumentAttribute];
[textView breakUndoCoalescing];
data = [[self string] dataFromRange:NSMakeRange(0, [[self string] length])
documentAttributes:dict error:outError];
return data;
}
Header file:
#import <Cocoa/Cocoa.h>
#interface MyDocument : NSDocument
{
IBOutlet NSTextView *textView;
NSAttributedString *mString;
}
- (NSAttributedString *)string;
- (void) setString: (NSAttributedString *)value;
#end
In your -dataOfType:error: method, when you assign something to data, are you sure it's not nil? Returning nil will cause this error.
I rebuilt the project from scratch with one exception: I didn't follow the step of dragging my MyDocument class to the File's Owner. The tutorial was written for a previous version of XCode even though it says it's for 3.2 (or maybe that much has happened in that version and now), but that step is unnecessary.