我对iOS和Objective-C以及整个MVC范式都是新手,我一直坚持以下几点:

我有一个充当数据输入表单的视图,我想让用户选择多个产品。产品列在另一个具有UITableViewController的视图中,我已启用多个选项。

如何将数据从一个视图传输到另一个视图?我会将UITableView上的选择保存在一个数组中,但如何将其传递回上一个数据输入表单视图,以便在提交表单时将其与其他数据一起保存到核心数据?

我在网上冲浪,看到一些人在应用程序委托中声明了一个数组。我读了一些关于单态的东西,但我不明白这些是什么,我读了关于创建数据模型的东西。

正确的执行方式是什么?我将如何执行?


当前回答

在给出的许多答案中都有一些很好的信息,但没有一个完全解决这个问题。

该问题询问如何在视图控制器之间传递信息。给出的具体示例要求在视图之间传递信息,但考虑到iOS自称的新颖性,原始海报可能是指视图控制器之间,而不是视图之间(没有viewControllers的任何参与)。似乎所有的答案都集中在两个视图控制器上,但如果应用程序发展到需要在信息交换中包含两个以上的视图控制器,该怎么办?

最初的海报还询问了Singleton和AppDelegate的使用。这些问题需要回答。

为了帮助其他人看这个问题,谁想要一个完整的答案,我将尝试提供它。

应用场景

与其进行高度假设、抽象的讨论,不如考虑具体的应用。为了帮助定义两个视图控制器情形和两个以上视图控制器情形,我将定义两个具体的应用场景。

场景一:最多需要两个视图控制器共享信息。

见图一。

应用程序中有两个视图控制器。有一个ViewControllerA(数据输入表单)和一个ViewController B(产品列表)。产品列表中选择的项目必须与数据输入表单中文本框中显示的项目相匹配。在这种情况下,ViewControllerA和ViewControllerB必须彼此直接通信,而不能与其他视图控制器通信。

场景二:两个以上的视图控制器需要共享相同的信息。

见图二。

应用程序中有四个视图控制器。这是一个基于选项卡的应用程序,用于管理家庭库存。三个视图控制器显示相同数据的不同过滤视图:

ViewControllerA-奢侈品ViewControllerB-非保险项目ViewControllerC-整个住宅库存ViewControllerD-添加新项目表单

无论何时创建或编辑单个项目,它也必须与其他视图控制器同步。例如,如果我们在ViewControllerD中添加了一条船,但它还没有投保,那么当用户转到ViewControllerA(豪华物品)和ViewControllerC(整个住宅库存)时,船必须出现,但当用户转到ViewControllerB(非投保物品)时则不会出现。我们不仅需要关注添加新项目,还需要关注删除项目(可以从四个视图控制器中的任何一个中删除),或编辑现有项目(可以在“添加新项目表单”中允许,将其重新用于编辑)。

由于所有视图控制器都需要共享相同的数据,所以所有四个视图控制器都必须保持同步,因此,每当任何单个视图控制器更改基础数据时,都需要与所有其他视图控制器进行某种通信。很明显,在这种情况下,我们不希望每个视图控制器直接与其他视图控制器通信。如果不明显,考虑我们是否有20个不同的视图控制器(而不是4个)。每当一个视图控制器发生更改时,通知其他19个视图控制器中的每一个会有多困难和容易出错?

解决方案:代表和观察者模式,以及Singleton

在场景一中,我们有几个可行的解决方案,正如其他答案所给出的

赛格牌手表代表直接设置视图控制器上的财产NSUserDefaults(实际上是一个糟糕的选择)

在场景2中,我们还有其他可行的解决方案:

观察者模式单身者

单例是类的一个实例,该实例是其生命周期中唯一存在的实例。单例的名字来源于它是一个实例。通常,使用singleton的开发人员有特殊的类方法来访问它们。

+ (HouseholdInventoryManager*) sharedManager; {
    static dispatch_once_t onceQueue;
    static HouseholdInventoryManager* _sharedInstance;

    // dispatch_once is guaranteed to only be executed
    // once in the lifetime of the application
    dispatch_once(&onceQueue, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

现在我们了解了单例是什么,让我们讨论单例如何适合观察者模式。观察者模式用于一个对象响应另一个对象的变化。在第二个场景中,我们有四个不同的视图控制器,他们都想知道底层数据的更改。“底层数据”应该属于单个实例,即单例。“了解更改”是通过观察对单例所做的更改来实现的。

家庭库存应用程序将具有一个类的单个实例,该类旨在管理库存项目列表。经理将管理一系列家庭用品。以下是数据管理器的类定义:

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

@interface HouseholdInventoryManager : NSObject
/*!
 The global singleton for accessing application data
 */
+ (HouseholdInventoryManager*) sharedManager;


- (NSArray *) entireHouseholdInventory;
- (NSArray *) luxuryItems;
- (NSArray *) nonInsuredItems;

- (void) addHouseholdItemToHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) editHouseholdItemInHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) deleteHoueholdItemFromHomeInventory:(JGCHouseholdInventoryItem*)item;
@end

当主库存项的集合发生更改时,视图控制器需要了解此更改。上面的类定义并没有说明这将如何发生。我们需要遵循观察者模式。视图控制器必须正式遵守sharedManager。有两种方法可以观察另一个对象:

关键值观察(KVO)NSNotificationCenter。

在场景2中,我们没有使用KVO可以观察到的HouseholdInventoryManager的单个属性。因为我们没有一个易于观察的属性,所以在这种情况下,必须使用NSNotificationCenter实现观察者模式。四个视图控制器中的每一个都将订阅通知,sharedManager将在适当的时候向通知中心发送通知。库存管理器不需要知道任何关于视图控制器或任何其他类的实例的信息,这些类可能有兴趣知道库存项集合何时发生变化;NSNotificationCenter负责这些实施细节。视图控制器只需订阅通知,数据管理器只需发布通知。

许多初学者程序员利用了这样一个事实,即在应用程序的生命周期中始终只有一个应用程序委托,它是全局可访问的。初级程序员利用这一事实将对象和功能填充到appDelegate中,以方便从应用程序中的任何其他位置进行访问。仅仅因为AppDelegate是单例并不意味着它应该替换所有其他单例。这是一个糟糕的实践,因为它给一个类带来了太多负担,打破了良好的面向对象实践。每个类都应该有一个易于解释的明确角色,通常只需通过类的名称。

每当您的应用程序代理开始变得臃肿时,就开始将功能删除到单例中。例如,核心数据堆栈不应该留在AppDelegate中,而是应该放在它自己的类coreDataManager类中。

工具书类

管理视图控制器之间的数据流在视图控制器之间传递数据Objective-C中的异步JSON请求

其他回答

这个问题在Stack Overflow上似乎很流行,所以我想我会尝试给出一个更好的答案,以帮助像我这样在iOS世界起步的人。

转发数据

将数据从另一个视图控制器转发到视图控制器。如果您想将对象/值从一个视图控制器传递到另一个视图控件,并将其推送到导航堆栈,则可以使用此方法。

对于本例,我们将使用ViewControllerA和ViewControllerB

要将BOOL值从ViewControllerA传递到ViewControllerB,我们将执行以下操作。

在ViewControllerB.h中为BOOL创建一个属性@属性(非原子,赋值)BOOL是SomethingEnabled;在ViewControllerA中,您需要告诉它ViewControllerB,所以使用#import“ViewControllerB.h”

然后,如果要加载视图,例如didSelectRowAtIndex或某个IBAction,则需要在将其推到导航堆栈之前在ViewControllerB中设置属性。

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.isSomethingEnabled = YES;
    [self pushViewController:viewControllerB animated:YES];

这会将ViewControllerB中的isSomethingEnabled设置为BOOL值YES。

使用Segues转发数据

如果您使用的是情节提要,则很可能使用segue,需要使用此过程来传递数据。这与上述类似,但不是在推送视图控制器之前传递数据,而是使用名为

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

因此,要将BOOL从ViewControllerA传递到ViewControllerB,我们将执行以下操作:

在ViewControllerB.h中为BOOL创建一个属性@属性(非原子,赋值)BOOL是SomethingEnabled;在ViewControllerA中,您需要告诉它ViewControllerB,因此使用#import“ViewControllerB.h”在情节提要上创建从ViewControllerA到ViewControllerB的片段,并为其提供标识符。在本例中,我们将其称为“showDetailSegue”接下来,我们需要将该方法添加到ViewControllerA,在执行任何segue时调用该方法。因此,我们需要检测调用了哪个segue,然后采取措施。在我们的示例中,我们将检查“showDetailSegue”,如果执行了,我们将把BOOL值传递给ViewControllerB-(void)准备segue:(UIStoryboardSegue*)segue发件人:(id)发件人{if([segue.identifier isEqualToString:@“showDetailSegue”]){ViewControllerB*控制器=(ViewControllerB*)segue.destinationViewController;controller.isSomethingEnabled=是;}}

如果在导航控制器中嵌入了视图,则需要将上面的方法稍微更改为以下方法

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
            controller.isSomethingEnabled = YES;
        }
    }

这会将ViewControllerB中的isSomethingEnabled设置为BOOL值YES。

传回数据

要将数据从ViewControllerB传递回ViewControllerA,需要使用协议和委托或块,后者可以用作回调的松耦合机制。

为此,我们将使ViewControllerA成为ViewControllerB的委托。这允许ViewControllerB向ViewControllerA发回消息,使我们能够发回数据。

要使ViewControllerA成为ViewControllerB的委托,它必须符合我们必须指定的ViewControllerC协议。这告诉ViewControllerA它必须实现哪些方法。

在ViewControllerB.h中,在#import下方,但在@interface上方,指定协议。@类ViewControllerB;@协议ViewControllerBDLegate<NSObject>-(void)addItemViewController:(ViewControllerB*)控制器didFinishEnteringItem:(NSString*)项;@完接下来仍然在ViewControllerB.h中,您需要设置委托属性并在ViewController B.m中进行合成@属性(非原子,弱)id<ViewControllerBDLegate>委托;在ViewControllerB中,当我们弹出视图控制器时,我们调用代理上的消息。NSString*itemToPassBack=@“将此值传递回ViewControllerA”;[self.delegate addItemViewController:self-didFinishEnteringItem:itemToPassBack];这就是ViewControllerB。现在在ViewControllerA.h中,告诉ViewControllerA导入ViewControllerB并遵守其协议。#import“ViewControllerB.h”@界面ViewControllerA:UIViewController<ViewControllerBDLegate>在ViewControllerA.m中,根据我们的协议实现以下方法-(void)addItemViewController:(ViewControllerB*)控制器didFinishEnteringItem:(NSString*)项{NSLog(@“这是从ViewControllerB%@返回的”,项);}在将viewControllerB推送到导航堆栈之前,我们需要告诉viewControllerB ViewControllerA是它的委托,否则我们将得到一个错误。ViewControllerB*ViewControllerB=[[ViewControllerB alloc]initWithNib:@“ViewControllerA”捆绑包:nil];viewControllerB.delegate=自身[[self-navigationController]pushViewController:viewControllerB动画:是];


工具书类

在《视图控制器编程指南》中使用委派与其他视图控制器通信代理模式

NS通知中心

这是传递数据的另一种方式。

// Add an observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
    id someObject = notification.object // Some custom object that was passed with notification fire.
}

// Post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

将数据从一个类传递回另一个类(类可以是任何控制器、网络/会话管理器、UIView子类或任何其他类)

块是匿名函数。

此示例将数据从控制器B传递到控制器A

定义块

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

添加块处理程序(侦听器)

需要值的地方(例如,需要ControllerA中的API响应,或者需要a上的ContorlerB数据)

// In ContollerA.m

- (void)viewDidLoad {
    [super viewDidLoad];
    __unsafe_unretained typeof(self) weakSelf = self;
    self.selectedVoucherBlock = ^(NSString *voucher) {
        weakSelf->someLabel.text = voucher;
    };
}

转到控制器B

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
    [self.navigationController pushViewController:vc animated:NO];

防火砖

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
    NSString *voucher = vouchersArray[indexPath.row];
    if (sourceVC.selectVoucherBlock) {
        sourceVC.selectVoucherBlock(voucher);
    }
    [self.navigationController popToViewController:sourceVC animated:YES];
}

块的另一个工作示例

使用通知中心将数据从一个视图传递到另一个视图。

观察者-收听者模式效果最好。另一种解决方法是在两个类中创建相同的对象。

在类1中创建类2对象。访问要传递的数据对象,设置它们,然后按下视图控制器。

共享数据有多种方法。

您始终可以使用NSUserDefaults共享数据。设置要与所选键共享的值,并在下一个视图控制器中从与该键关联的NSUserDefault获取值。[[NSUserDefaults standardUserDefaults]setValue:key:key的值][[NSUserDefaults standardUserDefaults]objectForKey:key]您只需在viewcontrollerA中创建一个属性。在viewcontrollerB中创建viewcontrollerA的对象,并为该属性分配所需的值。您也可以为此创建自定义代理。

我在这个解决方案中搜索了很长时间,终于找到了它

@interface SecondViewController: UIviewController
{
    NSMutableArray *myAray;
    CustomObject *object;
}

现在在实现文件中,为这些对象分配内存,如下所示:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self)
     {
         // Custom initialization
         myAray=[[NSMutableArray alloc] init];
         object=[[CustomObject alloc] init];
     }
     return self;
}

现在您已经为数组和对象分配了内存。现在,您可以在推送此ViewController之前填充该内存。

转到SecondViewController.h并编写两个方法:

-(void)setMyArray:(NSArray *)_myArray;
-(void)setMyObject:(CustomObject *)_myObject;

在实现文件中,您可以实现以下功能:

-(void)setMyArray:(NSArray *)_myArray
{
     [myArra addObjectsFromArray:_myArray];
}

-(void)setMyObject:(CustomObject *)_myObject
{
     [object setCustomObject:_myObject];
}

期望CustomObject必须具有setter函数。

现在你的基本工作完成了。转到要推动SecondViewController的位置,执行以下操作:

SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]] ;
[secondView setMyArray:ArrayToPass];
[secondView setMyObject:objectToPass];
[self.navigationController pushViewController:secondView animated:YES ];

注意拼写错误。

嗯,我们有几种方法可以使用委派系统或使用故事板Segue:

使用setter和getter方法,如viewController.h@属性(保留,非原子)NSString*str;现在,在viewController.m中@合成str;这里我有一个PDF URL和一个segue到另一个viewController,像这样,pdfObject是我的pdfModel。它基本上是一个NSOBJECT类。str=[NSString stringWithFormat:@“%@”,pdfObject.objPath];NSLog(@“pdfUrl:***:%@:***:”,pdfUrl);[self-performSegueWithIdentifier:@“programPDFViewController_segue”sender:self];#pragma标记-导航//在基于情节提要的应用程序中,您通常需要在导航之前做一些准备-(void)准备segue:(UIStoryboardSegue*)segue发件人:(id)发件人{if([[segue identifier]isEqualToString:@“programPDFViewController_segue”]){programPDFViewController*pdfVC=[segue destinationViewController];[pdfVC setRecivedPdfUrl:str];}}现在,我成功地接收了我的PDF URL字符串和其他ViewController,并在webview中使用该字符串。。。在处理这样的委托时,我有一个NSObject实用程序类,其中包含我的dateFormatter、sharedInstance、EscapeWhiteSpaceCharacters、convertImageToGrayScale等方法,以及我在整个应用程序中使用过的其他方法,现在在文件实用程序中使用。在这种情况下,您不需要在每次将数据从一个视图控制器解析到另一个视图时都创建变量。有一次,您在fileutility.h中创建了一个字符串变量。只需将其设为零并再次使用。@接口实用程序:NSObject文件实用程序.h:+(实用程序*)共享实例;@属性(非原子,保留)NSString*strUrl;现在在文件实用程序.m中:@实施实用程序+(实用程序*)共享实例{静态实用程序*sharedObj=nil;如果(sharedObj==nil){sharedObj=[[utilities alloc]init];}返回sharedObj;}现在完成了,请访问文件firstViewController.m并调用委托NSString*str=[NSString stringWithFormat:@“%@”,pdfObject.objPath];[连接共享实例].strUrl=nil;[连接共享实例].strUrl=str;现在直接转到文件secondViewController.m,在不创建变量的情况下使用它我所做的一切:-(无效)视图将出现:(BOOL)动画{[超级视图将出现:是];[self-webViewMethod:[Connection sharedInstance].strUrl];}-(void)WebViewMethod:(NSString)Url{//使用webview。享受编码:D}

这种委托工作在内存管理方面是可靠的。