无法从SLRequest块内部更改__block变量

时间:2013-02-11 03:24:53

标签: objective-c xcode scope objective-c-blocks block

我正在尝试更改块内int的值。 (在这个代码示例中,SLRequest本身只是来自我的完整应用程序的上下文设置,并且预计不会在这里工作 - 只需编译并运行正常,以便我可以测试设置int。社交框架需要包含在项目中编译的例子。输出如下。)

#import "MainViewController.h"
#import <Social/Social.h>

@interface MainViewController ()

@end

@implementation MainViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // This SLRequest stuff is just to provide the context for the block in question below
    NSString * theURLstr = @"http://someurl";
    NSURL * theNSURL = [[NSURL alloc] initWithString:theURLstr];
    SLRequest *theRequest = [SLRequest requestForServiceType: SLServiceTypeFacebook requestMethod:SLRequestMethodGET URL:theNSURL parameters:nil];
    [theRequest setAccount:nil]; // 

    // This is our test value.  We expect to be able to change it inside the following block
    __block int test = 44;
    NSLog(@"(BEFORE block) test is: %d", test);

    [theRequest performRequestWithHandler:
     ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error2)
     {
         // We get here late.
         test = 27;
         NSLog(@"(INSIDE block) test is: %d", test);

     }];
    // We get here fine, but test isn't changed
    NSLog(@"(AFTER block) test is: %d", test); 
}

// --- 输出:

2013-02-10 19:13:53.283 debugExp1[92743:c07] (BEFORE block) test is: 44
2013-02-10 19:13:53.285 debugExp1[92743:c07] (AFTER block) test is: 44
2013-02-10 19:13:53.312 debugExp1[92743:1303] (INSIDE block) test is: 27

注意无序输出线。它在执行中间之前开始并结束。我怎样才能解决这个问题?我只是想改变该位置的int值,并坚持在块外使用。

=== UPDATE ===

我在网上找到了这个问题的多个切向版本,但是我的目的是无法使用的形式。所以也许我以一种无法回答的方式提出我的问题。让我尝试添加一些上下文。

我的应用程序正在汇集来自Twitter指定的不同类型的多个Twitter API调用的数据。我不是在建造桌子进行展示。我只想要ivars中的数据并最终管理对象。该应用程序除了获取Twitter数据之外还有很多其他功能,因此我不想在单个performRequestWithHandler块中放置太多的应用程序结构,这样我就可以在某个时刻访问其返回的数据。对我来说,从网络获取数据是一种实用操作,而不是唯一的应用程序设计决定因素。我的一些API调用特别依赖于从早先按特定顺序执行的其他类型的API调用中收集的数据。

我需要知道之前的调用何时完成,所以我可以使用我从数据对象设置的ivar值。

也就是说,我需要一种方法来调用API调用#1,将各种返回数据分配给ivars,使用performRequestWithHandler块之外的ivars执行其他操作,根据内容执行API调用#2和#3等调用#1和后续API调用返回,并最终随机播放到其他viewControllers,以多种方式显示来自多个API调用的返回值的已处理数据。

我完全得到了performRequestWithHandler在一个未指定的线程上执行,该线程将被执行,我不知道何时。由于同步网络访问延迟,我并不担心UI长时间等待,因为我打算指定非常短的等待时间。我不打算让我的用户坐在那里等待网络连接速度慢!如果连接速度很慢,我会短暂显示一个活动指示器并放弃快速等待以显示警报。应用程序的这一部分不是我在这里的重点挑战。

我的挑战是在部分或全部完成后,在一个地方中汇总来自多个performRequestWithHandler调用的返回数据。

在我看来,performRequestWithHandler可能是一个太高级别的调用来实现我需要的那种同步控制和数据访问。我不知道。

上面的代码示例是我能想到的最简单的方法,以一种简单的方式来编译和演示我的问题。到目前为止,我尝试了许多变种但没有快乐。

希望这有助于澄清我正在使用的设计理念。

感谢您提供的任何具体帮助,线索或想法。在这一点上,一个微不足道的工作代码示例将是天赐之物,但是如果/当我找到它时,我会继续研究并发布解决方案。

3 个答案:

答案 0 :(得分:3)

我认为你已经回答了你自己的问题,正如你所看到的那样,你在块执行之前正在读取__block变量(两次)。

这是您正在调用的异步方法。它将在viewDidLoad返回后的某个时间完成。所以日志输出完全不是顺序输出。你所看到的是这些电话如何发挥的现实。

您应该考虑您的设计,并意识到此请求可能需要很长时间才能完成,尤其是当用户处于慢速网络等时。因此,您需要在请求时决定向用户提供的内容在飞行中,然后在请求最终完成时如何更新用户。

修改

从你的评论中,让我试着给你一个小例子,希望能让你解开。我不一定在这里为你写一个完整的工作示例,它考虑了所有可能的并发性,内存管理和设计含义。但是,一个简单的方法就是安排一个只更新视图控制器上某些属性的块(假设你的视图控制器已经存在足够长的时间以使请求完成或失败!)

[theRequest performRequestWithHandler:
 ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error2)
 {
     int test = 27;
     NSLog(@"(INSIDE block) test is: %d", test);

     // now schedule a block on the main queue which will update our UI with this
     // new data "27"
     dispatch_async(dispatch_get_main_queue(), ^{
         // here we are on the main queue, ready to read our test and do something with it.
         NSLog(@"(updating the UI) test is: %d", test);

         // now that we know we can read the data, we can save that data, update the UI, do
         // whatever is appropriate for your app
         // maybe this is as simple as:
         self.someProperty = test;

         // yes, there is a possible retain cycle here. Worry about that after
         // you have the basic mechanics of this understood.

     });

 }];

我希望指出你正确的方向。除此之外,我建议有很多好的课程(免费和付费)可以帮助你理解这些对于移动开发非常重要的基本概念(在Android iOS上)。处理异步方法和事件对于移动设备上的“hello world”以外的任何事情都是生活中的事实。

另一个编辑

我看到您对问题的新修改。这真的有助于解释这里的背景。如果你发生了很多不同的异步调用,那么你可能想要从视图控制器和一些数据控制器中获取它,这些数据控制器完成所有这些工作并组装你的结果。在这种情况下,相同的基本原则适用,你只需要更多的电话和更多的顺序来思考。

答案 1 :(得分:1)

根据Firoze对NSNotification的建议,我提出了一个解决方案。在这个版本中,我使用的是字符串而不是int,但是对于我的测试目的而言,这是相同的。 NSNotification实际上比我希望的要好!它将一个对象从一个线程传输到另一个线程的能力将会有很大的帮助,我只在这里使用nil。输出低于。

- (void)viewDidLoad
{
[super viewDidLoad];

// Test SLRequest wireframe
NSString * theURLstr = @"http://someurl";
NSURL * theNSURL = [[NSURL alloc] initWithString:theURLstr];
SLRequest *theRequest = [SLRequest requestForServiceType: SLServiceTypeFacebook requestMethod:SLRequestMethodGET URL:theNSURL parameters:nil];
[theRequest setAccount:nil];

// Set default
self.myNewTestStr = @"default";
NSLog(@"In viewDidLoad, self.myNewTestStr DEFAULT is %@", self.myNewTestStr);

// Listen on "weGotOurValue"
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(gotOurValue) name:@"weGotOurValue" object:nil];
// [ActivityAlert presentWithText:@"Please wait"];

// Pretend to get some data from the net
[theRequest performRequestWithHandler:
 ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error2)
 {
     // Not sure I need this dispatch, btw
     dispatch_async(dispatch_get_main_queue(), ^{

         // Change the ivar
         self.myNewTestStr = @"changed";

         // Post notification "weGotOurValue" 
         [[NSNotificationCenter defaultCenter] postNotificationName:@"weGotOurValue" object:nil];
         // [ActivityAlert dismiss];
     });
 }];
}

// Called by NSNotificationCenter when it receives
// a postNotificationName:@"weGotOurValue" message
- (void) gotOurValue {
    NSLog(@"In gotOurValue, self.myNewTestStr is %@", self.myNewTestStr);
}

输出:

2013-02-12 17:44:52.300 debugExp1[5549:c07] In viewDidLoad, self.myNewTestStr DEFAULT is default
2013-02-12 17:44:52.354 debugExp1[5549:c07] In gotOurValue, self.myNewTestStr is changed

答案 2 :(得分:-1)

- (无效)reloadProfile {

// 1

SLRequest *request = [SLRequest
                      requestForServiceType:SLServiceTypeFacebook
                      requestMethod:SLRequestMethodGET
                      URL:[NSURL URLWithString:@"https://graph.facebook.com/me"]
                      parameters:@{ @"fields" : @"id,first_name,last_name,gender,birthday,email,picture"}];

// 2
request.account = [FacebookController sharedFacebookController].facebookAccount;
// 3
__block NSDictionary *jsonDictD;
[request performRequestWithHandler:^(NSData *responseData,
                                     NSHTTPURLResponse *urlResponse, NSError *error) {
    if (error)
    {
        // 4
        [AppHelper showAlertViewWithTag:123 title:APP_NAME message:@"There was an error reading your Facebook account." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
    }
    else
    {
        // 5
        NSString *decodedString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
        // NSLog(@"%@",decodedString);
        jsonDictD = [decodedString JSONValue];
        NSLog(@"%@",jsonDictD);
        if (jsonDictD)
        {
            [AppHelper saveToUserDefaults:[jsonDictD objectForKey:@"email"] withKey:@"email"];
            [AppHelper saveToUserDefaults:[jsonDictD objectForKey:@"id"] withKey:@"facebook_id"];


            [userImage setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[[[jsonDictD objectForKey:@"picture"] objectForKey:@"data"] objectForKey:@"url"]]]
                             placeholderImage:[UIImage imageNamed:@"DefaultPic.png"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
             {

                 userImage.image=image;
                 dummyImg=image;
                 [dummyImg retain];

             }
            failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
             {

             }];

            self.firstNameTextField.text=[jsonDictD objectForKey:@"first_name"];
            self.lastNameTextField.text=[jsonDictD objectForKey:@"last_name"];
            self.emailTextField.text=[jsonDictD objectForKey:@"email"];

            self.dobTextField.text=[jsonDictD objectForKey:@"birthday"];

            if ([[jsonDictD objectForKey:@"gender"] isEqualToString:@"male"])
            {
                gender=@"Male";
            }
            else
            {
                gender=@"Female";
            }
            //  [[NSNotificationCenter defaultCenter] postNotificationName:@"Get FB" object:nil];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
                [[DubitAppDelegate getAppDelegate] hideActivityViewer];


            });
        }
    }
}];

}