2015年7月23日 星期四

iOS筆記:Multithreading & UIScrollView

Multithreading

Queues

Multithreading is mostly about “queues” in iOS.! Blocks are lined up in a queue (method calls can also be enqueued). 排隊等著被執行, 有一次取出一個block的. 也有一次讓多個block同時執行,彼此之間還會share一些資訊.

Main queue

All UI activity MUST occur on this queue and this queue only.
平常不會將要執行很久的code放在main queue. 因為我們不想讓他被block住. 大部份UIKit的code you just call them only on the main queue. 如果在其他queue使用有可能不能正常運作(主要是指關於可以跟screem同步或是改變的操作).
enter image description here

dispatch_async V.S. dispatch_sync

async: return after task is added to queue.
sync: return after task is done.

Example of an iOS API that uses multithreading!

下載網路上某個URL對應的內容. 不會在main queue執行而是另一個queue, 因為怕要下載過長時間, 但下載完之後會通知.

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL urlWithString:@“http://...”]];
  NSURLConfiguration *configuration = ...;
  NSURLSession *session = ...;
  NSURLSessionDownloadTask *task;
  task = [session downloadTaskWithRequest:request
                 completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
         /* want to do UI things here, can I? */ 
}];
downloadTaskWithRequest:completionHandler: will do its work (downloading that URL)! NOT in the main thread (i.e. it will not block the UI while it waits on the network).
下面這兩個範例就是在說在main queue 跟不在main queue 的寫法.

On the main queue

delegate: 在下載時會向你更新狀態. 但我們只需要知道什麼時候結束去呼叫completionHandler, 所以這裡設定nil.
delegate queue: Tells which queue are all your delegate methods going to be called on. 也可以設定nil那系統會隨機分配一個queue.
Since the delegateQueue is the main queue, our completionHandler will be on the main queue.
enter image description here
When the URL is done downloading, the block above will execute on the main queue. Thus we can do any UI code we want there.!

Off the main queue

在這邊不是在main queue中被執行所以要在completionHandler另外新增block來指向main queue才能執行UI stuff.
enter image description here
Can’t do any UI stuff because the completionHandler is not on the main queue.! To do UI stuff, you have to post a block (or call a method) back on the main queue.

UIScrollView

Adding subviews to a UIScrollView

Ex.
scrollView.contentSize = CGSizeMake(3000, 2000); #代表scrollView可以滾動的範圍
subview2.frame = CGRectMake(50, 100, 2500, 1600); #(座標,大小)
[view addSubview:subview2];
enter image description here
要怎麼知道現在在哪個區域?
CGPoint upperLeftOfVisible = scrollView.contentOffset;
enter image description here
Visible area of a scroll view’s subview in that view’s coordinates
CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:subview];
enter image description here

How do you create one?

enter image description here
建議使用addSubview
UIImage *image = [UIImage imageNamed:@“bigimage.jpg”];
UIImageView *iv = [[UIImageView alloc] initWithImage:image]; // frame.size = image.size! [scrollView addSubview:iv]; 

Scrolling programmatically!

- (void)scrollRectToVisible:(CGRect)aRect animated:(BOOL)animated; !

Zooming

Will not work without minimum/maximum zoom scale being set!
scrollView.minimumZoomScale = 0.5; // 0.5 means half its normal size 
scrollView.maximumZoomScale = 2.0; // 2.0 means twice its normal size!
Will not work without delegate method to specify view to zoom!
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)sender;
If your scroll view only has one subview, you return it here. More than one? Up to you.!
enter image description here

Demo

ImageViewController.h
#import <UIKit/UIKit.h>
@interface ImageViewController : UIViewController
@property (nonatomic,strong) NSURL *imageURL;
@end
ImageViewController.m
設置一個spinner反饋
#import "ImageViewController.h"
@interface ImageViewController () <UIScrollViewDelegate>
@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,strong) UIImage *image;
@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *spinner;
@end
[self.imageView sizeToFit]希望imageView能調整自身框架去fit圖像顯示
@implementation ImageViewController

- (void) viewDidLoad
{
    [super viewDidLoad];
    [self.scrollView addSubview:self.imageView]; #make sure get it on screen.
}

- (UIImageView *)ImageView
{
    if(!_imageView) _imageView = [[UIImageView alloc] init];
    return _imageView;
}

- (UIImage *)image
{
    return self.imageView.image;
}

- (void) setImage:(UIImage *)image
{
    self.imageView.image = image;
    [self.imageView sizeToFit]; 

    self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero; 
#避免self.image為nil,否則會得到underfined results.
    [self.spinner stopAnimating];
}


- (void)setScrollView:(UIScrollView *)scrollView
{
    _scrollView = scrollView;
    _scrollView.minimumZoomScale = 0.2;
    _scrollView.maximumZoomScale = 1.5;
    _scrollView.delegate = self;

    self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero; 
#當prepareForSegue在準備時,你的輸出還沒建立好,避免self.scrollView為nil.
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView; 
    #設定要在哪個view發生zomming. 只會發生在這邊return的view上
}

- (void)setImageURL:(NSURL *)imageURL
{
    _imageURL = imageURL;
    #self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:self.imageURL]]; 
    #如果下載需要花時間 就會造成延誤因為在main queue執行.
    [self startDownloadingImage];
}

- (void)startDownloadingImage
{
    #下載時先清除當前的image內容
    self.image = nil; 
    if (self.imageURL) {
        [self.spinner startAnimating];

        NSURLRequest *request = [NSURLRequest requestWithURL:self.imageURL];
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
#another configuration option is backgroundSessionConfiguration, 很強大,即使離開app還會執行直到完成下載.
# create the session without specifying a queue to run completion handler on (thus, not main queue)
# we also don't specify a delegate (since completion handler is all we need)
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
        NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
             # this handler is not executing on the main queue, so we can't do UI directly here
            if (!error) {
 #避免在下載的同時,網址變了導致內容不一樣.
                if ([request.URL isEqual:self.imageURL]) {
                    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:localfile]];#從網頁下載圖
# but calling "self.image =" is definitely not an exception to that!
# so we must dispatch this back to the main queue
                    dispatch_async(dispatch_get_main_queue(), ^{ self.image = image; });
//[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO]跟上一行結果一樣
                }
            }
        }];
        [task resume]; 
   # don't forget that all NSURLSession tasks start out suspended!
    }
}
@end
ViewController.m
#import "ViewController.h"
#import "ImageViewController.h"

@implementation ViewController

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[ImageViewController class]]) {
        ImageViewController *ivc = (ImageViewController *)segue.destinationViewController;
        ivc.imageURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://images.apple.com/v/iphone-5s/gallery/a/images/download/%@.jpg", segue.identifier]];
        ivc.title = segue.identifier;
    }
}
@end
enter image description here
參考資料:
http://www.slideshare.net/deeplovepan/standford-2015-week6?related=5

沒有留言:

張貼留言