ObjectiveC: Need suggestion for my way of having protected method? [duplicate] - ios

This question already has answers here:
Protected methods in Objective-C
(9 answers)
Closed 9 years ago.
Simply put, I need a way to have some private methods in a class that are only exposed for its subclasses, and it is difficult (maybe impossible) to do this in Objective-C.
What I did so far:
// MyClass.h
#protocol MyClassProtectedMethodsProtocol
- (void)__protectedMethod;
#end
#interface MyClass : NSObject
- (void)publicMethod;
- (id<MyClassProtectedMethodsProtocol>)protectedInstanceForSubclass:(id)subclass;
#end
Then:
// MyClass.m
#import "MyClass.h"
#interface MyClass() <MyClassProtectedMethodsProtocol>
#end
#implementation MyClass
- (void)publicMethod
{
// something
}
- (id<MyClassProtectedMethodsProtocol>)protectedInstanceForSubclass:(id)subclass
{
if ([subclass isKindOf:MyClass.class] && ![NSStringFromClass(subclass.class) isEqualToString:NSStringFromClass(MyClass.class)])
{
// the subclass instance is a kind of MyClass
// but it has different class name, thus we know it is a subclass of MyClass
return self;
}
return nil;
}
- (void)__protectedMethod
// something protected
{
}
#end
Then the subclass of MyClass can just:
id<MyClassProtectedMethodsProtocol> protectedMethodInstance = [self protectedMethodForSubclass:self];
if (protectedMethodInstance != nil)
{
[protectedMethodInstance protectedMethod];
}
This way does not break OO (compared to calling the private method and ignoring the compiler warning, or even guessing the private method name as only .h is known), but a protocol is needed for the available protected methods and once this is exposed, in a big project that we only deliver interface and static library to client, client can actually know the private methods and try to call them regardless of warning. And the bigest problem is from outside of the subclass, user can as well call this method to get the protectedInstance. Can anyone advice?
Thanks

Check this: Protected methods in Objective-C
Simply put, there is no way to prevent a method from being called in Objective-C, since ultimately, the client can still call performSelector on any object.

A standard way to handle this scenario is to include the internal methods in a separate header, like MySuperClass_Internal.h. Use a class extension: #interface MySuperClass (Internal). Do not install MySuperClass_Internal.h at /usr/local/include or in the framework, or however you're delivering the library to your clients.

Related

Implementing an Obj-C class in my Swift project

I'm trying to add a webshop to my app. The problem is that the webshop has provided me with an Obj-C framework and I'm only familiar with making iOS apps in Swift.
I've managed to setup an Obj-C bridging header and instantiate the webshop object.
My project looks like this:
When the shop button on my CodeDetailViewController gets tapped this function gets triggered:
#IBAction func shopButtonPressed(sender: UIButton){
let instanceOfShop : Shop = Shop()
instanceOfShop.showShop()
}
My Shop.m looks like this:
#import <Foundation/Foundation.h>
#import <FMShop/FMShop.h>
#import "Shop.h"
#implementation Shop
- (void) showShop {
//SANDBOX
FMShop* shop = [[FMShop alloc] initWithKey:#"4M7HDPAQMY68S"
storeKey:#"C8RSHBH710GK8"
buyerID:nil
sandbox:YES
controller:nil
delegate:nil];
[shop showStore:kShopViewHome ID:nil];
}
#end
The showShop function in this class gets triggered. Nothing however shows up.
The manual on how to implement this webshop is aimed at Obj-C apps. Concerning the delegate it states:
delegate: Please define a class or an interface that implements the delegates. Without such, the Store will not show. Please go to FMShopDelegate to see the supported list of methods and callbacks.
This is what I could find in the framework:
#protocol FMShopDelegate <NSObject>
#optional
-(void)shopWillAppear:(FMShop*)shop;
-(void)shopDidAppear:(FMShop*)shop;
-(void)shopWillDisappear:(FMShop*)shop;
-(void)shopDidDisappear:(FMShop*)shop;
-(void)shopProductInfo:(FMShop*)shop products:(NSArray*)productArray;
-(void)shopHistoryOrderInfo:(FMShop*)shop orders:(NSArray*)orderArray;
-(void)shopPlaceOrderSuccess:(FMShop*)shop orderID:(NSString*)orderID;
-(void)shopOrderUserCancelled:(FMShop*)shop orderID:(NSString*)orderID;
-(void)shopFailed:(FMShop*)shop error:(FMShopErrorType)error;
#end
Can anyone tell me how to connect this final method to the right delegate?
Sounds like if you don't provide a delegate when you instantiate your instance of FMShop, then the show method will not work. In your Shop.m file, you're instantiating the FMShop object with nil as the delegate parameter.
Try having an object conform to the FMShopDelegate protocol, and passing this object as the delegate parameter. You could even have your Shop object conform to the protocol itself, and pass self as the delegate if that would serve your purposes.
You could do this in Swift in a new Shop.swift file, or in the Shop.m file in Objective-C:
Swift:
class Shop: NSObject {
func showShop() {
// I'm guessing on the syntax here, Xcode will help autocomplete the correct functions
let shop = FMShop(key: KEY, storeKey: STOREKEY...)
shop.show(store: kShopViewHome, id: nil)
}
}
extension Shop: FMShopDelegate {
// Implement any of the protocol functions you want here
}
Objective-C:
#interface Shop() <FMShopDelegate> // Conform to the delegate here
#end
#implementation Shop
- (void) showShop {
//SANDBOX
FMShop* shop = [[FMShop alloc] initWithKey:#"4M7HDPAQMY68S"
storeKey:#"C8RSHBH710GK8"
buyerID:nil
sandbox:YES
controller:nil
delegate:self];
[shop showStore:kShopViewHome ID:nil];
}
// Implement any of the protocol methods here
#end
You have to use bridging header for accessing obj-c file in swift
Below is the apple doc to describe how to create & use bridging header
Apple Doc
Use bridging header in your project.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html

Declaring private variables in header file vs declaring variables in class extension

What's the difference between declaring a #private ivar in the header file and declaring the same ivar in the class extension without #private? As far as I understand it's the same thing.
Also, can you declare a private property in the header?
The concept is to declare in the header file only those things (methods, properties, etc) which are public. Declare all private items in the implementation file's class extension.
This provides the class users only information that is available for their use and hides all else. It also make it easier for a user of the class quickly see the functionality available to him. Writing code is all about readability and understandability to the developer.
This way a developer is free to change anything that is not exposed in the header files without making any externally visible changes.
In recent versions of Objective this is finally fully releasable via class extensions.
What's the difference between declaring a #private ivar in the header file and declaring the same ivar in the class extension without #private?
There are a few differences. In short, variables declared in the header file are visible to subclasses and class categories. Variables declared in the implementation are not.
1) Instance variables declared in a class's main #interface block are available to external class categories or extensions, even if those variables are declared #private. E.g.:
// YourClass.h
#interface YourClass : NSObject {
#private
int _yourPrivateIVar;
}
#end
// MyExtension.m
#implementation YourClass(MyExtension)
- (void)reset { _yourPrivateIVar = 0; } // This is allowed.
#end
Instance variables declared in the implementation are not available to external class categories.
2) A base class and its subclass cannot both declare the same ivar in their #interface, even if both ivars are #private. E.g., this is not allowed:
#interface Base : NSObject
{
#private
int _foo;
}
#end
#interface Subclass : Base
{
#private
int _foo; // Error: Duplicate member _foo
}
#end
If both ivars are declared in a class extension or implementation block then not only does it compile but it works as expected: both classes have their own separate _foo ivars that do not conflict with one another. On other words, both variables are truly private and separate:
#implementation Base {
int _foo;
}
#end
#implementation Subclass {
int _foo;
}
- (void)reset { _foo = 123; } // Does not affect base class's _foo
#end
Note: If the base class and subclass declare a "private" property or method with the same name it will compile without warning or error, but it will fail spectacularly at runtime as both classes unknowingly interfere with each other's private data.

ARC unavailable methods in Swift

I was able to see an interesting case using
Estimote nearables SDK
They have a class ESTNearable with property called zone.
// ENUM
typedef NS_ENUM(NSInteger, ESTNearableZone ) {
ESTNearableZoneUnknown = 0,
ESTNearableZoneImmediate,
ESTNearableZoneNear,
ESTNearableZoneFar,
};
// CLASS
#interface ESTNearable : NSObject <NSCopying, NSCoding>
// ...
#property (nonatomic, assign, readonly) ESTNearableZone zone;
// ...
#end
So when I try to use this method in Swift, compiler fails with that error:
As I understand there is some kind of compiler bug and for some reason it believes that I want to use old zone method from NSObject - (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE; I can use other specific properties of that class without any problems.
As I use an SDK I can not change the name of the zone method. I believe I can write some kind of obj-c category, add some new method there, which will return value of original one, but I do not want to add obj-c classes in my project.
Is there any possibility to call this method from swift as I believe correct zone method will be called for class instances?
Thanks in advance!
Here I found the same question. I answered more deeply there. I could not find something more good, so I went ahead with my old assumptions.
I Added this category to Bridging Header. It worked fine.
#import <EstimoteSDK/EstimoteSDK.h>
#interface ESTNearable (Ex)
/// Solving the compiler problem that "zone" method is unavailable in Swift
#property (readonly) ESTNearableZone nearableZone;
#end
// Implementation
#implementation ESTNearable (Ex)
- (ESTNearableZone)nearableZone
{
return self.zone;
}
#end
After that I just used nearableZone method in Swift
var zone = someNearable.nearableZone

Unit testing private methods from a category?

I have a category on NSString class that contains a private helper method.
It would be handy if I could use this method in my unit test.
However I have difficulties to expose it.
When I create a class extension on NSString and declare the method here, the method is not visible in unit test. And it doesn't matter if I create the class extension in a separate header file, or as a part of unit test .m file.
It looks like I am missing something here.
Any help guys?
Common unit testing guidance would tell you not to try and test your private methods. Only test via your public interfaces. Private methods are simply an implementation detail that could change at any time, when you refactor. Your public interfaces should be pretty stable, and will exercise your private methods.
However, if you still want to test your private category methods, the following works for me...
First, your category:
UIImage+Example.h
#interface UIImage (Example)
#end
UIImage+Example.m
#implementation UIImage (Example)
+ (NSString *)examplePrivateMethod
{
return #"Testing";
}
#end
MyExampleTests.m
#import <XCTest/XCTest.h>
#import "UIImage+Example.h"
#interface UIImage (Example_Test)
+ (NSString *)examplePrivateMethod;
#end
#interface MyExampleTests : XCTestCase
#end
#implementation MyExampleTests
- (void)testExample
{
XCTAssertEqualObjects(#"Test", [UIImage examplePrivateMethod], #"Test should be test");
}
#end
Essentially, redeclare your private method in a new category in your test. However, as mentioned above this is exposing private methods just for the purpose of testing, and coupling your tests to your implementation.
You can execute any method (private or not) on an object by simply using performSelector: on it, like so:
[something performSelector:#selector(somePrivateMethod)];
But I agree with James that you should only do that when absolutely necessary.

When to declare something in category .m file or in header .h file? [duplicate]

This question already has answers here:
Why is there another #interface inside the.m file? [duplicate]
(6 answers)
Closed 9 years ago.
As we know, normally we used to declare our class instance variables, properties, method declarations in class header file (.h).
But we can do the same things, in .m file, using blank category.
So my question is: what should be declared in .h file and what should be declared in .m file - and why?
Regards,
Mrunal
New Edit:
Hi all,
If you refer to newly added Apple examples over developer.apple.com - they are now declaring their IBOutlets and IBActions in .m file itself and that too with property declaration. But we can achieve the same thing by declaring those references in .h file in class private member section.
Then why are they declaring those in .m file and as properties, any idea?
-Mrunal
But we can do the same things, in .m file, using blank category.
A class continuation.
Normally, you choose to declare something in the header if it is intended to be public -- used by any client. Everything else (your internals) should typically go in the class continuation.
I favor encapsulation -- Here's my approach:
variables
Belongs in the class continuation or #implementation. Exceptions are very, very rare.
properties
Typically belongs in the class continuation in practice. If you want to give subclasses the ability to override these or to make these part of the public interface, then you could declare them in the class declaration (the header file).
method declarations
More in the class continuation than in the class declaration. Again, if it is meant to be used by any client it would belong in the class declaration. Often, you won't even need a declaration in the class continuation (or class declaration) -- the definition alone is adequate if it is private.
Basically, in the header file (.h) you declare your public API, while in the implementation file (.m) you declare your private API.
Visibility in Objective-C
You can also find the answer here
It's mostly up to you.
The .h file is like the description of your class.
It's smart to only put in the .h file what's really important to be visible from the outside of the class, especially if you're working with other developers.
It will help them to understand more easily what methods/properties/variables they can use, rather than having a whole list of things they don't.
Usually you want to use blank category in .m file for declaration of private properties.
// APXCustomButton.m file
#interface APXCustomButton ()
#property (nonatomic, strong) UIColor *stateBackgroundColor;
#end
// Use the property in implementation (the same .m file)
#implementation APXCustomButton
- (void)setStyle:(APXButtonStyle)aStyle
{
UIColor *theStyleColor = ...;
self.stateBackgroundColor = theStyleColor;
}
#end
If you try to access property declared in black category outside .m file, you will receive undeclared property compiler error:
- (void)createButton
{
APXCustomButton *theCustomButton = [[APXCustomButton alloc] init];
theCustomButton.stateBackgroundColor = [UIColor greenColor]; // undeclared property error
}
In most cases, if you want add new method/properties to an existing class without subclassing, then you want declare category in .h file and implementation of declared methods in .m file
// APXSafeArray.h file
#interface NSArray (APXSafeArray)
- (id)com_APX_objectAtIndex:(NSInteger)anIndex;
#end
// APXSafeArray.m file
#implementation NSArray
- (id)com_APX_objectAtIndex:(NSInteger)anIndex
{
id theResultObject = nil;
if ((anIndex >= 0) && (anIndex < [self count]))
{
theResultObject = [self objectAtIndex:anIndex];
}
return theResultObject;
}
#end
Now you can use "com_APX_objectAtIndex:" method wherever "APXSafeArray.h" is imported.
#import "APXSafeArray.h"
...
#property (nonatomic, strong) APXSafeArray *entities;
- (void)didRequestEntityAtIndex:(NSInteger)anIndex
{
APXEntity *theREquestedEntity = [self.entities com_APX_objectAtIndex:anIndex];
...
}

Resources