iOS I/O与多线程

沙盒模型

特点

  • 安全
  • 隐私
  • 整洁

sandbox

  • Bundle Container
    • yixin.app
    • [[NSBundle mainBundle] pathForResource:@"name" ofType:@"type"]
  • Data Container
    • NSHomeDirectory()
    • Documents
    • [NSSearchPathForDirectoriesInDomains(NSDocumentDictionary, NSUserDomainMask, YES) firstObject]
    • Library
    • Temp
    • NSTemporaryDirectory()

资源管理器

  • 发现资源 : 遍历目录
  • 修改资源 : 创建目录, 创建文件, 修改目录, 修改文件, 读写目录, 读写文件
NSFileManager *manager = [[NSFileManager alloc] init];
NSFileManager *defaultManager = [NSFileManager defaultManager];

多线程

  • 线程生而不平等 : 主线程 & 后台线程; 线程有优先级的关系
  • 线程是一个被模拟出来的概念 : CPU 通过分配时间片模拟出多线程同时工作的状态

NSThread

// 初始化
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
// 启动
- (void)start;

// 类方法, 会自动启动
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

// 使用继承, 需要使用 start 方法启动, 使用 delegate 将线程执行的结果传出
@interface MyThread : NSThread
@end
@implementation
- (void)main {
    // 在这里重写线程实现的方法
    NSLog(@"thread start");
}
@end
- (void)viewDidLoad {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:nil];
    [thread start];
}
- (void)downloadImage:(id)arg {
    NSURL *url = [NSURL urlWithString:@"http://xxx.com/image.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    
    [self performSelectorOnMainThread:@selector(onImageDone:) withObject:image waitUntilDone:NO];
}
- (void)onImageDone:(id)arg {
    if ([arg isKindOfClass:[UIImage class]]) {
        self.imageView.image = (UIImage *)arg;
    }
}
  • 使用 cancel 将线程状态标记成取消状态
+ (BOOL)isMainThread;
+ (NSThread *)mainThread;
+ (NSThread *)currentThread;

// 暂停线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
unsigned int sleep(unsigned int);

// 线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnBackground:(SEL)aSelector withObject:(nullable id)argA;

多线程优缺点

  • 优点
    • 提高 APP 实时响应性
    • 充分利用计算资源
  • 缺点
    • 额外的系统开销
    • 线程同步问题
    • 程序复杂度上升

多线程同时访问资源

  • 使用 NSLock
@property (nonatomic, strong) NSLock *lock;
_lock = [[NSLock alloc] init];

[_lock lock]; // 加锁
// 这里执行的代码会把使用的成员变量锁住
[_lock unlock]; // 解锁

// 过程猜测: 使用 [_lock lock] 的时候, 如果 _lock 在锁住状态, 则这个操作会一直等待, 
// 等到操作执行完之后, _lock 被解锁, 才能执行下次加锁操作
  • 使用 @synchronized
    • 简单
    • 不需要自己 unlock, 不容易产生死锁
@synchronized (self) {
    // 执行操作, 这里的操作会被加锁, 执行完后退出 synchronized 则会被解锁
}

死锁状态

GCD (Grand Central Dispatch)

// 队列
dispatch_quequ_t

// 创建
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

// 使用
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

单例

+ (instancetype)sharedObject {
    static SingletonObject *instance = nil;
    
    // 下面的两行代码只会被之行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[SingletonObject alloc] init];
    });
    return instance;
}

dispatch_semaphore (信号量)

dispatch_semaphore_t

// 创建
dispatch_semaphore_t
dispatch_semaphore_create(long value);

// 触发信号量
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

// 等待信号量
long 
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

NSOperation

  1. 创建 NSOperationQueue : NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  2. 创建 NSOperation 子类对象 : 重写 NSOperation 任务执行函数

    @interface  MyOperation : NSOperation
    @end
    @implementation MyOperation
    - (void)main {
        // 任务执行函数
    }
    
  3. 将 NSOperation 的子类对象加入 NSOperationQueue : [queue addOperation:operation];

  4. 设置子类对象的 completionBlock, 在 block 中进行剩余的 UI 操作

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    MyOperation *op = [[MyOperation alloc] init];
    __weak typeof(op) weakOp = op;
    op.completionBlock = ^(){
        // 执行完毕之后需要进行的操作
        // 在此的操作也是在子线程进行的, 所以如果涉及到 UI 的操作需要手动放到主线程中
    }
    

NSBlockOperation

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

NSInvocationOperation

+ (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

Serial vs Concurrent

@property NSInteger maxConcurrentOperationCount;
// maxConcurrentOperationCount = 1      Serial Queue
// maxConcurrentOperationCount > 1      Concurrent Queue
// [NSOperationQueue mainQueue]         dispatch_get_main_queue()
@property (readonly) NSUinteger operationCount;

GCD vs NSOperation

  • NSOperation 支持取消
    • 标记成取消状态, 当执行到这个任务的时候, 任务不会被执行但是 completetionBlock 仍然会被执行
  • 封装, 能适应更复杂的操作和提供更精细化的操控

NSOperation 等待

  • 创建两个 OperationQueue : uploadQueue, finalQueue
  • 在 uploadQueue 中添加所有的操作
  • 在 finalQueue 中添加后续的操作 finalOperation, 中间加入代码
    • [uploadQueue waitUntilAllOperationsAreFinished];
    • finalOperation 会等待到所有的 uploadQueue 中任务执行完然后执行

使用 dependency 等待任务

@interface NSOperation : NSObject
- (void)addDependency:(NSOperation *)op; // 添加依赖任务
- (void)removeDependency:(NSOperation *)op; // 移除依赖任务
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
  • 循环依赖
    • 显式循环依赖
    • 隐式循环依赖 : 串行队列中, 前面的任务依赖于后面的任务, 导致任务循环依赖

优先级

@interface NSOperation : NSObject
@property NSOperationQueuePriority queuePriority;

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L;
    NSOperationQueuePriorityLow     = -4L;
    NSOperationQueuePriorityNormal  = 0;
    NSOperationQueuePriorityHigh        = 4;
    NSOperationQueuePriorityVeryHigh    = 8;
}

RunLoop 常驻的主线程

  • 和线程一一对应, 每个线程有且只有一个 RunLoop
  • 线程创建的时候并未有 RunLoop, 需要手动创建, 主线程除外
  • RunLoop 的创建发生在第一次获取它的时候(单例)
  • 只能在线程内部获取对应的 RunLoop (主线程 RunLoop 除外)
@interface NSRunLoop : NSObject
+ (NSRunLoop *)currentRunLoop;
+ (NSRunLoop *)mainRunLoop;

- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
@end

常驻的后台线程

  • 将有繁重的操作的回调指定到固定的线程中执行
  • perform selector 指定到固定的线程执行

RunLoop Mode

  • NSDefaultRunLoopMode
  • NSRunLoopCommonModes
    • default mode
    • modal mode
    • tracking mode UITrackingRunLoopMode
@interface NSRunLoop : NSObject
- (void)addTimer:(NSTimer *)timer forMode:(NSString *);
@end

iOS 网络基础

NSURLRequest

/********** 创建请求 **********/
@interface NSURLRequest : NSObject
// 各种属性都不可设置
+ (instancetype)requestWithURL:(NSURL *)URL;
- (instancetype)initWithURL:(NSURL *)URL;
@property (nullable, readonly, copy) NSURL *URL;

@property (nullable, readonly, copy) NSString *HTTPMethod;
@property (nullable, readonly, copy) NSDictionary<NSString *, NSString *> *allHTTPHeaderFields;
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
@property (nullable, readonly, copy) NSData *HTTPBody;
@end

@interface : NSMutableURLRequest : NSURLRequest
// ... 各种属性都是可以设置的

@interface NSURL : NSObject
- (nullable instancetype)initWithString:(NSString *)URLString;
+ (nullable instancetype)URLWithString:(NSString *)URLString;
@property (readonly, copy) NSString *absoluteString;
@end


/********** 发送请求 **********/
@interface NSURLConnection : NSObject // 逐步被废弃, 推荐使用 NSURLSession
- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate startImmediately:(BOOL)startImmediately;
- (void)start;
@end


/********** 接收响应 **********/
@protocol NSURLConnectionDataDelegate <NSURLConnectionDelegate>
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
@end

@protocol NSURLConnectionDelegate <NSObject>
@optional
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
@end

@interface NSHTTPURLResponse : NSURLResponse
@property (readonly) NSInteger statusCode;
@property (readonly, copy) NSDictionary *allHeaderFields;
@end

@interface NSURLResponse : NSObject <NSSecureCoding, NSCopying>
@property (nullable, readonly, copy) NSURL *URL;
@end


/********** 获取数据 : 解析 JSON/XML 数据 **********/
// JSON  <==> NSData
@interface NSJSONSeriallization : NSObject
+ (BOOL)isValidJSONObject:(id)obj;
+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

流程 Demo

// 创建一个请求
NSURL *url = [NSURL URLWithString:@"http://xxx.com"];
NSMutableURLRequest *request = [NSMutableRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"NEDemoAgent" forHTTPHeaderField:@"User-Agent"];
[request setValue:@"Application/JSON" forHTTPHeaderField:@"Content-Type"];

// 发送请求
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection start];

// 接收响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.response = response;
    self.responseData = [NSMutableData data];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    // handle error.
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // handle when request finished.
    NSStringEncoding stringEncoding = NSUTF8StringEncoding;
    // 直接获取返回的字符串
    self.responseString = [[NSString alloc] initWithData:self.responseData 
                                encoding:stringEncoding];
    // self.responseInfo 是 id 类型, 在这里获取的是格式化之后的 JSON 数据, 可能是 NSArray 或者 NSDictionary
    self.responseInfo = [NSJSONSerialization JSONObjectWithData:self.responseData 
                                options:0 
                                error:nil];
}

使用 NSURLSession 代替 NSURLConnection

NSURLSession NSURLConnection
NSURLConnection NSURLSession & NSURLSessionTask & NSURLSessionConfiguration
NSURLConnectionDelegate && NSURLConnectionDatDelegate NSURLSessionDelegate
@interface NSURLSession : NSObject
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容