Protocols with optional properties? - objective-c

The short version is that I have a protocol which has an optional parameter. When I build a class that implements it with the iPhone SDK 3.x it compiles just fine, with no errors or warnings. When I used the 2.x SDK I get the following warning:
Class.m:68: warning: property 'field' requires method '-field' to be defined - use #synthesize, #dynamic or provide a method implementation
It works just fine in both cases.
So two questions:
What is the right way to fix the warning? I added #dynamic to the implementation which isn't really correct as the property really isn't there.
Why does work in SDK 3.x but not 2.x? The docs say "On Mac OS X v10.5, protocols may not include optional declared properties." Clearly that's not exactly the case here.
Here's a quick sample of the kind of code I have to make things a little more obvious if I wasn't completely clear.
#protocol MyProtocol
#required
- (void) method:(NSString*)param;
#optional
#property (nonatomic,retain) NSString* field;
#end
#interface MyClass : NSObject<MyProtocol> {
}
- (void) method:(NSString*)param;
#end

The iPhone SDK is not exactly identical to any paricular version of Mac OS X. Clearly a newer version of the toolset is included with SDK 3 that's more similar to the one from Snow Leopard.

The simnple way to remove the warning is to add
#dynamic field;
to your implementation. That tells the compiler that you will provide the implementation dynamically, which you wont, becuase its an optional property, but that should shut the compiler up.

The #optional was introduced in Objective-C 2.0 so it won't be applicable for older versions of the SDK. Your best bet is to determine whether it should be present (probably not) and then #ifdef that around with
#if __OBJC2__
#optional
#property ...
#endif
Then it should only compile under an OBJC2, and it won't be present in the older systems as part of the protocol itself.

Related

How to "audit" imported Objective-C methods/functions for optionals, like Apple did? [duplicate]

According to the Xcode release notes, Apple has been "auditing" their existing APIs to remove implicitly unwrapped optionals. That means that instead of T!, their APIs will return T or T? where appropriate.
Where do they do this? How can I annotate/wrap my existing Objective-C code (especially libraries) to make it cleaner to use from Swift?
Xcode 6.3/Swift 1.2
Xcode 6.3 added official support for annotating nullability in Objective-C.
Nullability
The nullability of a value can be declared by annotating the type with the keywords __nullable, __nonnull and __null_unspecified (the default). In properties and methods the keywords are nullable, nonnull and null_unspecified.
Examples from the Xcode release notes
- (void)registerNib:(nonnull UINib *)nib
forCellReuseIdentifier:(nonnull NSString *)identifier;
- (nullable UITableViewCell *)cellForRowAtIndexPath:(nonnull NSIndexPath)indexPath;
#property (nonatomic, readwrite, retain, nullable) UIView *backgroundView;
Changing the default
null_unspecified (which translates to T!) is the default for all existing code. A useful feature is the ability to change the default for sections of your API.
NS_ASSUME_NONNULL_BEGIN
// nonnull is the default here
NS_ASSUME_NONNULL_END
This removes a lot of noise, since methods that accept and handle nil usually are the exception, not the rule. Personally, I would use this for all audited APIs.
null_resettable
null_resettable is an additional annotation that is used for the uncommon case where you can set a property to nil, but it will never be nil (because it resets to a default value).
#property (nonatomic, retain, null_resettable) UIColor *tintColor;
Personally, I would avoid this behavior for new code. The hybrid nature of such properties isn't a good fit for Swift.
Xcode 7/Swift 2 (Beta)
Xcode 7 adds support for annotating generic types in Objective-C.
Generic type annotations for collections
NSArray, NSSet and NSDictionary (which are automatically bridged to Swift's Array, Set and Dictionary can be annotated with the type of their contents.
#property NSArray<NSString *> *stringArray;
#property NSSet<NSString *> *stringSet;
#property NSDictionary<NSString *, NSString *> *stringDict;
There's also the __kindof keyword that tells the Objective-C compiler to be less strict and allow downcasting. But it doesn't affect the Swift side.
Generic type annotations for custom classes
#interface MyArray1<__covariant T> : NSObject
- (void)addObject:(T)object;
#end
#interface MyArray2<__covariant T : NSObject *> : NSObject
- (void)addObject:(T)object;
#end
#interface MyArray3<__covariant T : id<NSCopying>> : NSObject
- (void)addObject:(T)object;
#end
The #implementation doesn't know about T and uses id/NSObject */id<NSCopying> as it always did.
I believe there's a way to do this at the moment. It looks like the information is being compiled to
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator
in files like
UIKit.apinotesc
UIKit.swiftdoc
UIKit.swiftmodule
The *.swiftmodule classes are generated by Xcode as part of the swift build but the additional information may be in the .apinotesc file, which there doesn't appear to be a documented form of generating it at the moment.
However, the swift command has an -apinotes option, which is used to generate this. You can see from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/UIKit.apinotesc that you can parse this with:
xcrun swift -apinotes -binary-to-yaml -o=- /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/UIKit.apinotesc
This generates a YAML file looking like:
Classes:
- Name: NSFileProviderExtension
Availability: available
AvailabilityMsg: ''
Methods:
- Selector: 'URLForItemWithPersistentIdentifier:'
MethodKind: Instance
Nullability: [ N ]
NullabilityOfRet: U
Availability: available
AvailabilityMsg: ''
If I were a betting man (and I'm not), I'd say that the MethodKind determines whether it's an Instance or a Class and that the Nullability is being used by Swift to determine whether it's an optional or not. I would suspect:
U - (implicitly) unwrapped optional
O - optional
N - non-optional?
These are wild guesses though.

How to Conditionally Use JSExport Protocol in 10.7+ apps?

I am writing an app that has a minimum deployment target of 10.7. As such, whenever I try to include a protocol that conforms to JSExport, the compiler says that it cannot find the protocol declaration for 'JSExport'.
If I set the minimum deployment target up to 10.9 it compiles just fine. Here is an example of noncompiling code.
#import <Cocoa/Cocoa.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import <WebKit/WebKit.h>
#protocol MyJSExport <JSExport>
-(void)doSomething;
#end
I've tried dynamically creating MyJSExport protocol at runtime by following the example set here, but their conclusion was that it didn't work.
So how do you write a protocol that conforms to JSExport while maintaining a 10.7 minimum deployment target?
The JSExport protocol causes Javascript getter/setters to be defined for properties in your protocol and Javascript wrapper functions to be defined for methods defined in your protocol.
It didn't exist before 10.9.
The answer depends on what behaviour are you expecting for targets less than 10.9
Assuming you just expect to silently ignore this functionality in older targets, then inspecting <JSExport.h> provides a clue on how you might do this - by inserting your own empty protocol for JSExport (just as <JSExport.h> does) if it is not already defined (as shown below), gives you code that doesn't need to change when you compile for 10.7 vs 10.9
But: inspecting the JavaScriptCore headers, I would expect that the rest of the Objective-C Javascript bridge functionality introduced with 10.9 will be disabled until you compile for a target of 10.9 - are you using JSContext or JSValue?
#import <Cocoa/Cocoa.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import <WebKit/WebKit.h>
// Check if JSExport functionality is available
#if !JSC_OBJC_API_ENABLED
#protocol JSExport
#end
#endif
#protocol MyJSExport <JSExport>
-(void)doSomething;
#end

Compile Warnings About Missing #required Protocol Methods NOT Appearing

I was tinkering with XCode 4.5.2 this morning and wanting to make a table view I naturally added the UITableViewDataSource and UITableViewDelegate protocols to one on my view controller definitions.
#import <UIKit/UIKit.h>
#interface MyViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#end
#import "MyViewController.h"
#implementation MyViewController
#end
Now I know at this stage I need to implement the #required methods of the UITableViewDataSource protocol but out of (presumably TDD) habit I decided to perform a build first with the expectation that the compiler would throw up warnings about the unimplemented #required methods (indicating to me which ones to implement).
But NO the build completed without a single error or warning from the compiler which has confused and concerned me slightly.
So I realize this question is normally the other way round i.e. 'why I am getting this warning', instead of 'why am I NOT getting this warning' but this really simple issue has really baffled me. Clearly I'm doing something wrong but I've no idea what. Any ideas how this might be possible?
FYI this a new project with no build/project setting customisation on a brand new clean install of XCode 4.5.2 on a new Mac Book.
In XCode 4.5.x there is a option for setting different warnings to display (YES/NO). You can check it under build setting Compiler Warnings for LLVM 4.1 compiler.
I hope it'll resolve your issue.

Under what conditions is #synthesize automatic in Objective-c?

Under what conditions is #synthesize automatic in Objective-c?
Perhaps when using LLVM 3.0 and up? From reading around the net it seems like #synthesize is unnecessary starting with Xcode 4. However I'm using Xcode 4 and receiving warnings when I don't #synthesize a property.
Some of the responses to Why don't properties get automatically synthesized seem to imply #synthesize can be omitted at some point under some circumstances.
Another (old) reference hinting that #synthesize might be automatic at some point in the future.
As of clang 3.2 (circa February 2012), "default synthesis" (or "auto property synthesis") of Objective-C properties is provided by default. It's essentially as described in the blog post you originally read: http://www.mcubedsw.com/blog/index.php/site/comments/new_objective-c_features/ (except that that post describes the feature as "enabled, then disabled"; I don't know if that's an issue with Xcode or if the clang developers themselves have gone back and forth on the question).
As far as I know, the only case in which properties will not be default-synthesized in clang 3.2 is when those properties have been inherited from a protocol. Here's an example:
#import <Foundation/Foundation.h>
#protocol P
#property int finicky;
#end
#interface A : NSObject <P>
#property int easygoing;
#end
#implementation A
#end
int main() { A *a = [A new]; a.easygoing = 0; a.finicky = 1; }
If you compile this example, you'll get a warning:
test.m:11:17: warning: auto property synthesis will not synthesize property
declared in a protocol [-Wobjc-protocol-property-synthesis]
#implementation A
^
test.m:4:15: note: property declared here
#property int finicky;
^
1 warning generated.
and if you run it, you'll get an error from the runtime:
objc[45820]: A: Does not recognize selector forward:: (while forwarding setFinicky:)
Illegal instruction: 4
As of Xcode 4.4, if you don't write #synthesize or #dynamic for a property. the compiler acts as though you had written #synthesize property = _property.
Prior to Xcode 4.4, you must do one of the following things for each property or else the compiler will issue a warning and you will get a runtime error. In Xcode 4.4 or later, you may do any of the following things instead of letting the compiler automatically synthesize the property accessors and instance variable.
Use the #synthesize directive.
Use the #dynamic directive and somehow provide the property getter and (if necessary) setter at runtime.
Explicitly write the property getter method and, if the property is readwrite, the property setter method.
Note that you can use the #synthesize directive (or the #dynamic directive) and also explicitly provide the getter and/or setter methods. But #synthesize provides them if you omit them.
From the New Features in Xcode 4.4 document:
Objective-C #properties are synthesized by default when not explicitly implemented.
So #synthesize is automatic by default starting from Xcode 4.4 with the LLVM 4.0 Compiler.
Also, synthesize will not be automatic if you have implemented the setter AND getter manually. So if you wonder why you can't access _someVariable, having declared #property (...) SomeType someVariable, then it is because you have implemented the setSomeVariable: and someVariable methods.
You can turn off the synthesize warnings by clicking on the project name in the Project Navigator on the left then click All Cobined in Build Settings and then search for synthesize. That should be set to No.

Universal App (iPad+iPhone) targeted to older (iOS < 4) devices - conditionally adopt a protocol?

I'm writing an Universal App that will run natively on both iPad and iPhone. I also need it to be targeted to older devices (those that cannot run 4.0) so 3.1 is a must.
I have already set up the Base SDK to the latest available version (4.2), and the Deployment Target to 3.1. I am making lots of runtime checks in order to call the corresponding methods only on the right device/version.
One of the things I am making use of in the iPad is an UISplitViewController. When assigning the splitViewController delegate, the compiler throws a warning because the class interface isn't explicitly adopting the UISplitViewControllerDelegate protocol and I'm afraid that if I declare it to make it so, the App will crash on older devices where there is no UISplitViewController/UISplitViewControllerDelegate.
What is the best way to supress the compiler warning? Should I declare an 'empty' UISplitViewControllerDelegate? If so, can I make it conditionally at runtime? Or should I just make the corresponding class interface conform to the protocol and not worry about older devices?
Best,
You can suppress the warning with a simple C cast:
foo.delegate = (id<UISplitViewControllerDelegate>)self;
I haven't tried this, but I'm pretty sure you can just go ahead and adopt the protocol unconditionally, even if the class may be used on an older runtime that does not have the protocol. And here's why:
All the information for defining a protocol is contained in the .h file that declares the #protocol. When you adopt a protocol, that protocol declaration is imported at some point in your .h file (presumably by #import <UIKit/UIKit.h>).
When the runtime needs to know something about a protocol, it references a "Protocol Object", which you would normally reference in source code by doing #protocol(MyProtocolName). And the compiler creates this protocol object (at compile time) when such a protocol reference is encountered, according to the section titled Protocol Objects in The Objective-C Programming Language documentation.
So knowing all that, if you adopt a protocol and you write any code that references the protocol object, that protocol object will be created by the compiler. Even if you're running on an older device, the compiler should've created that protocol object for you, so I don't think it'll cause a crash.
Hope that makes sense. If I have some time I can try this out to see if it holds in practice because I have devices running a range of iOS versions from 3.1 to 4.2.
Same problem if you want to use printing and stil have the app runnig before 4.2. Basically it works with this
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40200
// code for iOS 4.2++
#interface PersonDetailViewController : UITableViewController <EditViewControllerDelegate, EditPickerViewControllerDelegate, UITextFieldDelegate, UIActionSheetDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIPrintInteractionControllerDelegate>{
#else
// code for iOS til 4.1
#interface PersonDetailViewController : UITableViewController <EditViewControllerDelegate, EditPickerViewControllerDelegate, UITextFieldDelegate, UIActionSheetDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>{
#endif
But... It looks like the InterfaceBuilder can't handle this. All outlets defines after this conditional definition ar gone in IB.
So any better solution?
Regards
Gerd