【问题标题】:Asynchronous initialization in Objective-CObjective-C 中的异步初始化
【发布时间】:2013-07-13 20:12:47
【问题描述】:

我目前正在为 API 制作一个 Objective-C 库。为了轻松管理数据,我创建了一些充当模型的类。

例如,我有一个Account 类,其中包含有关一个特定帐户的所有数据。我想让这个类的初始化变得容易,我想到了这样的事情:

@interface Account : NSObject

@property (nonatomic, readonly) NSUInteger accountID;
@property (nonatomic, readonly) NSString *username;

// Other properties...

+ (instancetype)accountWithUsername:(NSString *)username success:(void (^)(Account *))success failure:(void (^)(NSError *))failure;
- (instancetype)initWithUsername:(NSString *)username success:(void (^)(Account *))success failure:(void (^)(NSError *))failure;

@end

 

@implementation Account

+ (instancetype)accountWithUsername:(NSString *)username success:(void (^)(Account *))success failure:(void (^)(NSError *))failure
{
    return [[JPImgurAccount alloc] initWithUsername:username success:success failure:failure];
}

- (instancetype)initWithUsername:(NSString *)username success:(void (^)(Account *))success failure:(void (^)(NSError *))failure
{
    self = [super init];

    // Launch asynchronous requests, the callback will be called when it's finished

    return self; // Returning an empty object until the asynchronous request is finished
}

@end

但是,通过init 方法返回一个空对象让我有点困扰,我问自己这是否是一个好主意,但我不知道为什么它可能有风险。

所以我问你:我可以使用这个结构吗?如果不是,为什么?我应该以经典方式将单个 init 方法与 loadWithUsername:(NSString *)username success:(void (^)(JPImgurAccount *))success failure:(void (^)(NSError *))failure 方法耦合吗?

谢谢。

【问题讨论】:

  • 您能更具体地说明您的担忧吗?
  • 我无法解释为什么它会这样困扰我。我搜索了以这种方式编写的源代码,但我没有找到一些,所以我认为如果没有人这样做,那可能是因为背后有一个很好的理由......

标签: objective-c asynchronous initialization


【解决方案1】:

我不认为你的方法是好的做法。作为 API 的用户,当我看到一个以“init”开头的方法时,我希望返回的对象可以立即使用。 看起来您需要使用工厂设计模式。使用此方法创建一个 AccountFactory 类:

+ (void)createAccountWithUsername:(NSString*)username success:(void (^)(Account *))success failure:(void (^)(NSError *))failure;

请注意,它返回 void,因此用户了解该对象仅在请求成功完成后才可用。此外,用户了解他不应该直接创建 Account 实例。 这就是我会使用的方法。

【讨论】:

  • 这似乎是一个更好的解决方案,但我真的需要创建一个AccountFactory 类吗?我不能重用accountWithUsername:success:failurevoid 作为返回值吗?使用工厂有优势吗?
  • @Nesk 分别是否、是和否。在 Objective-C 中使用类方法来创建对象是很常见的,但我想不出任何时候在 Obj-C 中看到过一个单独的工厂类。
  • 您没有来创建工厂,但是由于您正在编写其他开发人员将使用的 API,因此您应该尽可能提供信息。一个名为AccountFactory 的类让开发人员对这个类的用途毫无疑问。对于名为 Account 的类,开发人员需要阅读一些 cmets 或查看示例,然后才能了解他不应该创建 Account 实例。
  • 工厂设计模式的问题是我会有其他类,如AlbumImage,如果我必须为每个类创建一个工厂,那就有点多。我有两个想法,请告诉我你对它们的看法。首先,创建一个具有初始化各种类的方法的通用工厂,例如createAccountWithUsername:success:failurecreateAlbumWithID:success:failure... 或者避免使用工厂但创建一个类似loadWithUsername:success:failure 的方法在[[Account alloc] init] 之后使用,这样开发人员就会知道该对象init 返回的不完整。
  • 选择第一个选项。为您需要创建的每种类型的对象创建一个具有一种(或多种)方法的通用工厂。不要将“待处理”对象返回给用户并希望他了解如何处理它。
【解决方案2】:

但是,通过 init 方法返回一个空对象让我有点困扰,我问自己这是否是一个好主意,但我不知道为什么会有风险。

你是对的。这是个坏主意。很多人会立即使用结果而不了解它的初始化是异步发生的。另一个问题是,如果您返回一个对象,他们会认为它是有效的。因此,如果帐户最终无法初始化,您的客户可能不会检查它是否已正确初始化。这也使得客户端代码的执行变得不必要地奇怪。

Apple 框架中使用的惯用语通常是 Request。所以有一些使用这种设计的类,你可以寻找指导,以便 API 更熟悉(它们的名称包含“请求”)。有些人会使用委托,但块通常更可取——尤其是对于像这样简单的事情。

使用这种方法,您可以使客户端可以(合理地)访问Account 实例的唯一方法是通过AccountRequest API。

【讨论】:

    【解决方案3】:

    我会使用一个类方法,类似于你的(有两个块,一个代表成功,一个代表失败),但省略了返回类型。这种语法符合普通的 Objective-C 风格。例如,您可以:

    + (void)loadAccountWithUsername:(NSString *)username success:(void (^)(Account *))success failure:(void (^)(NSError *))failure
    

    在空对象上使用 'void' 是非常可取的,因为返回一个没有任何用途的对象是多余且毫无意义的。要向用户重申无法以“传统”方式(通过 [[Account alloc] init])初始化 Account 类,您可以通过类方法使 init 抛出异常,描述所需的初始化流程。

    【讨论】:

      【解决方案4】:

      您可以使用这种结构,但大多数人使用 Key Value Observing 来通知感兴趣的类对属性的更改。您仍然会在初始化期间发出异步请求。但不是稍后在回调中返回该类,您只需在请求完成时发出更改属性的通知。

      【讨论】:

        猜你喜欢
        • 2013-03-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-04
        • 1970-01-01
        • 2022-06-13
        相关资源
        最近更新 更多