Objective-C 完成调用永远不会完成

Posted

技术标签:

【中文标题】Objective-C 完成调用永远不会完成【英文标题】:Objective-C completion call never completing 【发布时间】:2018-06-27 03:38:33 【问题描述】:

我正在尝试在阻塞调用中发送一个实例并等待它完成,以便稍后在我的程序中使用该值,但完成函数永远不会完成。我对 Objective-C 非常陌生,我只使用 Objective-c 作为包装器,我无法弄清楚为什么我的完成调用永远不会完成。

[sharedClient createTokenWithCard:cardParams completion:^(STPToken *token,NSError *error) 
    if (token == nil || error != nil) 
        tokenChar = [error.localizedDescription UTF8String];
    
    else
        tokenChar = [token.tokenId UTF8String];
    
];
while(tokenChar == nil)

return tokenChar;

所以现在我把我的方法改成了这个

void StripeWrapper::retrieveToken:(id)(char* myKey, char* cardNumber, int expMonth, int expYear, char* cvc) completion:(void (^)(NSString *))completion 
    NSString* NScardNumber = [NSString stringWithUTF8String:cardNumber];
    NSString* NScvc = [NSString stringWithUTF8String:cvc];

    STPCardParams *cardParams = [[STPCardParams alloc] init];
    cardParams.number = NScardNumber;
    cardParams.expMonth = expMonth;
    cardParams.expYear = expYear;
    cardParams.cvc = NScvc;

    NSString *myPublishableKey = [NSString stringWithUTF8String:myKey];
    STPAPIClient *sharedClient = [[STPAPIClient alloc] initWithPublishableKey:myPublishableKey];

    [sharedClient createTokenWithCard:cardParams completion:^(STPToken *token,NSError *error) 
        NSString *tokenChar;
        if (token == nil || error != nil) 
            tokenChar = [error.localizedDescription UTF8String];
         else 
            tokenChar = [token.tokenId UTF8String];
        
        if (completion) completion(tokenChar);
    ];

【问题讨论】:

不要那样做。使用完成块中的值。您将阻塞主线程,这将使您无响应。 我可以想到两种方法来处理这种情况。 1)调度组等待。 2)创建一个处理回调的函数,并从您的 createTOkenWithCard 方法调用整个函数的完成。 要确定为什么永远不会调用完成,我们需要createTokenWithCard 的代码,因为这是负责调用完成块的人。 【参考方案1】:

自从我处理objective-c以来已经有一段时间了,如果我犯了一些语法错误,请原谅我。

// This just uses completion block and will call the completion once the createTokenWithCard function finishes its execution
- (void) someFunctionName: (void (^)(NSString * theThingIWant, NSError *error)) completion  

         [sharedClient createTokenWithCard:cardParams completion:^(STPToken *token,NSError *error) 
                      if (token == nil || error != nil) 
                           tokenChar = [error.localizedDescription UTF8String];
                        else
                              tokenChar = [token.tokenId UTF8String];
                      
                     completion(tokenChar, error)
          ];   

// And in your caller its like
   [self someFunctionName:^(NSString *some, NSError *error) 
         // do the thing you want here
   ];



// Second approach will be dispatches. This will wait for createTokenWithCard before it returns
 - (void) someFunctionName: (id) cardParams  
         __block NSString * theThingYouNeed;
         dispatch_group_t someGroupName = dispatch_group_create();

         [sharedClient createTokenWithCard:cardParams completion:^(STPToken *token,NSError *error) 
                      if (token == nil || error != nil) 
                           theThingYouNeed = [error.localizedDescription UTF8String];
                        else
                              theThingYouNeed = [token.tokenId UTF8String];
                      
                     dispatch_group_leave(someGroupName);
          ];

        // this will wait forever so try to have a timeout I guess.
        dispatch_group_wait(someGroupName,DISPATCH_TIME_FOREVER);
        return theThingYouNeed

【讨论】:

我看到我们几乎在同一时间回答了相同的问题。我会留下我的,因为它在几个方面是不同的:(a)在出现错误的情况下,OP 会用错误重载 tokenChar,(b)我添加 NSNotification 作为替代方案。 (KVO 也是一种替代方案)。 小点,someFunctionName需要声明cardParamstokenChar才能编译。 是的,从来没有想过通知,我认为它仍然是一个不错的选择,而且卡参数的好点,从来没有注意到它大声笑【参考方案2】:

异步方法有一种激增的方式,通常也迫使它们的调用者异步。也就是说,如果methodA的结果依赖于methodB,而methodB是异步的,那么methodA一定也是。

所以包含 OP 代码的方法可能应该这样声明:

- (void)getMyTokenChar:(id)someParams completion:(void (^)(NSString *))completion 
    // form cardParams with someParams (or maybe they are the same
    [sharedClient createTokenWithCard:cardParams completion:^(STPToken *token,NSError *error) 
        NSString *tokenChar;
        if (token == nil || error != nil) 
            tokenChar = [error.localizedDescription UTF8String];
         else 
            tokenChar = [token.tokenId UTF8String];
        
        if (completion) completion(tokenChar);
    ];

调用者会这样做...

[theTokenCharObject getMyTokenChar:@"someParams" completion:^(NSString *tokenChar) 
    // tokenChar will be a string or an error description here
];

这是一个坏消息,包含该调用代码的方法也可能需要异步。它永远不会结束吗?

是的,它结束了,通常在 UI...

// do something to the UI to say the app is busy, like an activity indicator view
[theTokenCharObject getMyTokenChar:@"someParams" completion:^(NSString *tokenChar) 
    // remove the activity indicator view
    // show something new to the user: "we got the thing that depends on tokenChar!!"
];

有几个选择,最简单的描述使用 NSNotificationCenter。你原来的方法是(似乎)同步的......

- (void)getMyTokenChar:(id)someParams 
    // form cardParams with someParams (or maybe they are the same
    [sharedClient createTokenWithCard:cardParams completion:^(STPToken *token,NSError *error) 
        NSString *tokenChar;
        if (token == nil || error != nil) 
            tokenChar = [error.localizedDescription UTF8String];
         else 
            tokenChar = [token.tokenId UTF8String];
        
        [[NSNotificationCenter defaultCenter] postNotificationName:@"TokenGetterDidGetToken" object:tokenChar];
    ];

您应用的任何其他部分都像这样订阅...

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didGetToken:) name:@"TokenGetterDidGetToken" object:nil];

并且必须实现选择器...

- (void)didGetToken:(NSNotification *)notification 
    // notification.object will be the tokenChar

不过,一般来说,你最好不要绕过障碍物。以我给出的 UI 示例为例。一部分代码会将 UI 更改为繁忙,另一部分未连接的部分将不得不将其更改回来。

【讨论】:

您好,感谢您的回复。抱歉,我是全新的,obj-c 看起来与 c++ 和 java 大不相同。我的格式应该是这样的吗? (见上文) 我认为可能会遇到问题,因为我正在通过 cython 运行这个包装器。我觉得我要做无尽的完成块。如果我通过 cython 调用最后一个完成块是否足够? 格式是个人喜好问题。我的被​​普遍接受(虽然在某些地方有点特殊,例如许多作者说SomeClass*,我说SomeClass *)。我从未使用过 cython,但这取决于 python 调用者是否需要返回给它的异步方法的结果。如果是这样,那么整个链需要异步。

以上是关于Objective-C 完成调用永远不会完成的主要内容,如果未能解决你的问题,请参考以下文章

Swift/iOS - Firebase 远程配置获取值永远不会完成

XCUIApplication().launch() 永远不会完成

如何在Playground中运行异步回调

动画中断时未调用 UIView 动画完成块

如何在 Playground 中运行异步回调

RxJS 订阅永远不会结束