I am building a ViewController in Swift to update when state changes occur from an Objective-C class. Others have written the ObjC; I am implementing the Swift view.
While conceptually aware of NSNotification, Delegates and Callbacks 1, I am looking to employ Key-Value Observing (KVO).
Apple has overview documentation of KVOs 2 and for Swift 3. Others have documented in Swift 4 and ObjC 5 seperately, but it is still not clear how to implement KVO with a mixed Swift/ObjC environment.
Unclear after reading through the previous StackOverflow discussion on this.
Is key-value observation (KVO) available in Swift?
It is understood that the Swift object is the Observer and the ObjC object to be the Observed. Also understood that the Observer inherits from NSObject.
I am looking to have the Swift ViewController, specifically the lastUpdate variable, to be updated each time the ObjC instance variable, lastRealTimeSynchronizeDate, changes.
And still uncertain how and to express the dependencies on the property I want to observe, where and how to create the global context variable and how the observer for the key path works.
Sample code attached below where KVO has yet to be implemented.
Any guidance would be appreciated. Thank you
SynchronizeMgr.m below:
- (void)synchronizeCompleted
{
// standard date formatting
NSDate *now = [NSDate date];
NSString *dateString = [NSDateFormatter localizedStringFromDate:now
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterShortStyle];
// want Swift ViewController, lastUpdate, to be updated each time this ivar changes
_lastRealTimeSynchronizeDate = dateString;
}
SynchronizeStatus.swift below:
class SynchronizeStatusViewController: UIViewController {
#IBOutlet weak var lastUpdate: UILabel!
func pullDate() -> String {
// retrieving from ObjC, lastRealTimeSynchronizeDate via sharedManager
let m = SynchronizeMgr.sharedManager()
let d = m.lastRealTimeSynchronizeDate
if d == nil {
return "nil"
} else {
return d
}
}
override func viewDidLoad() {
super.viewDidLoad()
// looking to have this update whenever lastRealTimeSynchronizeDate in ObjC is updated
lastUpdate.text = pullDate()
}
}
Related
I need to make init for my view controller in swift. so how can I make similar init method in swift.
- (instancetype)initWithFormlyData:(NSDictionary *)json afterUpdatingProfile:(void (^)(BOOL, BMErrors *))completion
{
self = [super initWithFormlyData:json];
if (self) {
self.updateCompletion = completion;
}
return self;
}
Here's one solution:
init(formlyData json: NSDictionary, afterUpdatingProfile completion: ((Bool, BMErrors?)->Void)?)
{
}
I took the liberty of making the completion's BMErrors an optional, even though your ObjC didn't specify nullable. You may have intended other parameters to be nullable too, so add optionals as needed.
You might want to just simplify to:
init(json: NSDictionary, completion: ((Bool, BMErrors?)->Void)?)
{
}
And maybe consider switching to a Swift Dictionary if your caller doesn't mind.
Hope this helps!
I recently discovered NSMapTable (doc, nshipster) and I have been wondering whether it can be used in place of associated objects.
Here's an example using standard associated objects:
var fooKey = "foo"
extension UIView {
var foo: UIImage? {
set {
objc_setAssociatedObject(self, &fooKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
get {
return objc_getAssociatedObject(self, &fooKey) as? UIImage
}
}
}
versus an implementation using NSMapTable:
let barTable = NSMapTable<UIView, UIImage>(keyOptions: [.weakMemory], valueOptions: [.strongMemory])
extension UIView {
var bar: UIImage? {
get {
return barTable.object(forKey: self)
}
set {
barTable.setObject(newValue, forKey: self)
}
}
}
I tried to google and understand the differences with no luck, as I don't even know how I can compare the two implementations.
How can I compare them? What are the differences?
Thanks
When an object is deallocated, all objects that are associated with it using OBJC_ASSOCIATION_RETAIN will be released (and sometimes deallocated if that was the last strong reference).
Your NSMapTable example won't clean up on dealloc.
Beyond that, they are functionally the same, but have different implementation details. The most significant is the threading policy; NSMapTable is not thread safe whereas OBJC_ASSOCIATION_RETAIN is treated the same as an #property(atomic, strong).
I believe it is the other way around: objc_setAssociatedObject can be used in place of NSMapTable. Instead of requiring a NSMapTable object to hold the references, the reference is stored on the object itself. And although associated objects allow categories to have properties as you demonstrated, in fact that removes powerful functionality. By wrapping it in a category means all UIImages now have that key, whereas by defining the key in the controller now it can store things in UIImage that only it requires.
I'm trying to build an Objective-C block in Swift 2 in order to add it to an NSArray like so :
typealias CompletionBlock = () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
nsArray.addObject(aBlock) // Error
I know it will work just fine with a Swift array, but I need an NSArray here for compatibility with existing Objective-C code. And if I use a swift array the compiler will refuse to cast it to an NSArray because it won't be a [AnyObject] (it will be a [Any]).
The problem here is that a swift closure is not an object contrary to Objective-C blocks which are objects behind the scene (they are instances of NSBlock which is a subclass of NSObject)
So my question is : How do a create an Objective-C block in swift ? I've tried using #convention (block) in the typealias but it doesn't work.
EDIT : As of Swift 3, this is completely unnecessary (and doesn't even work). Adding closures to Objective-C arrays works out of the box in Swift 3. The answer below is valid for Swift 2 only.
I know this is a duplicate but I will still post a refactored answer from swift-closure-as-anyobject and cast-closures-blocks in case anyone lands on this one first.
The solution is to use the unsafeBitCast function to convert the Swift closure to an Objective-C compatible object before adding it to an NSArray and back before using it in Swift.
// The `#convention(block)` is important here in order to get
// Objective-C like memory management
typealias CompletionBlock = #convention(block) () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
let blockObject = unsafeBitCast(aBlock, AnyObject.self)
nsArray.addObject(blockObject)
let closureObject = nsArray[0]
let closure = unsafeBitCast(closureObject, CompletionBlock.self)
closure()
In my application, a NSDocument subclass mission-critical hardware – users really don’t want to close a document by accident! So, I’ve implemented canCloseDocumentWithDelegate… to show an NSAlert and ask before closing.
I am now trying to implement this same thing in an application written in Swift.
Since the answer comes asynchronously, the “should close” result is passed to a callback on a delegate, and not simply returned. In the documentation for -canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:, it says:
The shouldCloseSelector callback method should have the following signature:
- (void)document:(NSDocument *)doc shouldClose:(BOOL)shouldClose contextInfo:(void *)contextInfo
So, as there’s 3 arguments of different types, I cannot use the simple performSelector:withObject: style methods – you have to use NSInvocation. Note that the delegate is of type id, and the signature above does not appear in any formal protocol – you can’t simply call the method normally. (See this mailing list post for example of how this should be done)
Now, the issue is, NSInvocation is not allowed in Swift! See Swift blog “What Happened to NSMethodSignature”:
Bringing the Cocoa frameworks to Swift gave us a unique opportunity to look at our APIs with a fresh perspective. We found classes that we didn't feel fit with the goals of Swift, most often due to the priority we give to safety. For instance, some classes related to dynamic method invocation are not exposed in Swift, namely NSInvocation and NSMethodSignature.
That sounds like a good thing, but falls down when a simple NSDocument API requires NSInvocation still! The real solution to this whole problem would be for Apple to introduce a new canCloseDocument… API using a block callback. But until that happens, what’s the best solution?
You can solve this with some low level runtime functions:
override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
let allowed = true // ...or false. Add your logic here.
let Class: AnyClass = object_getClass(delegate)
let method = class_getMethodImplementation(Class, shouldCloseSelector)
typealias signature = #convention(c) (AnyObject, Selector, AnyObject, Bool, UnsafeMutablePointer<Void>) -> Void
let function = unsafeBitCast(method, signature.self)
function(delegate, shouldCloseSelector, self, allowed, contextInfo)
}
If you need to move this behaviour to another method (eg. after a sheet gets confirmation from the user), simply store the delegate and shouldCloseSelector in properties so you can access them later.
So, my current solution to this, is to keep using Objective-C to perform the NSInvocation. The NSDocument subclass is written in Swift, and calls an Objective-C category to do this bit of work.
Since NSInvocation does not exist in Swift, I really don’t see any other way.
- (void)respondToCanClose:(BOOL)shouldClose delegate:(id)delegate selector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
NSDocument *doc = self;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:shouldCloseSelector]];
invocation.target = delegate;
invocation.selector = shouldCloseSelector;
[invocation setArgument:&doc atIndex:2]; // Note index starts from 2 - 0 & 1 are self & selector
[invocation setArgument:&shouldClose atIndex:3];
[invocation setArgument:&contextInfo atIndex:4];
[invocation invoke];
}
You can see my sample project:
https://github.com/DouglasHeriot/canCloseDocumentWithDelegate
Another option is to use Objective-C to wrap around objc_msgSend, which is also unavailable in Swift. http://www.cocoabuilder.com/archive/cocoa/87293-how-does-canclosedocumentwithdelegate-work.html#87295
At least as of Swift 4.1, you can do something like:
// Application Logic
myDocument.canClose(
withDelegate: self,
shouldClose: #selector(MyClass.document(_:_:_:)),
contextInfo: nil)
...
// Handler
#objc
private func document(_ doc: NSDocument, _ shouldClose: Bool, _ contextInfo: UnsafeMutableRawPointer) {
...
}
Here is a Swift solution to this issue that I received from Apple Developer Technical Support:
override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
super.canCloseDocumentWithDelegate(self, shouldCloseSelector: "document:shouldClose:contextInfo:", contextInfo: contextInfo)
}
func document(doc:NSDocument, shouldClose:Bool, contextInfo:UnsafeMutablePointer<Void>) {
if shouldClose {
// <Your clean-up code>
doc.close()
}
}
I am working on a Swift project with custom classes. The old Objective-C version of the project had a custom init method shown below.
Custom init from custom class
-(id) initWithURLDictionary: (NSDictionary *) dictionary{
self = [super init];
if (self) {
self.URLDictionary = dictionary;
}
return self;
}
When using this class I would create the object with the custom initializer and then set the delegate for the custom class to self.
// Create a temporary dictionary of the feeds, allocate and initialize the FLODataHandler object and set the delegate (me) to self.
NSDictionary *URLTempDictionary = [[NSDictionary alloc] initWithObjectsAndKeys: kFLOCyclingURL, #"FLO Cycling", kTriathleteURL, #"Triathlete", kVeloNewsURL, #"Velo News", kCyclingNewsURL, #"Cycling News", kRoadBikeActionURL, #"Road Bike Action", kIronmanURL, #"Ironman", nil];
self.dataHandler = [[FLODataHandler alloc] initWithURLDictionary: URLTempDictionary];
self.dataHandler.delegate = self;
In Swift I am a bit confused. It seems I have two options. Option 1 would have me creating a custom initializer in the custom class.
Custom Initializer in custom class
init(dictionary : [String:String]) { self.URLDictionary = dictionary }
The process is the same as in Objective-C.
let URLTempDictionary = [kFLOCyclingURL : "FLO Cycling", kTriathleteURL : "Triathlete", kVeloNewsURL : "Velo News", kCyclingNewsURL : "Cycling News", kRoadBikeActionURL : "Road Bike Action", kIronmanURL : "Ironman"]
var tempDataHandler = FLODataHandler(dictionary: URLTempDictionary)
self.dataHandler! = tempDataHandler
Option 2 does not through a complaint but seems incomplete.
Instead of creating a custom initializer I would simply do the following. The custom class has a dictionary property called URLDictionary.
let URLTempDictionary = [kFLOCyclingURL : "FLO Cycling", kTriathleteURL : "Triathlete", kVeloNewsURL : "Velo News", kCyclingNewsURL : "Cycling News", kRoadBikeActionURL : "Road Bike Action", kIronmanURL : "Ironman"]
self.dataHandler!.URLDictionary = URLTempDictionary
self.dataHandler.delegate = self
So my question has to do with the need for the custom initializer and the use of
var tempDataHandler = FLODataHandler(dictionary: URLTempDictionary)
Does the use of
self.dataHandler!.URLDictionary = URLTempDictionary
accomplish the same result?
Take care,
Jon
The purpose of an initializer is effectively to compel the caller to supply the data - and Swift helps you by enforcing this contract in a way that Objective-C does not. If you declare init(dictionary:), all other inherited initializers cease to be inherited, and init(dictionary:) becomes the only way to make a FLODataHandler.
So if it is crucial that a FLODataHandler have a URLDictionary value from the get-go, by all means declare the initializer. Indeed, if it is crucial that it also have a delegate, declare init(dictionary:delegate:) instead. That is "best practice".
On the other hand, there is nothing inherently evil about two-stage initialization, i.e. first make the object, then give its properties values; and there are situations in real-life iOS programming where there's no real alternative (prepareForSegue comes to mind). The problem with it is that it relies on an unenforceable contract, which the caller must simply know in some other way and adhere to voluntarily.
EDIT: You also seem to be asking whether merely saying
self.dataHandler!.URLDictionary = URLTempDictionary
somehow magically creates a FLODataHandler to occupy the dataHandler property. It most certainly does not. No objects come magically into existence in Swift, any more than in Objective-C. If no one has ever said FLODataHandler(...), then no such instance exists. If no one has ever assigned a FLODataHandler instance to self.dataHandler, then there is no FLODataHandler there (and the above code will crash if it means you are trying to unwrap nil).