GeekBand.Swift-属性与方法

属性表达实例状态或类型状态
方法是成员函数,表达行为可用于:struct,enum,class

属性

属性类别

根据读写性划分

  • 存储属性
  • 计算属性

根据可变性划分

  • 变量属性
  • 常量属性

根据归属权划分

  • 实例属性
  • 类属性(在struct和enum中用static声明,class中用class声明)

存储属性的本质

一个存储变量,同时编译器会给该变量自动添加get和set访问器方法。

计算属性的本质

一个get方法。(可以添加set)。

class Rectangle{

    //常量属性
    let name:"矩形";
    
    //存储属性(变量属性)
    var width:Int;
    var height:Int;
    //计算属性
    var area:Int{
        get{
            return width*height;
        }
        set{
            self.width = newValue / height;
        }
    }
    
    
}

属性监视器

属性监视器是swift的一个特殊功能,可以在属性值修改之前和之后完成对应动作。

var name:String = "coder"{
    willSet{
        doSomthing();
    }
    didSet{
        doSomthing();
    }
}

注意

实例属性在使用之前必须初始化!

方法

方法的类别

方法根据归属不同可以分为:

  • 实例方法
  • 类型方法

方法的声明

声明代码:func name(参数1,参数2)->返回值{}

说明:

  • 参数形式:外部参数名 本地参数名:类型
  • 声明时可以省略外部参数名,这时外部参数名默认使用本地参数名
  • 调用时,第一个参数名可忽略,但后面的参数名必须显示标明。如果在声明时加_,调用时也可忽略
  • 方法可以没有参数和返回值
  • 参数传递默认为传值

更多参数和返回值功能

  • 提供参数默认值
  • 常量参数与变量参数
  • 可变长参数
  • inout参数,可以改变外部实参
  • 返回元组(Tuple)

class MyClass{
    var instanceData=100;
    static var typeData=10000;
 
    //实例方法
    func instanceMethod(){
        ++instanceData
        ++MyClass.typeData
        instanceMethod2()
        MyClass.typeMethod()
    }
    
    //静态方法
    static func typeMethod(){
        ++typeData
        //++instanceData
        typeMethod2()
        //instanceMethod()
    }
    
    func instanceMethod2(){
        print(instanceData)
    }
    static func typeMethod2(){
        print(typeData)
    }

    //显式内部参数名,省略外部参数名
    func sayGreeting(name:String, _ words:String)->String{
        return words+"! "+name
    }
    func sayGreeting(){
        print("Hello!")
    }
    func sayGreeting(name:String)->String {
        return "Hello! "+name
    }
    //显式内部参数名,显式外部参数名
    func sayGreeting(person name:String,greeting words:String)->String{
        return words+"! "+name
    }
    
    //可变数目参数
    func averageNumber(numbers:Double...)->Double{
    
        var sum=0.0
        for item in numbers {
            sum+=item
        }
        return sum / Double(numbers.count)
    }
    
    //常量参数 VS. 变量参数
    func changeParameter(data1:Int, var data2:Int){
        var dataTemp=data1
        data2++
        dataTemp++
    }

    //inout参数
    func swap(inout a:Int,inout b:Int){
        let tempA=a
        a=b
        b=tempA
    }
    
    //返回Tuple类型
    func minMax(array: [Int]) -> (min: Int, max: Int){
        var currentMin = array[0]
        var currentMax = array[0]
        for value in array[1..<array.count] {
            if value < currentMin {
                currentMin = value
            } else if value > currentMax {
                currentMax = value
            }
        }
        return (currentMin, currentMax)
    }
}

var myObject=MyClass()
//调用实例方法
myObject.instanceMethod()
//调用类型方法
MyClass.typeMethod()
myObject.sayGreeting()
myObject.sayGreeting("Jason")
myObject.sayGreeting("Jason","Welcome")
myObject.sayGreeting(person:"Jason", greeting:"You are welcome")
myObject.averageNumber(10,90,30,80,50,100)
var data1=100
var data2=200
myObject.swap(&data1,b: &data2)
let range=myObject.minMax([3,-9,23,15,-45,7])
print("max=\(range.max), min=\(range.min)")

特殊的方法

初始化器

初始化器用于初始化类型实例,是一个特殊的函数,无返回值。

初始化过程
  1. 分配内存
  2. 调用初始化器初始化内存
应用范围
  • class
  • struct
  • enum
作用
  • 初始化器主要用于初始化实例储存属性,可以使用默认初始化器或自定义的初始化器。
  • 所有储存属性必须被初始化
    • 实例储存属性要么指定默认值,要么在初始化器内初始化
    • 类型存储属性必须指定默认值
  • 可选属性类型可以不初始化。(声明时用修饰)
  • 属性被初始化时,不会调用属性观察者
默认初始化器
  • 一个类可以有多个初始化器,但至少有一个初始化器
  • 如果一个类没有人为提供初始化器,编译器会自动生成一个默认的初始化器
  • 默认初始化器,无参形式init();
便捷初始化器
  • 指定初始化器为类的主初始化器,负责初始化所有属性,默认调用父类的主初始化器
  • 便捷初始化器为类的辅助初始化器。必须调用同的指定初始化器。用convenience修饰
class Point3D{
    var x:Int
    var y:Int
    var z:Int
    
    init(x:Int, y:Int, z:Int){
        self.x=x
        self.y=y
        self.z=z
        
        //other processing
        print("other task")
    }
    
    convenience init(x:Int, y:Int){
        
        self.init(x:x,y:y,z:0)
    }
    
    convenience init(){
        
        self.init(x:0,y:0,z:0)
    }
}

析构器

析构器(deinit),在实例内存被释放前调用,用于释放实例使用的非内存资源。

  • 析构器仅可以定义于class,且只能定义一个。
  • 只有实例析构器,没有类型析构器
  • 析构器运行根据ARC的释放规则,自动调用,程序员无法手动调用
class FileStream{
    init(){
        print("open file...")
    }
    
    func process(){
        print("process file...")
    }
       
    deinit{
        print("close file...")
    }
}

2016/5/10 posted in  iOS学习笔记
 

GeekBand.Swift-第一周作业

请为以下四种类型设计方式画出类型的内存模型。分析每种设计方案的优缺点,选出你认为最佳的实现方式,并解释为什么?
心中有沟壑
原谅我。。英语拼错了。。。

设计方式一

class Point{
    var x:Int;
    var y:Int;
}
class  Rectangle{
    var leftUp:Point;
    var width:Int;
    var height:Int;
}

内存模型:
设计模式一内存模型

设计方式二

struct Point{
    var x:Int;
    var y:Int;
}
 
class Rectangle{
    var leftUp:Point;
    var width:Int;
    var height:Int;
}

内存模型:
设计方式二

设计方式三

class Point{
    var x:Int;
    var y:Int;
}
 
struct  Rectangle{
    var leftUp:Point;
    var width:Int;
    var height:Int;
}

内存模型:
设计方式三

设计方式四

struct Point{
    var x:Int;
    var y:Int;
} 
struct Rectangle{
    var leftUp:Point;
    var width:Int;
    var height:Int;
}

内存模型:
设计方式四

Swift中struct和class的区别

WWDC2014 Introduction to Swift有比较清楚的介绍,总结区别主要有两点:

  1. struct没有继承的功能,而class是可以继承的,这是面向对象语言的核心能力,class当然会有这个能力
  2. 体现在内存使用上,struct是通过值传递,数据保存在栈上。而class是通过引用传递的,数据保存在堆中。

使用情景

当满足以下条件的时候,建议使用struct,否则建议使用class

  1. 封装少量、简单的数据的时候。比如题目中的Point。
  2. 数值在传递的过程中应使用值拷贝的情况。比如Frame,Point的实现。
  3. 不需要继承。这个是面向对象的基本特性。

结论

综上所示,个人认为四种设计方式按照设计良好来排序应为(在具体实际情况下可能有所改变):2>1>4>3

2016/5/8 posted in  iOS学习笔记
 

GeekBand.iOS-数据持久化

在我们日常的开发过程中,从网络下载的数据或者由用户产生的数据,一般都需要保存下来。并且可以供给用户进行进一步的修改。这个过程被称之为数据持久化。
在iOS中,我们可以采用如下技术进行数据的持久化:

  • 文件存储
  • SQLite嵌入式数据库
  • Core Data

文件存储

使用文件存储数据是最简单的方式,把数据以二进制的形式或字符串的形式写入文件。

沙盒

在iOS中,为了保证用户的数据安全。苹果对文件数据的访问采用了很大的限制。主要概述为Sandbox(沙盒)。即一个APP只能访问自己盒内的文件,对外部文件没有访问权限。

  • 根目录:NSHomeDictionary()
  • /Docouments:程序的文件数据保存在该目录下,iTunes备份时会包含该目录
  • /Library:储存程序的默认设置或其它状态信息
  • /Library/Caches:缓存文件。iTunes不备份,程序退出后不删除
  • /tmp:创建临时文件的地方,重启时会丢弃

APP Bundle

bundle是一个目录,其中包含了程序会使用到的资源.这些资源包含了如图像,声音,编译好的代码,nib文件(用户也会把bundle称为plug-in).对应bundle,
cocoa提供了类NSBundle。
[NSBundle mainBundle]

NSFileManager

iOS的文件操作类,可以使用它来创建、删除文件。
常用操作请参考:
NSFileManager

数据库SQLite

文件少说一些。在iOS中,我们同样可以使用数据库技术来自己创建各种表,来存储数据。在iOS中,我们使用嵌入式的SQLite作为数据库管理系统,创建用户数据。

什么是SQLite

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。目前我们使用SQLite 3.X版本。

使用SQLite

在iOS开发过程中,我们使用纯C语言的API来对其进行访问。主要步骤包括以下几点。

  1. 链接libSQLite3.dylib
  2. 使用SQLite3函数用于创建、打开数据库、执行SQL语句或关闭数据库

纯C语言API

//打开 SQLite
sqlite3* db = NULL
int result = sqlite3_open([path UTF8String],&db);

//执行SQL语句
int result = sqlite_exec(db,sql,NULL,NULL,&errorMsg);

//预定义SQL语句,可以有效防止SQL注入
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(db,sql,-1,&stmt,NULL)== SQLITE_OK){
    sqlite3_bind_text(stmt,1,"string",-1.NULL);
    sqlite3_bind_int(stmt,2,27);
}
sqlite3_finalize(stmt);


//结果集遍历
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(db,sql,-1,&stmt,NULL)== SQLITE_OK){
    while(sqlite3_step(stmt)==SQLITE_ROW){
        int _id = sqlite3_column_int(stmt,0);
        char* _name = sqlite3_column_text(stmt,1);
        NSString* name = [NSString stringWithUTF8String:_name];
        ...
    }
}
sqlite3_finalize(stmt);

//关闭SQLite
sqlite3_close(db);

FMDB

使用原生的API对SQLite访问效率挺高,但是在我们面向对象的开发过程中加入这样的代码,总是不太舒服。而且写起来也比较坑。于是伟大的第三方访问工具诞生啦。就是FMDB:https://github.com/ccgus/fmdb
使用方法如下:

//打开
FMDatabase *db = [FMDatabase databaseWithPath:(NSString *)path];
[db open];

//更新语句,executeUpdate这个方法后面必须是对象比如NSString,NSInteger,否则会崩溃
[db executeUpdate:@"insert into contacters(name,etc,telephone,email) values(?,?,?,?)",contacter.name,contacter.etc,contacter.telephone,contacter.email];
//取插入的id
contacter.contacterId = (int)[db lastInsertRowId];

//查询
FMResultSet* result = [db executeQuery:@"select * from contacters where id=?",contacterId];
while ([result next]) {
    contacter.contacterId = [result intForColumn:@"id"];
    contacter.name = [result stringForColumn:@"name"];
    contacter.telephone = [result stringForColumn:@"telephone"];
    contacter.email = [result stringForColumn:@"email"];
    contacter.etc = [result stringForColumn:@"etc"];
 }

//关闭
[db close];

CoreData简介

虽然使用了SQLite之后,可以实现所有的数据存储。但是,这并不是苹果最推荐的方法。官方最推荐的解决方案就是Core Data。

什么是CoreData

Apple提供的对象持久化框架,并不是数据库,但通常使用数据库作为底层存储。
基本概念

开始使用CoreData

  1. 在新建项目时勾选Use CoreData
  2. 在项目中*.xcdatamodel中构建模型

简单使用

//创建一个Entity对象
NSManagedObject* entity = [NSEntityDescription insertNewObjectForEntityForName:@"Entity" inManagedObjectContext:context];

//填充属性
[entity setValue:value forKey:@"key"];

//保存
NSError* error = nil;
[context save:&error];

//查询
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];

//排序过滤
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"key" ascending:YES]];
request.predicate = [NSPredicate predicateWithFormat:@"name like %@",@"j"];


//执行
NSError* error = nil;
NSArray* objs =[context executeFetchRequest:request error:&error];

//遍历,正常遍历数组即可。
for(NSManagedObject *obj in objs){
    NSLog(@"%@",[obj valueForKey:@"key"]);
}

//删除
[context deleteObject:managedObject];

参考资料

本部分只简要介绍CoreData。大坑慢慢填。送一些参考资料。
CoreData入门
CoreData入门知识

2016/5/4 posted in  iOS学习笔记
 

GeekBand.iOS-多线程编程

程序都是按部就班的一步一步执行,可是我们现在却可以在听歌的过程中同时做很多事情。cpu为了提高性能也变成双核、四核、八核等等。这些都是多线程技术的应用。今天我们就来总结一下iOS中的多线程编程。

进程与线程

  1. 什么是进程?

    进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

    比如同时打开QQ、Xcode,系统就会分别启动2个进程。通过“活动监视器”可以查看Mac系统中所开启的进程。

  2. 什么是线程?

    1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)
    线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行
    比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行。

  3. 线程的串行

    1个线程中任务的执行是串行的。如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务。也就是说,在同一时间内,1个线程只能执行1个任务。比如在1个线程中下载3个文件(分别是文件A、文件B、文件C)。

多线程

  1. 什么是多线程?

    1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
    进程 ->车间,线程->车间工人
    多线程技术可以提高程序的执行效率
    比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)

  2. 多线程的优缺点
    优点:

    • 能适当提高程序的执行效率
    • 能适当提高资源利用率

    缺点:

    • 启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    • 线程越多,CPU在调度线程上的开销就越大
    • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享
  3. 多线程在iOS开发中的应用
    主线程:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
    主线程的主要作用:显示\刷新UI界面、处理UI事件(比如点击事件、滚动事件、拖拽事件等)。
    主线程的使用注意:别将比较耗时的操作放到主线程中。
    耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。

iOS中的多线程开发

iOS中目前主流的多线程实现方案有三种,分别是:

  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

NSThread

这是一套经过苹果封装后的,面向对象的多线程操作方案。利用它可以直接操控线程对象,但是在生命周期管理以及代码复用上并不是很好,所以使用并不是很多。

创建并启动

  1. 先创建线程类,再启动

    // 创建
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    // 启动
    [thread start];
    
  2. 创建并自动启动

    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
    

    其他方法

    NSThread虽然不常用,但是很多方法在我们编程的时候还是比较有价值的。

//取消线程
- (void)cancel;

//启动线程
- (void)start;

//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//获取当前线程信息
+ (NSThread *)currentThread;

//获取主线程信息
+ (NSThread *)mainThread;

//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

GCD

GCD(Grand Central Dispatch),名字霸气。他是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是c语言API,不过由于使用了Block,使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案。

任务和队列

在GCD中,加入了两个非常重要的概念:任务和队列。

  • 任务:即操作,你想要干什么,说白了就是一段代码,在GCD中就是一个Block,所以添加任务十分方便。任务有两种执行方式:同步执行和异步执行。

    • 同步(sync)执行:它会阻塞当前线程并等待Block中的代码执行完毕,然后当前线程才会继续往下运行。不具备开启新线程的能力。
    • 异步(async)执行:当前线程会直接往下执行,并不会阻塞当前线程。具备开启新线程的能力。
  • 队列:用于存放任务。在GCD中有两种队列:串行队列和并行队列。

    • 串行队列:队列中的任务会根据队列的先进先出的顺序执行,一个接一个的执行。
    • 并行队列:放到并行队列的任务,GCD 也会FIFO(先进先出)的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

队列的创建

  • 主队列:这是一个特殊的串行队列。主队列,就是APP的主线程,放在这个队列中的任务会在主线程中运行。主要用于刷新UI,所以一般耗时的任务都应该放在别的线程中执行,防止主线程阻塞。

    dispatch_queue_t queue = dispatch_get_main_queue();
    
    
  • 自建队列:自己创建的队列可以是串行队列,也可以是并行队列。创建的方法为dispatch_queue_create。这个方法有两个参数,第一个参数是标识符,用于DEBUG的时候表示唯一的队列,可以为空,一般命名方式为域名的反写。第二个参数声明创建的队列的参数的类型。

    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("site.zhanjingbo.testQueue", NULL);
    dispatch_queue_t queue = dispatch_queue_create("site.zhanjingbo.testQueue", DISPATCH_QUEUE_SERIAL);
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("site.zhanjingbo.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
  • 全局并行队列:一个全局的并行队列,系统提供。一般并行任务都加入这个队列中。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    创建任务

  • 同步任务

    dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
    });
    
  • 异步任务

    dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
    });
    

    队列组

    队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。下面是使用方法,这是一个很实用的功能。

    //1.创建队列组
    dispatch_group_t group = dispatch_group_create();
    //2.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //3.多次使用队列组的方法执行任务, 只有异步方法
    //3.1.执行3次循环
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-01 - %@", [NSThread currentThread]);
        }
    });
    
    //3.2.主队列执行8次循环
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 8; i++) {
            NSLog(@"group-02 - %@", [NSThread currentThread]);
        }
    });
    
    //3.3.执行5次循环
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"group-03 - %@", [NSThread currentThread]);
        }
    });
    
    //4.都完成后会自动通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
    

NSOperation和NSOperationQueue

NSOperation是苹果公司对GCD的封装,完全面向对象,所以使用起来更好理解。 大家可以看到NSOperationNSOperationQueue 分别对应 GCD的任务和队列。操作步骤也很好理解:

1. 将要执行的任务封装为一个NSOperation对象
2. 将这个任务添加到一个NSOperationQueue对象中。

添加任务

值得说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2个子类用于封装任务。分别是:NSInvocationOperationNSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其cancel方法即可。

  • NSInvocationOperation
//1.创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

//2.开始执行
[operation start];
  • NSBlockOperation
    //1.创建NSBlockOperation对象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.开始任务
  [operation start];

之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation 还有一个方法:addExecutionBlock:,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务会并发执行,它会在主线程和其它的多个线程执行这些任务。

  • 自定义Operation 除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。所以这个功能提供给高级玩家,我在这里就不说了,等我需要用到时在研究它,到时候可能会再做更新。

创建队列

看过上面的内容就知道,我们可以调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样做他们默认是同步执行的。就算是 addExecutionBlock方法,也会在 当前线程和其他线程中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法。

  • 主队列

细心的同学就会发现,每套多线程方案都会有一个主线程(当然啦,说的是iOS中,像 pthread 这种多系统的方案并没有,因为 UI线程 理论需要每种操作系统自己定制)。这是一个特殊的线程,必须串行。所以添加到主队列的任务都会一个接一个地排着队在主线程处理。

NSOperationQueue *queue = [NSOperationQueue mainQueue];

  • 其它队列

因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列了,因为只有这两种队列,除了主队列,其他队列就不需要名字了。

    //1.创建一个其他队列    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2.创建NSBlockOperation对象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    
    //3.添加多个Block
    for (NSInteger i = 0; i < 5; i++) {
        [operation addExecutionBlock:^{
            NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
        }];
    }
    
    //4.队列添加任务
    [queue addOperation:operation];

OK, 这时应该发问了,大家将 NSOperationQueue 与 GCD的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行怎么办?

这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue 有一个参数 maxConcurrentOperationCount 最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,他不就是串行了嘛!

NSOperationQueue 还有一个添加任务的方法,- (void)addOperationWithBlock:(void ()(void))block; ,这是不是和 GCD 差不多?这样就可以添加一个任务到队列中了,十分方便。
NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上传图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.设置依赖
[operation2 addDependency:operation1];      //任务二依赖任务一
[operation3 addDependency:operation2];      //任务三依赖任务二

//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

其它方法

  • NSOperation

    BOOL executing; //判断任务是否正在执行
    
    BOOL finished; //判断任务是否完成
    
    void (^completionBlock)(void); //用来设置完成后需要执行的操作
    
    - (void)cancel; //取消任务
    
    - (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕
    
  • NSOperationQueue

    NSUInteger operationCount; //获取队列的任务数
    
    -(void)cancelAllOperations; //取消队列中所有的任务
    
    -(void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
    
    [queue setSuspended:YES]; // 暂停queue
    
    [queue setSuspended:NO]; // 继续queue
    

更多内容

参考关于iOS多线程,你看我就够了

2016/4/24 posted in  iOS学习笔记
 

GeekBand.iOS-网络操作

大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的。如今,网络编程越来越普遍,孤立的应用通常是没有生命力的。今天就会给大家介绍这部分内容:

Web请求和响应

做过Web开发的朋友应该很清楚,Http是无连接的请求。每个请求request服务器都有一个对应的响应response,无论是asp.net、jsp、php都是基于这种机制开发的。

在Web开发中主要的请求方法有如下几种:

  • GET请求:get是获取数据的意思,数据以明文在URL中传递,受限于URL长度,所以传输数据量比较小。
  • POST请求:post是向服务器提交数据的意思,提交的数据以实际内容形式存放到消息头中进行传递,无法在浏览器url中查看到,大小没有限制。
  • HEAD请求:请求头信息,并不返回请求数据体,而只返回请求头信息,常用用于在文件下载中取得文件大小、类型等信息。 在开发中往往数据存储在服务器端,而客户端(iOS应用)往往通过向服务器端发送请求从服务器端获得数据。要模拟这个过程首先当然是建立服务器端应用,应用的形式没有限制,你可以采用任何Web技术进行开发。

原生网络访问

iOS原生为我们提供了网络访问的能力。我们今天只研究NSURLSession的使用方法。该方法适用于iOS7以后的设备。它提供如下功能:

  1. 通过URL将数据下载到内存
  2. 通过URL将数据下载到文件系统
  3. 将数据上传到指定URL
  4. 在后台完成上述功能

NSURLSession基本用法

  1. 根据Session类型选择NSURLSessionConfiguration
  2. 设置Configuration,如指定NSURLCache等
  3. 用Configuration创建Session

    +[NSURLSession sessionWithConfiguration:delegate:delegateQueue:];
    +[NSURLSession sessionWithConfiguration];
    
  4. 在Session里创建网络访问任务

  5. 启动任务-[NSURLSessionTask resume]

访问Demo

 NSURL *URL = [NSURL URLWithString:@"http://example.com"];
 NSURLRequest *request = [NSURLRequest requestWithURL:URL];

 NSURLSession *session = [NSURLSession sharedSession];
 NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                         completionHandler:
     ^(NSData *data, NSURLResponse *response, NSError *error) {
         // ...
     }];

 [task resume];

Upload task 的创建需要使用一个 request,另外加上一个要上传的 NSData 对象或者是一个本地文件的路径对应的 NSURL:

NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
 NSURLRequest *request = [NSURLRequest requestWithURL:URL];
 NSData *data = ...;

 NSURLSession *session = [NSURLSession sharedSession];
 NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
                                                            fromData:data
                                                   completionHandler:
     ^(NSData *data, NSURLResponse *response, NSError *error) {
         // ...
     }];

 [uploadTask resume];

Download task 也需要一个 request,不同之处在于 completionHandler 这个 block。Data task 和 upload task 会在任务完成时一次性返回,但是 Download task 是将数据一点点地写入本地的临时文件。所以在 completionHandler 这个 block 里,我们需要把文件从一个临时地址移动到一个永久的地址保存起来:

 NSURL *URL = [NSURL URLWithString:@"http://example.com/file.zip"];
 NSURLRequest *request = [NSURLRequest requestWithURL:URL];

 NSURLSession *session = [NSURLSession sharedSession];
 NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
                                                         completionHandler:
    ^(NSURL *location, NSURLResponse *response, NSError *error) {
        NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];
        NSURL *newFileLocation = [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];
        [[NSFileManager defaultManager] copyItemAtURL:location toURL:newFileLocation error:nil];
    }];

 [downloadTask resume];

NSURLSessionConfiguration

NSURLSessionConfiguration 对象用于对 NSURLSession 对象进行初始化。NSURLSessionConfiguration 对以前 NSMutableURLRequest 所提供的网络请求层的设置选项进行了扩充,提供给我们相当大的灵活性和控制权。从指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,你会发现使用 NSURLSessionConfiguration 可以找到几乎任何你想要进行配置的选项。
NSURLSession 在初始化时会把配置它的 NSURLSessionConfiguration 对象进行一次 copy,并保存到自己的 configuration 属性中,而且这个属性是只读的。因此之后再修改最初配置 session 的那个 configuration 对象对于 session 是没有影响的。也就是说,configuration 只在初始化时被读取一次,之后都是不会变化的。

NSURLSessionConfiguration 的工厂方法

NSURLSessionConfiguration 有三个类工厂方法,这很好地说明了 NSURLSession 设计时所考虑的不同的使用场景。

优秀的第三方网络访问库 AFNetworking

AFNetworking是由Mattt Thompson开发的一个开源的网络访问组件。地址AFNetworking
从3.0版本开始AFNetworking开始使用NSURLSession进行实现。

用法

AFURLSessionManager 创建并管理一个NSURLSession对象 基于指定的NSURLSessionConfiguration对象, 并遵从一下协议 , , , and .

Creating a Download Task

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
    return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
    NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];

Creating an Upload Task

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    if (error) {
        NSLog(@"Error: %@", error);
    } else {
        NSLog(@"Success: %@ %@", response, responseObject);
    }
}];
[uploadTask resume];

Creating an Upload Task for a Multi-Part Request, with Progress

NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
    } error:nil];

AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
              uploadTaskWithStreamedRequest:request
              progress:^(NSProgress * _Nonnull uploadProgress) {
                  // This is not called back on the main queue.
                  // You are responsible for dispatching to the main queue for UI updates
                  dispatch_async(dispatch_get_main_queue(), ^{
                      //Update the progress view
                      [progressView setProgress:uploadProgress.fractionCompleted];
                  });
              }
              completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                  if (error) {
                      NSLog(@"Error: %@", error);
                  } else {
                      NSLog(@"%@ %@", response, responseObject);
                  }
              }];

[uploadTask resume];

Creating a Data Task

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:@"http://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    if (error) {
        NSLog(@"Error: %@", error);
    } else {
        NSLog(@"%@ %@", response, responseObject);
    }
}];
[dataTask resume];

Another way

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
AFHTTPSessionManager* manger = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:config];
[manger GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //do somthing
    } failure:nil];
2016/4/19 posted in  iOS学习笔记