Access instance properties of an Objective-C NSDocument subclass from Swift plugin? - objective-c

The goal is to write a Swift plugin which can access public instance properties of an Objective-C NSDocument subclass. The main application is in Objective-C. The plugin is intended to be just Swift.
The main application has a public Objective-C document interface:
#interface ObjcNSDocument : NSDocument
#property (nonatomic, readwrite) NSString *markdown;
#property (nonatomic, readonly) NSString *html;
#end
A reasonable Swift counterpart would seem to be:
public class SwiftNSDocument: NSDocument {
public var markdown: String = ""
public var html: String = ""
}
The following Swift code retrieves the current NSDocument OK. However, the casting fails.
public func doFilter() {
let dc = NSDocumentController.shared()
if let document: NSDocument = dc.currentDocument {
print("document.displayName = \(document.displayName)") // works OK
let markdownSource = document as! SwiftNSDocument // FAIL: cannot cast as
}
}
While I'm asking this is general, my particular test case is to rewrite the macdown-gistit Objective-C plugin as close as possible to pure Swift.
Ideally, the solution would not require including any Objective-C code from the application. What is possible, even if not ideal?
Additional Observation
In the case of a working Objective-C plugin, the Application's NSDocument subclass .h file is not included in the plugin project.
Instead, the Objective-C plugin has only the single relevant property as an NSObject subclass.
#protocol MarkdownSource <NSObject>
#property (readonly) NSString *markdown;
#end
The NSDocument is then downcast as the NSObject subclass in Objective-C:
id<MarkdownSource> markdownSource = (id)document;
NSString *markdown = markdownSource.markdown;
Theoretically, one would think that something similar might work in Swift like
public class MarkdownSource: NSObject {
public var markdown: String = ""
}
where id in Object-C would be a Swift Any. I've tried various downcastings were like such as:
let markdownSource: MarkdownSource = (nsdocument as Any) as MarkdownSource
let markdown: String = markdownSource.markdown
I have not, so far, been able to downcast an NSDocument subclass to any NSObject subclass with just the one desired property in Swift.

Related

Is it safe to ignore compiler warning caused by referring to dispatch_queue_t from Swift?

I have an Objective C class which has the following property:
#property (nonatomic, strong, readonly) dispatch_queue_t couchDispatchQueue;
I have a Swift extension of that class where I reference that property like so:
couchDispatchQueue.async {
When I do that, I get the following compiler warning:
Property type 'OS_dispatch_queue * _Nullable' is incompatible with type 'dispatch_queue_t _Nullable' (aka 'NSObject *') inherited from 'BZCouchDatabase'
I can see why, since my app's generated App-Swift.h file has:
#property (nonatomic, readonly, strong) OS_dispatch_queue * _Nullable couchDispatchQueue;
while dispatch_queue_tis defined as:
typedef NSObject<OS_dispatch_queue> *dispatch_queue_t;
Edit
I've figured out the "further complication" that I was missing in my original description. This property is required by an Objective C protocol which also requires inheritance from NSObject. As soon as I make the Swift class inherit from NSObject and conform to the objective C protocol, I get the warning. The following sample code is enough to set off the warning:
Objective C:
#protocol Thingness
#property (nonatomic, strong, readonly, nullable) dispatch_queue_t couchDispatchQueue;
#end
Swift:
class Thing: NSObject, Thingness {
var couchDispatchQueue: DispatchQueue?
}
My question still is: is it safe for me to just silence this warning? Should I file a radar?
Very well described situation — but I can't reproduce it. When I declare
#property (nonatomic, strong, readonly) dispatch_queue_t couchDispatchQueue;
in an Objective-C class file, the generated header shows me
open var couchDispatchQueue: DispatchQueue! { get }
This is thus seen as a normal Swift 3 DispatchQueue, and my call to couchDispatchQueue.async produces no warning at all.
To be clear, I tried it two ways. I declared the couchDispatchQueue property in the .h file for a Thing class. I imported Thing.h into Swift. I then wrote this code in Swift:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Thing().couchDispatchQueue.async {}
}
}
extension Thing {
func test() {
self.couchDispatchQueue.async {}
}
}
Neither in the straight instance method call nor in the extension do I see any warning.

Accessing Obj-C properties in Swift extension file

I started writing Swift extensions on my view controllers. So I have three files right now:
My header file, ViewController.h:
#interface MyViewController : UIViewController
#end
My Obj-C implementation file, ViewController.m:
#interface MyViewController () <UIScrollViewDelegate>
#property (strong, nonatomic) UIScrollView *scrollView;
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] init];
[self customiseScrollView]; //This is Swift method called from Obj-C file
}
#end
And finally, ViewController.swift:
extension MyViewController {
func customiseScrollView() {
}
}
My question is, is it possible to access my Obj-C properties from my Swift implementation file? Every time I refer to it, I got an error:
Value of type 'MyViewController' has no member 'scrollView'
Bonus 1: Can someone also clarify if the Swift component can see what the .m is a delegate of as well. (To clarify, doing scrollView.delegate = self in Swift is a compile error because the Swift file does not realise that the .m file is a UIScrollViewDelegate).
Bonus 2: Can Swift extension file call Obj-C methods that are declared from the .m counterpart?
I think that you can't access private properties from extension. Your scrollView property is in .m file, not .h - which means it's private and it's not visible from extension file.
Solution: move
#property (strong, nonatomic) UIScrollView *scrollView;
to your header file.
You can access internal objc properties and methods if you declare the objc class extension in a separate header and include that header in the bridging header.
MyClass.h
#interface MyClass : NSObject
#property (nonatomic, copy, readonly) NSString *string;
#end
MyClass+Private.h
#import "MyClass.h"
#interface MyClass ()
#property (nonatomic, copy) NSString *string;
#end
MyClass.m
#import "MyClass+private.h"
#implementation MyClass
//...
#end
Project-Bridging-Header.h
#import "MyClass.h"
#import "MyClass+Private.h"
In Swift, private properties are not accessible from another file. This is the meaning of private in Swift. For example:
file1.swift
class MyClass {
private var privateProperty: String = "Can't get to me from another file!"
}
extension MyClass: CustomStringConvertible {
var description: String {
return "I have a `var` that says: \(privateProperty)"
}
}
file2.swift
extension MyClass {
func cantGetToPrivateProperties() {
self.privateProperty // Value of type 'MyClass' has no memeber 'privateProperty'
}
}
A property declared in the implementation of an Objective-C class is a private property. As such, the property cannot be accessed from a Swift extension since this will be necessarily from a different (.swift) file...
You can. All you need is to create a bridging objective C header.
Per Apple Documentation:
To import a set of Objective-C files in the same app target as your
Swift code, you rely on an Objective-C bridging header to expose those
files to Swift. Xcode offers to create this header file when you add a
Swift file to an existing Objective-C app, or an Objective-C file to
an existing Swift app.
Just create a Bridging-Header file and later import your ObjC file in it, like:
#import <objectivec.h>
Later in your swift file:
var a = objectivec()
a.method_from_those_file
For more information read Apple Doucmentation from here.

Swift Classes vs. Objective-C Classes. Am I on the right track?

I am new to Swift. I've taken a couple of online course and have started converting an existing Objective-C project to Swift as a learning experience. I have a few questions if someone has time. I've searched the board but haven't found the answers. I am sorry if I missed them.
Delegate Protocols - I'm used to defining my own in Objective-C. Below I have the original Objective-C version and below it I have my new Swift version. Have I followed the correct design pattern for Swift?
I find myself making optionals for all of the properties especially objects like NSData or custom classes. Is this practice acceptable? I am not sure how I would know an initial value for most objects. I know the language wants you to set an initial value but it seems strange for certain objects.
If I am not mistaken we do not have to call self.super init in custom initializers in Swift. Is this correct?
Objective-C Version
#protocol FLOParserHandlerDelegate;
#interface FLOParserHandler: NSObject <NSXMLParserDelegate>
// Properties
#property (nonatomic, strong) NSMutableData *PHData;
#property (nonatomic, strong) NSMutableString *currentParsedCharacterData; // This grabs the characters as they come in and adds them together.
#property (nonatomic, strong) NSMutableArray *XMLDataArray; // This is the master array that holds all of the article arrays with the date, title and link objects.
#property (nonatomic, strong) NSMutableDictionary *dateTitleLinkDictionary;// This is used to gather the date, title and link in an array to added to the master array.
// Delegate Property
#property (nonatomic, weak) id <FLOParserHandlerDelegate> delegate;
// init Methods
- (id) initWithCHData: (NSMutableData *) data;
// Class Methods
-(void) startParser;
#end
#pragma mark-
#pragma mark FLOParserHandler Protocol Definition
#protocol FLOParserHandlerDelegate
#optional
- (void) floParserHandlerDidFinishParsing: (FLOParserHandler *) parserHandler;
- (void) floParserHandler: (FLOParserHandler *) parserHandler didFailWithError: (NSError *) error;
#end
Swift Version
import Foundation
protocol FLOParserHandlerDelegate
{
func floParserHandlerDidFinishParsing(parserHandler : FLOParserHandler) -> ()
func floParserHandler(parserHandler : FLOParserHandler, error : NSError) -> ()
}
// Note that we have to inherit from NSObject here. I believe this iis because we are mixing it with Objective-C.
class FLOParserHandler : NSObject, NSXMLParserDelegate
{
var PHData : NSData?
var currentParsedCharacterData : String?
var XMLDataArray : [String]?
var dateDictionary : [String:NSDate]?
var titleDictionary : [String:String]?
var linkDictionary : [String:String]?
// Delegate Property
var delegate : FLOParserHandlerDelegate?
// Init Methods
init(data : NSData)
{
self.PHData = data
}
// Class Methds
func startParser()
{
var parser = NSXMLParser(data: self.PHData)
parser.delegate = self
parser.parse()
}
}
Thank you,
Jon
Your protocol definition is valid. There is one little thing you should know about:
As with type property requirements, you always prefix type method
requirements with the class keyword when they are defined in a
protocol
protocol SomeProtocol {
class func someTypeMethod()
}
Its perfectly fine to use optionals, or you may use implicity unwrapped optionals like NSData!. In which case you should do this and where no, you may read here: Why create "Implicitly Unwrapped Optionals"?.
Shortly, you doing this in following situations:
a) Constant cannot be defined using initializtion, but you know that it would not be nil (otherwise app will crash)
b) Objective-C Api required you to use pointers, and pointers in Obj-C could be nil. In that case you use imilicity unwrapped optionals.
You always have to call 'super' if you have superclass, to be sure, that class is properly initialized.
Hope that helps.

Swift class using Objective-C class using Swift class

I have an obj-c project to which I successfully added a new Swift class A, which is being used by some existing obj-c class B - the use of the automatically generated "MyProject-Swift.h" header worked as expected.
I also successfully added a new Swift class C that uses some existing obj-c class D - the use of the bridging header also worked as expected.
However, suppose I want to refer from my Swift class C to the existing obj-c class B (which in turn refers to the new Swift class A). In order to do that I need to import "B.h" to the bridging header. However, if I do that I get an error in class B: "'MyProject-Swift.h' file not found" (i.e., the file is no longer generated).
Am I doing something wrong or is this a kind of interaction between Swift and Objective-C that is not allowed? It looks like there is a kind of circular reference that the compiler is unable to solve.
--- EDIT ---
I'll try to make the question clearer by adding some code.
-- PREAMBLE --
I added a new Swift class to an obj-c project:
// SwiftClassA.swift
import Foundation
#objc class SwiftClassA : NSObject {
var myProperty = 0
}
The code compiles correctly and is translated into obj-c stubs in the automatically generated "MyProject-Swift.h" header like so:
// MyProject-Swift.h
...
SWIFT_CLASS("_TtC7MyProject11SwiftClassA")
#interface SwiftClassA : NSObject
#property (nonatomic) NSInteger myProperty;
- (instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
Now, one obj-c class uses SwiftClassA:
// ObjCClass.h
#import <Foundation/Foundation.h>
#import <MyProject-Swift.h>
#interface ObjCClass : NSObject
#property (nonatomic, strong) SwiftClassA *aProperty;
#property (nonatomic) int *aNumber;
#end
This also works seamlessly.
-- THE QUESTION --
Can I now create a new Swift class that refers to the obj-c class (ObjCClass) that is using the Swift class SwiftClassA?
This is what I can't do.
If I add the new Swift class:
// SwiftClassB.swift
import Foundation
#objc class SwiftClassB : NSObject {
var aPropertyOfClassB = 1
func someFunc() {
var objCObject = ObjCClass()
var theProperty = objCObject.aProperty
print("The property is \(theProperty)")
}
}
this of course won't compile because of "Use of unresolved identifier 'ObjCClass'". So I need to add that to the bridging header file:
// BridgingHeader.h
#ifndef MyProject_BridgingHeader_h
#define MyProject_BridgingHeader_h
...
#import "ObjCClass.h"
#endif
However, if I do that, the ObjCClass.h file won't compile giving a "'MyProject-Swift.h' file not found".
I've read in several places (with no example, though) that this may mean that there is a circular reference and that a forward reference using #class could solve the problem. However, I'm not sure what needs to be forward referenced and where, and all my attempts failed.
I hope the question is no longer confusing now!
This is a typical cyclical referencing problem.
Be careful to read the docs:
To avoid cyclical references, don’t import Swift into an Objective-C header file. Instead, you can forward declare a Swift class to use it in an Objective-C header. Note that you cannot subclass a Swift class in Objective-C.
So, you should use "forward declare" in .h, and #import in .m:
// ObjCClass.h
#import <Foundation/Foundation.h>
#class SwiftClassA;
#interface ObjCClass : NSObject
#property (nonatomic, strong) SwiftClassA *aProperty;
#property (nonatomic) int *aNumber;
#end
// ObjCClass.m
#import "ObjCClass.h"
#import "MyProject-Swift.h"
#implementation ObjCClass
// your code
#end

Is there any way to add an iVar that's not in the header file (not using LLVM 2.0 or later) in Objective-C?

I recently learned that you can add ivar in a class extension with LLVM2.0. (gcc can't do this)
This is somehow really private iVar because other users don't it's existence since it's not in the header file.
like:
//SomeClass.h
#interface SomeClass : NSObject {
}
#end
//SomeClass.m
#interface SomeClass ()
{
NSString *reallyPrivateString;
}
#end
#implementation SomeClass
#end
But this does rely on the compiler. Is there any other way to declare an ivar that's not in the header file?
The only place to declare instance variables is in the interface or a class extension (which is really an extension of the interface). But you can effectively add instance variables at any time with the modern runtime using the associated object functions.
If you are implementing a library and want to hide your instance variables take a look at what Apple does in the interface for UIWebView. They have an internal webview that does not expose a header file.
#class UIWebViewInternal;
#protocol UIWebViewDelegate;
UIKIT_CLASS_AVAILABLE(2_0) #interface UIWebView : UIView <NSCoding, UIScrollViewDelegate> {
#private
UIWebViewInternal *_internal;
}
If you're just going to be using the ivar internally, and you're using the modern runtime (Snow Leopard 64 bit and iOS 3.0+, I think) then you can just declare properties in a class extension and synthesize them inside the class. No ivars are exposed in your header, no messy id _internal objects, and you get around fragile ivars, too.
// public header
#interface MyClass : NSObject {
// no ivars
}
- (void)someMethod;
#end
// MyClass.m
#interface MyClass ()
#property (nonatomic, retain) NSString *privateString;
#end
#implementation MyClass
#synthesize privateString;
- (void)someMethod {
self.privateString = #"Hello";
NSLog(#"self.privateString = %#", self.privateString);
NSLog(#"privateString (direct variable access) = %#", privateString); // The compiler has synthesized not only the property methods, but also actually created this ivar for you. If you wanted to change the name of the ivar, do #synthesize privateString = m_privateString; or whatever your naming convention is
}
#end
This works with Apple's gcc, in addition to LLVM. (I'm not sure if this works on other platforms, ie not Apple's gcc, but it will certainly work for both iOS and Snow Leopard+).