How to identify custom classes vs Framework Classes in Objective-C - ios

I am working on an Analytics Project by Swizzling UIViewController methods viewDidAppear and viewDidAppear, code snippet as follows,
- (void) swizzledViewDidAppear : (BOOL)animated {
if ([UA isAppInitialized]) { // Check if Analytics Initialized
if ([[self class] isSubclassOfClass:[UIViewController class]]) {
[UA startPage:[NSString stringWithFormat:#"%#", NSStringFromClass ([self class])]];
}
}
[self swizzledViewDidAppear:animated];
}
- (void) swizzledViewDidDisappear : (BOOL)animated {
if ([UA isAppInitialized]) { // Check if Analytics Initialized
if ([[self class] isSubclassOfClass:[UIViewController class]]) {
[UA endPage:[NSString stringWithFormat:#"%#", NSStringFromClass ([self class])]];
}
}
[self swizzledViewDidDisappear:animated];
}
This is the code snippet where I want to track only the Custom ViewController, ex: MyViewController or FooViewController...etc and not Framework related classes like UICompatibilityInputViewController, UIInputWindowController...etc.
Please let me know how can I achieve this. I tried to check for Subclass but still at one point Framework classes are getting recorded.
Thanks,
Vijay

you can get a class's NSBundle and check if that bundle is yours.
swift example using an extension:
//code
extension NSObject {
static var isOurClass: Bool {
let appBundle = Bundle.main
let clsBundle = Bundle(for: self.self);
return clsBundle.bundlePath.hasPrefix(appBundle.bundlePath)
}
}
//test
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("appdelegate class ours? \(AppDelegate.isOurClass)")
print("NSString class ours? \(NSString.isOurClass)")
return true
}
}
basic objc example:
#import <Foundation/Foundation.h>
#interface T : NSObject
#end
#implementation T
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
NSBundle *bundleOfApp = [NSBundle mainBundle];
NSLog(#"%#", bundleOfApp.bundlePath);
//ours
T *t = [T new];
NSBundle *bundleOfT = [NSBundle bundleForClass:t.class];
NSLog(#"%#", bundleOfT.bundlePath);
if([bundleOfT.bundlePath hasPrefix:bundleOfApp.bundlePath]) {
NSLog(#"ours");
}
//theirs
bundleOfT = [NSBundle bundleForClass:NSString.class];
NSLog(#"%#", bundleOfT.bundlePath);
if([bundleOfT.bundlePath hasPrefix:bundleOfApp.bundlePath]) {
}
else {
NSLog(#"theirs");
}
}
}

In this case you have make instance of your class like and check it like
MyViewController *myVcntrl = [[MyViewController alloc]init];
if ([myVcntrl isKindOfClass:[MyViewController class]]) {
NSLog(#"Class is of MyViewController type");
}
else {
NSLog(#"Not of MyViewController type");
}

I was swizzling viewDidLoad, loadView and awakeFromNib for measuring load times and there is some strange behavior I've noticed.
For framework classes like UIInputWindowController, UIKeyboardCandidateGridCollectionViewController : awakeFromNib or loadView doesn't get called. They immediately appear after viewDidLoad has been called.
So the following lines of codes:
- (void)swizzled_awakeFromNib
{
NSLog(#"*** Awake from nib %#", NSStringFromClass(self.class));
[self swizzled_awakeFromNib];
}
- (void)swizzled_viewDidLoad
{
NSLog(#"*** View did load %#", NSStringFromClass(self.class));
[self swizzled_viewDidLoad];
}
- (void)swizzled_loadView
{
NSLog(#"*** Load view %#", NSStringFromClass(self.class));
[self swizzled_loadView];
}
Prints:
*** Awake from nib UINavigationController
*** View did load UINavigationController
*** Load view MyLoginViewController
*** View did load UIInputWindowController
*** View did load UIKeyboardCandidateGridCollectionViewController
*** View did load UIInputWindowController
*** View did load UIApplicationRotationFollowingControllerNoTouches
*** View did load MyLoginViewController
So maybe you can add a property to your category and set it to YES if awakeFromNib and/or loadView has called. Then check that bool in your viewDidLoad method to see if its a framework class.

Related

how to extends multiple classes in iOS

I am developing an application in a flutter, and I want to extend one library class in AppDelegate.swift file. As iOS is not supporting multiple inheritances I can't extend the library class and FlutterAppDelegate class together.
Can someone help me with this? Is there any other way to achieve this kind of function?
Here is some code snippet for ios
App's AppDelegate.swift
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return true
}
}
This is my library's header class which I also want to extends to AppDelegate
#import <UIKit/UIKit.h>
#interface AppDelegateSEG : UIResponder <UIApplicationDelegate>
#property(strong, nonatomic) UIWindow *window;
#end
This is my AppDelegateSEG.m
// AppDelegateSEG.m
// ObjcPodsSample
//
//
#import "AppDelegateSEG.h"
#import "SEGAppsFlyerIntegrationFactory.h"
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#interface AppDelegateSEG ()
#end
#interface AppDelegateSEG ()<SEGAppsFlyerLibDelegate>
#end
#implementation AppDelegateSEG
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// For ApsFlyer debug logs
[AppsFlyerLib shared].isDebug = YES;
// [[AppsFlyerLib shared] waitForATTUserAutxhorizationWithTimeoutInterval:60];
/*
Based on your needs you can either pass a delegate to process deferred
and direct deeplinking callbacks or disregard them.
If you choose to use the delegate, see extension to this class below
*/
// SEGAppsFlyerIntegrationFactory* factoryNoDelegate = [SEGAppsFlyerIntegrationFactory instance];
SEGAppsFlyerIntegrationFactory* factoryWithDelegate = [SEGAppsFlyerIntegrationFactory createWithLaunchDelegate:self];
SEGAnalyticsConfiguration *config = [SEGAnalyticsConfiguration configurationWithWriteKey:#"********"];
// [config use:factoryNoDelegate];
[config use:factoryWithDelegate]; // use this if you want to get conversion data in the app. Read more in the integration guide
config.enableAdvertisingTracking = YES; //OPTIONAL
config.trackApplicationLifecycleEvents = YES; //OPTIONAL
config.trackDeepLinks = YES; //OPTIONAL
config.trackPushNotifications = YES; //OPTIONAL
[SEGAnalytics debug:YES]; //OPTIONAL
[SEGAnalytics setupWithConfiguration:config];
NSLog(#"before delay");
NSString *userId = [[SEGAnalytics sharedAnalytics] getAnonymousId];
NSLog(#"----%#", userId);
[[SEGAnalytics sharedAnalytics] identify:(userId)];
// [[SEGAnalytics sharedAnalytics] identify:userId];
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Code below is to collect IDFA. Read more here - https://support.appsflyer.com/hc/en-us/articles/360011451918-iOS-SDK-V6-beta-integration-guide-for-developers#integration-34-support-apptrackingtransparency-att
// if (#available(iOS 14, *)) {
// [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
// //....
// }];
// }
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:#"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
-(void)onConversionDataFail:(NSError *) error {
NSLog(#"%#",error);
}
-(void)onConversionDataSuccess:(NSDictionary*) installData {
id status = [installData objectForKey:#"af_status"];
if([status isEqualToString:#"Non-organic"]) {
id sourceID = [installData objectForKey:#"media_source"];
id campaign = [installData objectForKey:#"campaign"];
NSLog(#"This is a none organic install. Media source: %# Campaign: %#",sourceID,campaign);
} else if([status isEqualToString:#"Organic"]) {
NSLog(#"This is an organic install.");
}
}
- (void) onAppOpenAttribution:(NSDictionary*) attributionData {
NSLog(#"%#",attributionData);
}
- (void) onAppOpenAttributionFailure:(NSError *)error {
NSLog(#"%#",error);
}
#end
Looks like you need only setup part, declared in didFinishLaunchingWithOptions. You need to create some other class in your PodsSample project where you can handle all setup. So when you need some necessary configs for AppsFlyer or SEGAnalytics, from anywhere you can just call this static (for example) method from your Pod project.
AppConfigProvider.h
#interface AppConfigProvider : NSObject
+ (void)setupConfig
#end
AppConfigProvider.m
#import "SEGAppsFlyerIntegrationFactory.h"
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#implementation AppConfigProvider
+ (void)setupConfig {
// For ApsFlyer debug logs
[AppsFlyerLib shared].isDebug = YES;
// [[AppsFlyerLib shared] waitForATTUserAutxhorizationWithTimeoutInterval:60];
/*
Based on your needs you can either pass a delegate to process deferred
and direct deeplinking callbacks or disregard them.
If you choose to use the delegate, see extension to this class below
*/
// SEGAppsFlyerIntegrationFactory* factoryNoDelegate = [SEGAppsFlyerIntegrationFactory instance];
SEGAppsFlyerIntegrationFactory* factoryWithDelegate = [SEGAppsFlyerIntegrationFactory createWithLaunchDelegate:self];
SEGAnalyticsConfiguration *config = [SEGAnalyticsConfiguration configurationWithWriteKey:#"********"];
// [config use:factoryNoDelegate];
[config use:factoryWithDelegate]; // use this if you want to get conversion data in the app. Read more in the integration guide
config.enableAdvertisingTracking = YES; //OPTIONAL
config.trackApplicationLifecycleEvents = YES; //OPTIONAL
config.trackDeepLinks = YES; //OPTIONAL
config.trackPushNotifications = YES; //OPTIONAL
[SEGAnalytics debug:YES]; //OPTIONAL
[SEGAnalytics setupWithConfiguration:config];
NSLog(#"before delay");
NSString *userId = [[SEGAnalytics sharedAnalytics] getAnonymousId];
NSLog(#"----%#", userId);
[[SEGAnalytics sharedAnalytics] identify:(userId)];
// [[SEGAnalytics sharedAnalytics] identify:userId];
}
#end
Something like this. Also you'll be need to add AppConfigProvider to public headers of your library to be visible outside

Is it bad design to set self.delegate = self

I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html

Is it possible to call a performSegueWithIdentifier from another class?

I just build a class to manage correctly my database and JSON request. The problem is that now, how can I perform the segue ?
Here is my code
In my view :
- (IBAction)loginClick:(id)sender
{
NSString *post = [NSString stringWithFormat:#"username=test&password=test"];
[[DataManagement sharedManager] WebServiceLogin:post];
}
- (void) showTypeView
{
[self performSegueWithIdentifier:#"showTypeView" sender:nil];
}
In my class :
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
...
switch ([[response valueForKey:#"success"] intValue])
{
case 0:
{
NSLog(#"error: %# error Description: %#", [response valueForKey:#"success"], [response valueForKey:#"error_message"]);
break;
}
case 1:
{
LoginViewController *showView = [LoginViewController new];
[showView showTypeView];
break;
}
default:
break;
}
...
}
When I launch, I have an error :
**
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<LoginViewController: 0x165afd30>) has no segue with identifier 'showTypeView''
*** First throw call stack:
(0x2592e2eb 0x250fadff 0x29e2b037 0xe1819 0xdb64f 0x25f64de1 0x25f64d99 0x25f64e8d 0x25e261ef 0x25edf04f 0xa77cab 0xa7f835 0x25e171e3 0x258415f9 0x25e170cb 0x25e16f95 0x25e16e29 0x258f1257 0x258f0e47 0x258ef1af 0x25841bb9 0x258419ad 0x26abbaf9 0x29b2dfb5 0xe3ea9 0x254f4873)
libc++abi.dylib: terminating with uncaught exception of type NSException
**
If you're using segueWithIdentifier then you need to already have the segue built in Storyboard and labeled correctly as "showTypeView". Otherwise you should use a navigation controller to push a view controller or use self presentViewController to show a modal view controller.
EDIT:
Building off of Larme's comment, you can build a delegate like this:
// In your class.h file
#property (weak, nonatomic)id<SegueDelegate> delegate;
// In class.m file
LoginViewController *showView = [LoginViewController new];
self.delegate = showView;
[self.delegate segue];
// In LoginViewController.h
#protocol SegueDelegate
-(void)segue;
#end
#interface LoginViewController: UIViewController <SegueDelegate>
-(void)segue;
#end
// In LoginViewController.m
#implementation LoginViewController
-(void)segue
{
[self performSegueWithIdentifier:#"showTypeView" sender:nil];
}
#end

Singleton class return nil value for its NSString property

I have a singleton class, and I have a property declared in it:
#property (nonatomic, strong) NSString *currentTableName;
+ (SuperNoteManager*)sharedInstance;
.m file:
+ (SuperNoteManager*)sharedInstance
{
static SuperNoteManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[SuperNoteManager alloc] init];
});
return _sharedInstance;
}
When I run my app for the first time, there is no data in the data base,so it shows the EmptyViewController.
#property (nonatomic, strong) SuperNoteManager *myManager;
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
_myManager=[SuperNoteManager sharedInstance];
}
-(void)changeRootView{
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
HomeViewController *hVC=[storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
UINavigationController *mNavVC=[storyboard instantiateViewControllerWithIdentifier:#"MainNavigationController"];
mNavVC.viewControllers=#[hVC];
[[UIApplication sharedApplication].keyWindow setRootViewController:mNavVC];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
There is + button on NavigationBar on EmptyNotesViewController, and on tap '+',
NotesViewController is pushed from EmptyNotesViewController.
In the NotesViewController, after I write some notes, I save the notes in database:
NotesViewController:
#property (nonatomic,strong) SuperNoteManager *myManager;
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
_myManager.currentTableName=#"WorkTable";
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
NSLog(#"going back");
[self insertTextintoDatabase]; //Text is inserted . I double checked
}
}
And then When I go back to my EmpytNotesViewController, I check for data, and if data is present, I change the rootViewController as it is not EmptyNotesView anymore.
So When I go back to my EmptyNotesViewController:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
//Put a breakpoint here
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
Here at the breakpoint _myManager.currentTableName is nil. why?
I set it in the NotesController, and it became nil when it come back to the EmptyNotesController.
I thought once a value is set in singleton, it will persist as long as the app is closed/killed.
Note: I have declared the property of my Singleton class as strong and also all the properties in the singleton are declared as strong.
It appears like you never get a reference to the SuperNoteManager singleton in NotesViewController, like you did in your EmptyNotesController.
Therefore the currentTableName property never gets set in the first place.
You want to insert:
_myManager = [SuperNoteManager sharedInstance];
in your -viewDidAppear: before you set the currentTableName property.

Why is a UINavigationController failing to take ownership of a block callback parameter using ARC

Given the following code example (iOS 7, Xcode 5):
/**
* SampleProvider Class
*/
typedef void(^RequestCallback)(UIViewController *result);
static NSString * const cControllerRequestNotification = #"controllerRequestNotification";
static NSString * const cRequestClassNameKey = #"className";
static NSString * const cRequestCallbackKey = #"callback";
#interface SampleProvider : NSObject
+ (void)requestControllerForClassName:(NSString *)className completion:(RequestCallback)callback;
#end
#interface SampleProvider ()
- (UIViewController *)controllerForClassName:(NSString *)className;
- (void)didReceiveControllerRequest:(NSNotification *)n;
#end
#implementation SampleProvider
#pragma mark - Overrides
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id)init {
self = [super init];
if( self ) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didReceiveControllerRequest:) name:cControllerRequestNotification object:nil];
}
return self;
}
#pragma mark - Public API
+ (void)requestControllerForClassName:(NSString *)className completion:(RequestCallback)callback{
NSDictionary *requestInfo = #{ cRequestClassNameKey : className, cRequestCallbackKey : [callback copy] };
[[NSNotificationCenter defaultCenter] postNotificationName:cControllerRequestNotification object:requestInfo];
}
#pragma mark - Private API
- (UIViewController *)controllerForClassName:(NSString *)className {
UIViewController *result = nil;
Class controllerClass = NSClassFromString(className);
if( (nil != controllerClass) && ([controllerClass isSubclassOfClass:[UIViewController class]]) ) {
result = [[controllerClass alloc] init];
}
return result;
}
- (void)didReceiveControllerRequest:(NSNotification *)n {
NSDictionary *requestInfo = [n object];
NSString *className = requestInfo[cRequestClassNameKey];
RequestCallback callback = requestInfo[cRequestCallbackKey];
UIViewController *result = [self controllerForClassName:className];
if( nil != callback ) {
callback(result);
}
}
#end
/**
* SampleViewController Class
*/
#interface SampleViewController : UIViewController
#end
#implementation SampleViewController
#pragma mark - Overrides
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSString *className = #"ClassName";
[SampleProvider requestControllerForClassName:className completion:^(UIViewController *result) {
if( nil != result ) {
// Result is valid pointer, not a zombie.
[self.navigationController pushViewController:result animated:YES];
// Result is released, not nil.
} else {
NSLog(#"Unable to load controller with class name: %#", className);
}
}];
}
#end
Why would my UINavigationController fail to take ownership of the callback controller, received by SampleProvider's public class method, even after showing the view?
I'm seeing the following behavior:
The new controller class is properly allocated and returned via the callback method. Upon entering the callback the result parameter is pointing to valid memory.
The new controller is pushed to my UINavigationController's navigation stack.
The newly pushed controller's "viewDidLoad" method is called.
When inspecting the UINavigationController's "viewControllers" property, the newly pushed controller is referenced in the array.
The newly push controller is is deallocated while UINavigationController pushViewController:animated: is still executing.
The new controller is now a zombie.
Thank you for any assistance.
I don't have a clearcut answer because the answer may be in code you haven't posted -- the code you have posted looks valid apart from two observations (which could lead you to an answer):
Should that isKindOfClass be isSubclassOfClass? -isKindOfClass: is an
instance method on NSObject, not a class method.
Calling pushViewController: synchronously during viewDidLoad seems
dangerous. It's quite possible that the state of the view hierarchy
is not stable at that time. That push should happen in response to
some other discrete event, I'd think. Try making that push (or the
entire requestControllerForClassName:) asynchronous via
dispatch_async, as a test, and see if that solves your problem.

Resources