How do I access private instance variables in Obj-C or Swift? - objective-c

I'm trying to get access to MKMapSnapshotter's private instance variables _lodpiSnapshotCreator and _hidpiSnapshotCreator in Swift on macOS.
Thanks to class-dump, I know they're there (see here):
#interface MKMapSnapshotter : NSObject
{
[...]
VKMapSnapshotCreator *_lodpiSnapshotCreator;
VKMapSnapshotCreator *_hidpiSnapshotCreator;
}
but no matter what I do, I can't get ahold of them.
This is how I checked whether I could access them or not:
let snapshotter = MKMapSnapshotter(options: snapshotOptions)
let varNames = ["_lodpiSnapshotCreator", "_hidpiSnapshotCreator"]
for varName in varNames {
if let testIVar = class_getInstanceVariable(MKMapSnapshotter.self, varName) {
if let test = object_getIvar(snapshotter, testIVar) as? VKMapSnapshotCreator {
print(test)
} else {
print("Got ivar, but \(varName) is still nil (getInstanceVariable)")
}
} else {
print("\(varName) is nil (getInstanceVariable)")
}
}
Curiously, class_getInstanceVariable doesn't return nil, but object_getIvar does.
Got ivar, but _lodpiSnapshotCreator is still nil (getInstanceVariable)
Got ivar, but _hidpiSnapshotCreator is still nil (getInstanceVariable)
I'm at my wit's end here. All I can find via Google is people recommending the use of class_getInstanceVariable (which I already use) and key-value-coding (which doesn't work at all).
This must have been done before. Can anyone help me out?
Edit: So far, I have tried this:
#interface MKMapSnapshotter() {
#public VKMapSnapshotCreator *_lodpiSnapshotCreator;
#public VKMapSnapshotCreator *_hidpiSnapshotCreator;
}
#end
That compiles successfully, but when trying to use it, Swift keeps insisting that the members _lodpiSnapshotCreator and _hidpiSnapshotCreator don't exist.

Since we don't have or control the source code we can't change the variables to properties. Just tried this works for your case:
extension MKMapSnapshotter {
func getPrivateVariable() -> String? {
return value(forKey: "_lodpiSnapshotCreator") as? String
}
open override func value(forUndefinedKey key: String) -> Any? {
if key == "_lodpiSnapshotCreator" {
return nil
}
return super.value(forUndefinedKey: key)
}
}
You can find more about this here.
If this does not work then I believe that there is no way to access Objective-C instance variables from Swift. Only Objective-C properties get mapped to Swift properties.
Hope this helps!!

Related

Unable to cast element in ForEach Loop to NSManagedObject Subclass SwiftUI

I got a ListView in SwiftUI and want to generate RowViews depending on a given property.
Therefore I want to check the property of the element in the ForEach loop.
Xcode does not recognize the type of the element, thats why i want to cast the element to the correct Class which is an NSManagedObject Subclass.
I created the NSManagedObject Subclass from the xcdatamodeld (Editor -> Create NSManagedObject Subclass...).
The NSManagedObject Subclass is created in objective-c and I added the .h file to the bridging header.
I have no problem working with the NSManagedObject Subclass in the rest of the project.
I can even create an property of that Type in the same Struct like so:
struct MyListView: View {
var test : MyNSManagedObjectSubclass //Xcode does recognize it here
For some reason Xcode wont recognize the NSManagedObject Subclass inside the ForEach Loop.
code looks like:
struct MyListView: View {
var test : MyNSManagedObjectSubclass //Xcode does recognize it here
#EnvironmentObject private var fetchResultsContainer : FetchedResultsContainer
var body: some View {
NavigationView{
VStack{
ForEach(fetchResultsContainer.results , id: \.identifier) { result in
if let castedResult = result as! MyNSManagedObjectSubclass { //Xcode does not recognize it here
if castedResult.property{
ResultRowView(input: result)
}
}
}
}
}
}
}
FetchedResultsContainer:
#objc class FetchedResultsContainer : NSObject, ObservableObject{
#objc #Published var results: [MyNSManagedObjectSubclass]()
#objc static let sharedInstance: FetchedResultsContainer = {
let instance = FetchedResultsContainer()
return instance
}()
}
I feel like im missing something obvious, as im still quite new to Swift and SwiftUI.
Appreciate your help.
Ok after taking a step back and simply coding it again from scratch it simply worked an no casting was needed...
List(fetchResultsContainer.results, id: \.identifier) { result in
if (result.property == 0) {
//do something
}
}
My assumption that the casting was not working was not correct.
I probably checked result.property == false, with property being of type NSNumber and it gave out some strange compile errors that led me on the wrong path.

Objc-C to Swift: How to create a property in Swift that guarantees it's of a certain type when callers use it?

TL;DR: This is about migrating an Objective-C pattern to Swift. It might be best to first look at the Objective-C interface below to better understand what I'm trying to achieve.
I am just starting to adapt a rather large codebase from Objective-C to Swift. There were some design patterns in the legacy codebase that were put in place to try and provide some type-safety.
These patterns seem really out of place in Swift but I'm not sure what the proper "Swift Way" of doing this is. Using Generics feels like the way to go about it, but I'm unclear on how best to proceed.
The goal is to create a struct that has a property that can hold "almost anything". Callers are expecting the property to be of a certain type when used and an error or exception should be thrown if there is a type-mismatch. (i.e.: The caller expected the argument to be an integer but in reality a string was store.)
struct Command<T> {
let directive: Directive
let argument: T
}
let command = Command(directive: .draw, argument: NSZeroRect)
let command2 = Command(directive: .toggle, argument: true)
// Somewhere else in the code...
//
// How do I pass in a Command<> here?
// This generates an error because Command<Bool> cannot be converted to Command<Any>
//
func processCommand(_ command:Command<Any>) {
switch command.directive {
case .draw:
// How do I ensure that command.argument is indeed an NSRect?
case .toggle:
// How do I ensure that command.argument is indeed a boolean?
}
}
The Objective-C interface looks something like this. Note that argument can be many different types. Ranging from primitives (Integers, Booleans, Doubles, etc...) to anything that can be stored in NSValue or that supports NSCoding.
There are multiple property accessors for each type where it makes sense.
#interface FLCommand : NSObject
#property(assign, readonly) FLDirective directive;
#property(strong, readonly) id argument;
#property(strong, readonly) BOOL argumentAsBoolean;
#property(strong, readonly) NSRect argumentAsRect;
- (instancetype)initWithDirective:(FLDirective)directive booleanArgument:(BOOL)value;
- (instancetype)initWithDirective:(FLDirective)directive rectArgument:(NSRect)rect;
- (instancetype)initWithDirective:(FLDirective)directive argument:(id)arg;
#end
#implementation FLCommand
- (instancetype)initWithDirective:(FLDirective)directive
booleanValue:(BOOL)value {
// Convert boolean to object.
return [self initWithDirective:directive
argument:#(value)];
}
- (instancetype)initWithDirective:(FLDirective)directive
rectArgument:(NSRect)rect {
// Convert NSRect to object.
return [self initWithDirective:directive
argument:[NSValue valueWithRect:rect]];
}
- (BOOL)argumentAsBoolean {
NSAssert([_argument isKindOfClass:NSNumber.class], #"Expected argument to be an NSNumber.");
return [self.argument boolValue];
}
- (NSRect)argumentAsRect {
NSAssert([_argument isKindOfClass:NSValue.class], #"Expected command argument to be an NSValue.");
return [(NSValue *)self.argument rectValue];
}
#end
// Somewhere else in the code the commands are acted upon. Using the
// asserts and type-specific property accessors offers a poor-man's
// way of doing type safety to ensure the the command's argument is
// of the expected type.
- (void)processCommand:(FLCommand *)command {
switch (command.directive) {
case FLDirectiveToggleSomething:
// The assert will fire if the argument is not a boolean.
[self toggleSomething:command.argumentAsBoolean];
break;
case FLDirectiveDrawSomething:
[self drawSomethingInFrame:command.argumentAsRect];
break;
}
}
}
Using an equivalent pattern in Swift seems very un-Swift like to me. Is there a better way to go about this using Generics?
Swift 5 and macOS 10.15+ solutions are OK.
Have you considered using enumerations with associated values (often referred to as complex enums)
enum Directive {
case draw(NSRect)
case toggle(Bool)
}
struct Command {
let directive: Directive
}
let command = Command(directive: .draw(.zero))
let command2 = Command(directive: .toggle(true))
func processCommand(_ command: Command) {
switch command.directive {
case .draw(let rect):
// do something with rect
case .toggle(let value):
// do something with the value
}
}
(And you could actually skip the Command struct entirely in the above)
Or an alternative solution is to use a protocol with an associated type:
protocol Command {
associatedtype AssociatedType
var argument: AssociatedType { get }
init(_ argument: AssociatedType)
func process()
}
struct DrawCommand: Command {
typealias AssociatedType = NSRect
let argument: AssociatedType
init(_ argument: AssociatedType) {
self.argument = argument
}
func process() {
print("draw something with \(argument)")
}
}
struct ToggleCommand: Command {
typealias AssociatedType = Bool
let argument: AssociatedType
init(_ argument: AssociatedType) {
self.argument = argument
}
func process() {
print("toggle something with \(argument)")
}
}
let command = DrawCommand(.zero)
let command2 = ToggleCommand(true)
command.process()
command2.process()
This have a bit more boilerplate/overload, but provides a better separation of concerns and will be more flexible for you introducing more commands in the future without having to update enum/switches multiple places in the code.

How to use pointer from other classes in swift

I'm immigrating my old ObjectiveC code to swift. In ObjcC had a separate class to handle my Admob activity.
In this class I've created a pointer in the init func, and when changing scene, I could use this pointer to change the location of the ads banner.
#implementation MyAdsSupport
+(id)ShowAds:(My_Ads_Position)posIndex
{
if (_adsBannerPointer == nil)
{
_adsBannerPointer = [[KTFAdsSupport alloc]initAds:posIndex];
}
else
{
[_adsBannerPointer setAdsPosition:posIndex];
}
return _adsBannerPointer;
}
In Swift I created the Admob class, and managed to present ads on screen but when I try to call the pointer from another class it returns always nil.
Here is my Swift Code:
var adsPointer: My_Ads_Support!
func initAds(myView: UIViewController, atPos: My_Ads_Position) -> KTF_Ads_Support {
if adsPointer == nil {
adsPointer = self
adsPointer.ShowAds(myView: myView, atPos: atPos)
}
else
{
print("adsPointer ALIVE")
adsPointer.setAdsPos( atPos: atPos)
}
return self.adsPointer!
}
How can I set a pointer in Swift to be able to reach the ads banner from any scene?
In your Objective-C code you have three methods, the instance methods initAds: and setAdsPosition:, and the class method ShowAds:. The latter uses a variable, presumably declared static, called _adsBannerPointer.
Your Swift code is not the same. It has two methods, the instance methods initAds and setAdsPos, and one variable, the instance variable adsPointer.
In Swift class methods are termed type methods (as they can belong to classes, structs and enumerations) and are indicated by the use of the keyword static, type (class) variables are also indicated with static. So to follow your Objective-C model you need something along the lines of:
static var adsPointer: My_Ads_Support!
// instance init
init(startingPos : My_Ads_Position) { ... }
// instance set position
fun setAdsPos(atPos : My_Ads_Position) { ... }
static func showAds(myView: UIViewController, atPos: My_Ads_Position) -> KTF_Ads_Support { ... }
HTH

Property 'sharedInstance' not found on object of type ClassA

I am creating a swift framework. In that one class is like this as shown below.
import Foundation
#objc public class classA: NSObject {
public override init (){
super.init();
}
/**
Singleton intance is returned.
*/
public class var sharedInstance: classA {
struct Static {
static let instance = popeye();
}
return Static.instance
}
}
Now when i add this framework into a Objective c project and try to access "sharedInstance" i get this error.
Property 'sharedInstance' not found on object of type ClassA.
Fix it Replace 'sharedInstance' with 'sharedInstance'
But even if i try use Fix it, this issue isnt solved.
NOTE: This issue doesn't happen when i integrate this framework with a swift project!!!
I AM STUCK.. :(
I tried to reproduce your problem. At first the syntax highlighter in Xcode flagged the same error in Objective-C that you mentioned, but the code actually was built and ran fine.
However, there is a cleaner way of doing this. In your code you are using a computed type property, which is evaluated every time you access it! You work around this by introducing the struct Static, where you essentially do what could be done in classA itself, like this:
/**
Singleton intance is returned.
*/
public static var sharedInstance: classA = popeye()
Here we used a stored type property, which is a recommended way to implement singletons, see here:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html
And here is some documentation on different kinds of properties:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
Finally i was able to fix this with a minor change !! :)
Swift framework code
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
// the sharedInstance class method can be reached from ObjC
class func sharedInstance() -> SingletonTest {
return SingletonTest.swiftSharedInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
Objective C parent project code
SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(#"Singleton says: %#", [aTest testTheSingleton]);

Objective-C Block in Swift - Variable Missing?

Here's a block type that I am defining in objective-c
typedef void (^arrayBlock)(NSArray *blockArray);
I have an objective-c class with a method that uses this as a return block
-(void)loadTimesWithSuccessBlock:(arrayBlock)successBlock;
When I try to use this method in Swift, this is what autocomplete gives me.
let timeClockLibrarian = HMFTimeClockLibrarian()
timeClockLibrarian.loadTimesWithSuccessBlock { ([AnyObject]!) -> Void in
//Where is blockArray?
}
I'm assuming that [AnyObject]! is supposed to be the NSArray. But I don't see how I'm supposed to get access to that variable?
If I were to use this method in Objective-C I get a result like this:
[timeClockLibrarian loadTimesWithSuccessBlock:^(NSArray *blockArray) {
//I can use the blockArray here :)
}];
[AnyObject]! is indeed only the type of the variable; autocomplete didn't name it. You just need to do something like (blockArray: [AnyObject]!).
let timeClockLibrarian = HMFTimeClockLibrarian()
timeClockLibrarian.loadTimesWithSuccessBlock { (blockArray: [AnyObject]!) -> Void in
// your code here
}
Write like this:
let timeClockLibrarian = HMFTimeClockLibrarian()
timeClockLibrarian.loadTimesWithSuccessBlock { blockArray in
doSomething(blockArray)
}
If you want to refer to weak self use this:
let timeClockLibrarian = HMFTimeClockLibrarian()
timeClockLibrarian.loadTimesWithSuccessBlock { [weak self] blockArray in
self?.doSomething(blockArray)
}
You may also want to get rid of implicit unwrapping. If so, specify nullability in Obj-C code:
typedef void (^arrayBlock)(nullable NSArray *blockArray);