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需要声明cardParams
和tokenChar
才能编译。
是的,从来没有想过通知,我认为它仍然是一个不错的选择,而且卡参数的好点,从来没有注意到它大声笑【参考方案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 远程配置获取值永远不会完成