I have a problem with the Dropbox API. I'm working on big app, which was started by another developer. Now I'm going to clean the code. All the Delegate Methods (loadedMetadata & Co.) were directly in the view. Now I want to extract them into their own class. So I created a DropboxService class with all the methods in it. So I have the view and call the method loadMetadata from the DropboxService. The method is called and woking fine. But the Delegate method loadedMetadata is never called.
What did I do wrong / what do I have to change to get this working correctly?
The Dropbox Service has the DBRestClientDelegate as a "Superclass" (don't know how it i exactly called)
#interface DropboxService : CloudProviderService <DBRestClientDelegate> {
}
Edit:
The Service is instantiated in the AppDelegate and is a variable there:
- (DropboxService *)getDropboxService {
if (self.dropboxService == nil) {
self.dropboxService = [[DropboxService alloc] init];
}
return self.dropboxService;
}
Greetings from Germany
Alexander
You need to also set the delegate after init
DBRestClient.delegate = self;
from this interface
#interface DBRestClient : NSObject { ... id<DBRestClientDelegate> delegate;
That header doesnt say that DropboxService is a sub-class of DBRestClientDelegate
it says that DropboxService conforms to the protocol of DBRestClientDelegate
The Dropbox sample project explains all this, but you want to look for where you set up DBRestClient and make sure DropboxService is made the delegate of that instance.
This is what it looks like in my code which conforms to DBRestClientDelegate
- (DBRestClient*)restClient {
if (restClient == nil) {
restClient = [((DBRestClient *)[DBRestClient alloc]) initWithSession:[DBSession sharedSession]];
restClient.delegate = self;
}
return restClient;
}
Related
I am pretty new to iOS and Swift and currently I'm facing a problem with writing a Unit Test. I have a class (let's suppose it is called A) which has (readonly property from Objective-C) and in my test I want to have object of this class to pass to the method which later does something with it. Oh, I don't have any initializers as well... My question is, how to test such think? Maybe I have to mock such object somehow?
------EDIT-----
Okay. My post wasn't quite precise. Okay, so I know only basics of Swift (unfortunately I don't have time now to learn Objective - C as I was asked to write sth in Swift). I have a class provided by some company in which I have a class (written in Objective-C) like:
#interface MainClassX : NSObject
#property (nonatomic, strong, readonly) NSString* code;
#property (nonatomic, strong, readonly) NSArray<XYZ*>* classification;
#end
And in my test want to create an object of this class and init at least 'code' property... but the setter is private so I can't do any 'inheritance trick'...? Is there any option to do it or should I do it another way? The problem is that I want to test a method which takes array of such objects and does sth with them.
It's pretty tricky because they wanted those properties to be readonly, why do you want to test them?
Regardless the purpose, you can do these steps:
1. Look into adding methods to that class using Category (in Objective C), or extension(in Swift).
2. Implement that new init method, set the code properpty using Key-Value Programming
I have managed to do it real quick in Objective C, it's pretty straight forward to convert to Swift.
#implementation MainClassX(Test)
-(instancetype)initWithCode:(NSString *)code {
self = [self init];
if (self) {
[self setValue:code forKey:#"code"];
}
return self;
}
#end
Test it:
MainClassX *test = [[MainClassX alloc] initWithCode:#"TEST"];
NSLog(#"code: %#", test.code); // Should print out "TEST" in the console
Swift:
extension MainClassX {
convenience init(_ code: String) {
self.init()
self.setValue(code, forKey: "code")
}
}
In the unit test:
import XCTest
#testable import YourAppModule
class YourAppModuleTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let cls = MainClassX("TEST")
XCTAssert(cls.code == "TEST")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
You are likely looking for dependancy injection. This is a way that you can initialize a class with an optional value that can set values for testing, as you want to do.
The following is a simple example.
Create an optional initialize for your Objective-C class:
- (instancetype)initWithOption:(NSString *)option {
self = [super init];
if (self) {
self.option = option;
}
return self;
}
You can have it such that when you normally call the class, you call its default init. But for testing, initialize it with this function. Another thing to consider if you may want to have a protected header file (such as classname_protected.h) which you only use in your unit tests so that you do not expose this function to your application.
Without seeing more of your test, it is a bit difficult to add on to this, but DI is likely where you need to go for it.
I attended to a watchkit hackathon yesterday and I had some problems regarding calling a method on an NSObject class which uses the Google Maps API and send local notifications. If I call this method from my Watchkit extension, the code doesn't compile, but If I call from the ViewController, for example, everything works perfectly
#import "InterfaceController.h"
#import "Methods.h"
#interface InterfaceController()
#end
#implementation InterfaceController
- (instancetype)initWithContext:(id)context {
self = [super initWithContext:context];
if (self){
// Initialize variables here.
// Configure interface objects here.
NSLog(#"%# initWithContext", self);
}
return self;
}
- (IBAction)butRoute
{
Methods *mt = [[Methods alloc]init];
[mt notif:#"ARRIVING!"];
//***** If I call this method, my code won't compile!!! *****
}
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
NSLog(#"%# will activate", self);
}
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
NSLog(#"%# did deactivate", self);
}
#end
The error I get is:
Check the target for your Methods class and make sure it is in your watch kit extensions target.
Alternately, look at building a framework for your shared classes. https://developer.apple.com/videos/wwdc/2014/?id=416
I don't know what xcode version you are using but take into account that the initWithContext method is no longer valid. You should be using:
- (void)awakeWithContext:(id)context
And you shouldn't be overwriting it, just use it.
just remove #import line and replace it with WatchKit framework.
I am having some difficulty with key-value-observing logic in conjunction with XCTest (the original code is being retrofitted with test coverage). The logic works fine in the normal (non-test) context, but blows out with an exception every time in the context of a test.
The gist of the logic is this -- I have two classes, call them Service and Helper. Scaffold implementations are:
interface Service : NSObject {
BOOL svcCallComplete;
}
#end
#implementation Service
- (id) init {
if ((self=[super init])==nil) {
return nil;
}
return self;
}
#end
interface Helper : NSObject {
}
#end
#implementation Helper
- (id) init {
if ((self=[super init])==nil) {
return nil;
}
return self;
}
#end
Helper is an observer of an attribute in Service. In the context of my normal runtime logic I do this with a call to a Service instance method addSvcObserver:
Service.m:
- (void) addSvcObserver:(id)observer {
[self addObserver:observer
forKeyPath:#"svcCallComplete"
options:NSKeyValueObservingOptionNew
context:nil];
}
Helper complies with the KVO observing pattern thus:
Helper.m:
- (void) observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
}
Pretty straight-forward, and I won't go into the logic for monitoring the attribute change as the problem occurs way before that -- if I have a code excerpt like:
Service* service = [[Service alloc] init];
Helper* helper = [[Helper alloc] init];
[service addSvcObserver:helper];
there are no problems in the non-test case (i.e., this and associated KVO logic works as expected). However the addSvcObserver call when performed in the context of an XCTest test method produces an immediate access denied exception.
I've included an exception "break on all" breakpoint -- the problem seems to be occurring in objc_registerClassPair during the addObserver:forKeyPath:options:context: call. The test target has ARC explicitly disabled as the project for which it is providing test coverage (the target is iOS7) is non-ARC for legacy reasons; this does not seem to cause any problems with other tests.
Thoughts?
interface Service : NSObject {
}
#property (nonatomic) BOOL svcCallComplete;
You should declare svcCallComplete as a property.
because The observed class must be key-value observing compliant for the property that you wish to observe
The reason you get the objc_registerClassPair i think maybe because KVO dynamic register a subclass of your Service , but could not find a setter method of svcCallComplete.which the dynamic subclass needs to override that setter method and send notification.
For more detail read this.
The cause of this turned out to be that my implementation of the KVO logic was incomplete. According to the guide here, you must override the NSObject implementation of automaticallyNotifiesObserversForKey: when using manual change notification -- I somehow missed that in my initial read of the text. I added the following to my Service class:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:#"svcCallComplete"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
and all is now correct in the test case. Anyone care to hazard a guess as to why this was not blowing out in the normal (non-test) case?
I have the following code to start my own Cocoa HTTP Server. In order to manually handle the responses I have created my custom HTTPConnection class named PictureHTTPConnection
self.httpServer = [[HTTPServer alloc]init];
self.httpServer.type = #"_http._tcp.";
self.httpServer.name = #"MyBonjour Name";
self.httpServer.connectionClass = [PictureHTTPConnection class];
The problem here is that the PictureHTTPConnection class needs some information in order to handle the HTTP connections. However, I only provide the class and therefore I don't have a reference to the instance. I could do something with global data but this is not very good programming practise.
The best way I can think of is setting the PictureHTTPConnection's delegate to the UIApplicationDelegate so that it can answer any callbacks. :-(
I had the same exact problem. To solve this, I changed the code of the HTTPServer class so that you can set it a delegate that will in turn be set on each HTTPConnection created. When the HTTPConnection has to handle the request I call the delegate. This is far from clean as well and I was seeking for a better solution.
You can get callbacks by adding a category to HTTPServer that gets & sets a delegate. objc_setAssociatedObject is your friend here:
#interface HTTPServer (WithDelegate)
#property (strong, nonatomic) id delegate;
#end
#implementation HTTPServer (WithDelegate)
NSString const *key = #"HttpServer.WithDelegate.key";
- (void)setDelegate:(id)delegate
{
objc_setAssociatedObject(self, &key, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)delegate
{
return objc_getAssociatedObject(self, &key);
}
#end
Set the delegate when you initialize the HTTPServer instance (or whenever). Call methods on it from your custom HTTPConnection implementation like this:
[config.server.delegate handleRequest:theRequest];
You must implement handleRequest:(Whatever*)theRequest in your delegate class
I have a problem with the init() method of a standard NSObject. I wrote a class (EFAPersistence) which is a subclass of NSObject. EFAPersistance has a attribute called efaDatabase.
EFAPersistence.h
#interface EFAPersistence : NSObject {
FMDatabase * efaDatabase;
}
#property (assign) FMDatabase * efaDatabase;
Everytime an instance of EFAPersistance is created I want to assign efaDatabase a value from my AppDelegate.
EFAPersistence.m
#implementation EFAPersistence
#synthesize efaDatabase;
- (id)init {
if (self = [super init]) {
efaDatabase = [[NSApp delegate] efaDatabase];
}
return self;
}
#end
This way of assigning does not work. But it works if I put the code in a normal method. So I am sure that efaDatabase is correctly instantiated in my AppDelegate. It's just not working in my init() method. That's why I have the feeling that NSApp is not working inside the init() method.
That's how the important AppDelegate code looks like.
AppDelegate.h
#interface AppDelegate : NSObject <NSApplicationDelegate> {
FMDatabase * efaDatabase;
}
AppDelegate.m
- (id)init {
if (self = [super init]) {
NSString * databasePath =
[[NSBundle mainBundle] pathForResource:#"efa" ofType:#"sqlite"];
self.efaDatabase = [FMDatabase databaseWithPath:databasePath];
if (![efaDatabase open]) {
NSLog(#"Couldn't open database: %#", databasePath);
// TODO: Create a database here
}
self.db = [[EFAPersistence alloc] init];
}
return self;
}
As you can see I am calling the init method. I also affirmed this by using NSLog(). init() is called. The attribute I am trying to assign in EFAPersistence is also created before init() is called.
To sum everything up:
How can I make this work within the init() method so I do not have to write boiler plate code in all my methods of EFAPersistence?
It looks to me that your AppDelegate is unset when you try to create the EFAPersistance object the first time. This is on below line in AppDelegate.m
self.db = [[EFAPersistence alloc] init];
I imagine the app delegate is set after the init is done (returned).
This way of assigning does not work. But it works if I put the code in a normal method. So I am sure that efaDatabase is correctly instantiated in my AppDelegate. It's just not working in my init() method. That's why I have the feeling that NSApp is not working inside the init() method.
NSApp works fine.
Quoting epatel:
I imagine the app delegate is set after the init is done (returned).
Correct. The nib loader completely instantiates each object (including the app delegate, if it's in a nib), then sets it as the value of any properties it's connected to. These are two separate operations; it will not set a not-yet-initialized object as the application delegate.
Quoting you (Jens) again:
The question is how to assign efaDatabase in EFAPersistences only once . There are other methods like awakeFromNib and viewDidLoad etc. But those are not available in a plain NSObject subclass.
Incorrect. awakeFromNib is sent to every object in a nib after the object has been initialized.
That said, I'm curious as to why you have EFAPersistence in a nib. From its name, it doesn't sound interface-related. Shouldn't the app delegate own the EFAPersistence, and the EFAPersistence own the database directly?