AFNetworking的简单使用

最近看学习一些项目代码到了使用AFNetworking的项目,所以去学习了一下,这里简单的总结一下,AFNetworking的使用方法。

AFNetworking简介

AFNetworking是一个很受大众欢迎的网络框架,可以帮助管理和处理网络任务请求,包括下载、上传、getpost请求等。

安装

AFNetworking的安装可以使用CocoaPods在文件中加入:

pod 'AFNetworking'

并执行 pod install就可以了。有一点要注意的是最新版本为3.1这个版本删除了基于NSURLConnectionOperationAFHTTPRequestOperationManager的支持。转而使用基于NSURLSession封装的AFHTTPSessionManager

网络监听

AFNetworking提供了一个监听网络状态的方法,来实时的判断当前网络是否良好。具体代码如下:

// 如果要检测网络状态的变化,必须用检测管理器的单例的startMonitoring
    /**
     AFNetworkReachabilityStatusUnknown          = -1,  // 未知
     AFNetworkReachabilityStatusNotReachable     = 0,   // 无连接
     AFNetworkReachabilityStatusReachableViaWWAN = 1,   // 3G 花钱
     AFNetworkReachabilityStatusReachableViaWiFi = 2,   // 局域网络,不花钱
     */
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        debugLog(@"%ld",(long)status);
    }];

根据当前网络状态会输出所对应的状态数值。

下载

    //session的默认配置
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //根据配置创建管理者
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    NSURL *url = [NSURL URLWithString:@"http://smartdsp.xmu.edu.cn/memberpdf/fuxueyang/cvpr2017/cvpr2017.pdf"];
    //根据url创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //创建下载任务
    NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        //设置下载路径
        NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
        //返回文件存放在本地的地址
        return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        //下载完成后调用方法
        debugLog(@"File download to:%@ , %@",filePath,error);
    }];
    //开始下载任务
    [downloadTask resume];

get请求

    NSString *urlString = @"https://www.weifar.com/api/ExamQuestion/id";
    NSDictionary *parameters = @{@"id":@6};
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    //根据上述参数和请求地址来发送请求
    [manager GET:urlString parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
        debugLog(@"%@",downloadProgress);
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //成功获取数据后,进行处理
        if (responseObject) {
            NSArray *a = responseObject[@"Questions"];
            NSDictionary *dic = a[0];
            NSString *str = dic[@"BlockDescription"];
            debugLog(@"%@",str);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //请求失败,打印错误
        debugLog(@"%@",error);
    }];

总结

以上就是AFNetworking的基本使用,还有其他的一些功能由于没有合适的接口可以进行操作,暂且先搁置下。具体的操作可以参考官方的AFNetworkingAPI文档,

2017/11/18 posted in  iOS

FMDB的使用方法

  • 总结

    最近再看其它大牛写的项目代码,发现许多用到了FMDB,所以去了解了一下。

    FMDB简介

    FMDB是一个第三方的开源库,我们可以通过cocopods搜索并整合到项目里面,FMDB其实就是对SQLiteAPI进行了封装,加上了面向对象的思想,让我们不必使用繁琐的C语言API函数,比起直接操作SQLite更加方便。

    并且FMDB 同时兼容 ARC 和非 ARC 工程,会自动根据工程配置来调整相关的内存管理代码。

    使用方法

    本文使用方法,均参考FMDBgithub项目文档https://github.com/ccgus/fmdb

    引入相关文件

    因为是对sqlite的封装所以我们在项目中需要引入它的库。

    之后在文件中导入它的头文件:

    #import "FMDB.h"
    

    建立数据库

    建立数据库只有简单的一句代码,如果当前路径不存在所需的数据库则会自动创建,若存在则会获取到。当路径为字符(@“”)时,一个空的数据库将被创建在临时的位置,数据库关闭时候将被自动删除。路径为NULL时空数据库会被放在内存中,关闭时也将自动被删除。具体信息可以参见:http://www.sqlite.org/inmemorydb.html

    #define PATH_OF_DOCUMENT    [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
    
    _path = [PATH_OF_DOCUMENT stringByAppendingPathComponent:@"test.db"];
    //创建数据库
    FMDatabase *db = [FMDatabase databaseWithPath:_path];
    

    打开数据库

    在对数据库进行交互时,必须要先打开它。如果打开失败,可能是权限不足或者资源不足。

    if (![db open]) {
        // [db release];   // uncomment this line in manual referencing code; in ARC, this is not necessary/permitted
        db = nil;
        return;
    }
    

    执行更新(update)操作

    FMDB中除了select为查询(query)以为都为更新操作。

    例如我们执行一个插入操作的完整步骤为:

        static int idx = 1;
        FMDatabase *db = [FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString * sql = @"insert into User (name, password) values(?, ?) ";
            NSString *name = [NSString stringWithFormat:@"lzh%d",idx++];
            BOOL result = [db executeUpdate:sql,name,@"op"];
            if (!result) {
                debugLog(@"error to insert data");
            }else{
                debugLog(@"succ to insert data");
            }
            [db close];
        }
    

    查询操作:

        FMDatabase *db = [FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString *sql =@"select * from User";
            FMResultSet *result = [db executeQuery:sql];
            while ([result next]) {
                int userId = [result intForColumn:@"id"];
                NSString *name = [result stringForColumn:@"name"];
                NSString *pass = [result stringForColumn:@"password"];
                debugLog(@"user id = %d, name = %@, pass = %@", userId, name, pass);
            }
            [db close];
        }
    

    删除操作:

        static int idx = 1;
        FMDatabase *db =[FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString *sql = @"delete from User where id = ?";
            BOOL result = [db executeUpdate:sql , @(idx++)];
            if (!result) {
                debugLog(@"error to delete db data");
            } else {
                debugLog(@"succ to deleta db data");
            }
            [db close];
        }
    

    我们可以看到执行sql语句的时候用的都是executeUpdate:方法。

    执行查询操作

    查询操作与上面的有点区别,我们需要用FMResultSet来存储我们的查询结果,并调用它的next:方法来对数据进行逐行操作:

        FMDatabase *db = [FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString *sql =@"select * from User";
            FMResultSet *result = [db executeQuery:sql];
            while ([result next]) {
                int userId = [result intForColumn:@"id"];
                NSString *name = [result stringForColumn:@"name"];
                NSString *pass = [result stringForColumn:@"password"];
                debugLog(@"user id = %d, name = %@, pass = %@", userId, name, pass);
            }
            [db close];
        }
    

    上面代码可以发现执行sql语句变为executeQuery:方法,该方法会将结果返回为FMResultSet类型,之后我们需要调用stringForColumn:对结果进行解析。
    FMDB提供如下多个方法来获取不同类型的数据:

    intForColumn:
    longForColumn:
    longLongIntForColumn:
    boolForColumn:
    doubleForColumn:
    stringForColumn:
    dateForColumn:
    dataForColumn:
    dataNoCopyForColumn:
    UTF8StringForColumn:
    objectForColumn:
    

    也可以按照列的索引对数据进行获取,{type}ForColumnIndex:

    数据参数

    我们可以在sql语句中,用表示执行语句的参数,然后在 executeUpdate:方法来将?所指代的具体参数传入,例如上面的代码:

        NSString * sql = @"insert into User (name, password) values(?, ?) ";
        NSString *name = [NSString stringWithFormat:@"lzh%d",idx++];
        BOOL result = [db executeUpdate:sql,name,@"op"];
    

    线程安全

    FMDatabase这个类是线程不安全的,如果在多个线程同时使用一个FMDatabase实例,会造成数据混乱问题。所以,提供了一个FMDatabaseQueue并且使用它来对多个线程间进行交互,FMDatabaseQueue对象将通过接入多个线程进行同步和整合。

    使用的方法也很简单:

    首先创建一个数据库path来初始化FMDatabaseQueue,然后就可以将一个闭包 (block) 传入 inDatabase 方法中。

    
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
    
    [queue inDatabase:^(FMDatabase *db) {
        [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
        [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
        [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
    
        FMResultSet *rs = [db executeQuery:@"select * from foo"];
        while ([rs next]) {
            …
        }
    }];
    

    按照上面的方法我们可以创建多个线程来异步的对数据库进行操作:

    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:_path];
        dispatch_queue_t q1 = dispatch_queue_create("queue1", NULL);
        dispatch_queue_t q2 = dispatch_queue_create("queue2", NULL);
        
        dispatch_async(q1, ^{
            for (int i =1; i<100; ++i) {
                [queue inDatabase:^(FMDatabase *db){
                    NSString *sql = @"insert into User (name, password) values(?, ?)";
                    NSString *name = [NSString stringWithFormat:@"queue1 %d", i];
                    BOOL result = [db executeUpdate:sql,name,@"opop"];
                    if (!result) {
                        debugLog(@"error to add db data: %@", name);
                    } else {
                        debugLog(@"succ to add db data: %@", name);
                    }
                }];
            }
        });
        dispatch_async(q2,^{
            for (int i = 0; i < 100; ++i) {
                [queue inDatabase:^(FMDatabase *db) {
                    NSString * sql = @"insert into user (name, password) values(?, ?) ";
                    NSString * name = [NSString stringWithFormat:@"queue2 %d", i];
                    BOOL result = [db executeUpdate:sql, name, @"opop22"];
                    if (!result) {
                        debugLog(@"error to add db data: %@", name);
                    } else {
                        debugLog(@"succ to add db data: %@", name);
                    }
                }];
            }
        });
    

    执行后可以发现数据库中的部分表数据如下:

    两个线程可以异步执行互不干扰。

    上面数据库的显示 使用的是Navicat,也有其它的数据库管理软件可以显示。

    总结

    FMDB是一个在iOS上简化sqlite API的第三方库,对sqlite进行了很有好的封装,便于维护与增加效率。

  • 2017/11/17 posted in  iOS

    贪心算法(构造霍夫曼树)

    霍夫曼树的构造是使用了变长编码来减少数据所占空间,一般我们只考虑前缀码,即没有任何码字是其它码字的前缀。

    我们的构造过程如下所示:

    这里我们根据上述表示,我们每次总是“贪心”的选择两个最小的值来求和,并插入队列中。所以我们可以构造一个最小优先队列来存储各字母的频率。算法代码如下:

    //
    //  main.cpp
    //  HUFFMAN
    //
    //  Created by LZH on 2017/11/4.
    //  Copyright © 2017年 LZH. All rights reserved.
    //
    
    #include <iostream>
    #include <queue>
    #include <vector>
    using namespace std;
    
    typedef struct BitNode
    {
        int frep;
        struct BitNode *left;
        struct BitNode *right;
    }Node;
    
    //定义比较结构
    struct cmp1{
        bool operator ()(int &a,int &b){
            return a>b;//最小值优先
        }
    };
    //构造霍夫曼树
    void HUFFMAN(int c, priority_queue<int, vector<int>, cmp1> &que1)
    {
        int n = c;
        for (int i=0; i<n-1; i++) {
            Node *newNodeZ = (Node*)malloc(sizeof(Node));
            Node *newNodeX = (Node*)malloc(sizeof(Node));
            Node *newNodeY = (Node*)malloc(sizeof(Node));
            newNodeX->frep = que1.top();
            que1.pop();
            newNodeY->frep = que1.top();
            que1.pop();
            newNodeZ->left = newNodeX;
            newNodeZ->right = newNodeY;
            
            newNodeZ->frep =newNodeX->frep + newNodeY->frep;
            que1.push(newNodeZ->frep);
        }
    }
    
    int main() {
    //    priority_queue<int> que; //构造优先队列
        int b[6] = {9,5,12,13,16,45};
    //    vector<int> a(b,b+6);
        priority_queue<int , vector<int>, cmp1 >que1; //最小优先队列
        for (int i=0; i<6; i++) {
            que1.push(b[i]);
        }
        HUFFMAN(6, que1);
        while (!que1.empty()) {
            cout<<que1.top()<<' ';
            que1.pop();
        }
        return 0;
    }
    
    

    最后输出为100,返回了编码树的根节点。

    2017/11/4 posted in  算法理论

    贪心算法(活动选择问题)

    这个问题是一个典型的可以运用贪心算法的题目,我们在初始的选择一个活动之后,应该将之后的资源被其他任务尽量多的占用。所以我们应该第一个选择活动最快结束的活动,因为它剩下的资源可以被它之后开始的任务尽量多的使用。所以我们可以运用一种递归形式或者迭代算法来表达这个形式:

    //
    //  main.cpp
    //  Greedy_Activity_Selector
    //
    //  Created by LZH on 2017/11/3.
    //  Copyright © 2017年 LZH. All rights reserved.
    //
    
    #include <iostream>
    #include <vector>
    using namespace std;
    void print(int n)
    {
        cout<<n<<' ';
    }
    
    /**
     迭代形式函数
    
     @param s 活动开始时间数组
     @param f 活动结束时间数组
     @param result  用于存储结果
     */
    void GREED_ACTIVITY_SELECTOR(vector<int> s , vector<int> f , vector<int> &result)
    {
        int n = s.size();
        result.push_back(1);
        int k=1;
        for (int m=2; m<=n; m++) {
            if (s[m]>=f[k]) {
                result.push_back(m);
                k = m;
            }
        }
    }
    
    /**
     尾递归形式函数
    
     @param s 活动开始时间数组
     @param f 活动结束时间数组
     @param k 从活动k开始计算
     @param n 共有n个活动
     @param result 用于存储结果
     */
    void RECURSIVE_ACTIVITY_SELECTOR(vector<int> s,vector<int> f, int k, int n, vector<int> &result)
    {
        int m = k+1;
        while (m<=n && s[m]<=f[k]) {
            m++;
        }
        if (m<=n) {
            result.push_back(m);
            RECURSIVE_ACTIVITY_SELECTOR(s, f, m, n ,result);
        }
    }
    
    int main() {
        int a[12] ={0,1,3,0,5,3,5,6,8,8,2,12};
        int b[12] ={0,4,5,6,7,9,9,10,11,12,14,16};
        vector<int> s(a,a+12);
        vector<int> f(b,b+12);
        vector<int> result;
        RECURSIVE_ACTIVITY_SELECTOR(s, f, 0, 11, result);
        GREED_ACTIVITY_SELECTOR(s, f, result);
        for_each(result.begin(),result.end(),print);
        return 0;
    }
    
    

    其中递归形式的流程如下图所示:

    但两种算法运行时间类似都为\(\Theta(n)\)

    与动态规划的不同

    贪心算法通过做出局部最优来构造全局最优解。换句话说,直接做出在当前问题下看起来最优的选择,而不必考虑子问题的解。

    在动态规划中,每个步骤都要进行一次选择,但选择通常依赖于子问题的解,因此,我们通常以自底向上的方式来求解动态规划问题,先求解较小的子问题,然后是较大的子问题。但是在贪心算法中,我们总是做出在当时看起来最佳的选择,然后求解剩下的唯一子问题。贪心算法进行选择时可能依赖之前做出的选择,但不依赖于任何将来的选择或者是子问题的解。所以贪心算法在进行第一次选择时不求解任何子问题,自顶向下进行逐步选择,将给定的子问题实例变小。

    两者最典型的区别,可以用分数背包0-1背包问题来举例,这里不做细说。

    2017/11/3 posted in  算法理论

    最长公共子序列(动态规划)

    最长公共子序列问题(LCS)给定两个序列\(X=<{x_1,x_2,...x_m}>和Y=<y_1,y_2,...y_n>\),求X和Y长度最长的公共子序列。总共分三个步骤:

    步骤一:刻画最长公共子序列的特征

    步骤二:一个递归解

    步骤三:计算LCS的长度

    上述过程我们有如下图来表示:

    步骤4 构造LCS

    这里我们用C++来构造这个算法:

    //
    //  main.cpp
    //
    //  Created by LZH on 2017/10/30.
    //  Copyright © 2017年 梁中豪. All rights reserved.
    //
    
    
    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    using namespace std;
    
    //计算LCS的长度
    void LCS_LENGTH(char *X ,char *Y ,int **b ,int **c ,int &m ,int &n)
    {
        //    int m = sizeof(X)/sizeof(char);
        //    int n = sizeof(Y)/sizeof(char);
        
        //    int b[m][n];
        //    int c[m][n];
        
        for (int i=1; i<m; i++) {
            for (int j=1; j<n; j++) {
                if (X[i]==Y[j]) {
                    c[i][j] = c[i-1][j-1]+1;
                    b[i][j] = 1;
                }
                else if (c[i-1][j]>=c[i][j-1])
                {
                    c[i][j] = c[i-1][j];
                    b[i][j] = 2;
                }
                else
                {
                    c[i][j] = c[i][j-1];
                    b[i][j] = 3;
                }
            }
        }
    }
    
    void PRINT_LCS(int **b,char *X,int i,int j)
    {
        if (i==0 || j==0) {
            return;
        }
        if (b[i][j]==1) {
            PRINT_LCS(b, X, i-1, j-1);
            cout << X[i] <<endl;
        }
        else if (b[i][j]==2)
        {
            PRINT_LCS(b, X, i-1, j);
        }
        else
        {
            PRINT_LCS(b, X, i, j-1);
        }
    }
    
    int main()
    {
        char X[8] = {'x','A','B','C','B','D','A','B'};
        char Y[7] = {'y','B','D','C','A','B','A'};
        int m = sizeof(X)/sizeof(char);
        int n = sizeof(Y)/sizeof(char);
        int **b =new int*[m];
        for (int i = 0; i<m; i++) {
            b[i] = new int[n];
        }
        
        int **c = new int*[m];
        for (int i = 0; i<m; i++) {
            c[i] = new int[n];
        }
        LCS_LENGTH(X,Y,b,c,m,n);
        PRINT_LCS(b,X,m-1,n-1);
        return 0;
    }
    
    
    2017/10/24 posted in  算法理论

    动态规划

  • 总结

    动态规划与分治法很类似,都是通过组合子问题的解来求解原问题。但是不同的是动态规划应用于子问题重叠的情况,即不同的子问题也具有公共的子子问题。分治法在这种情况下,会反复的求解那些公共子子问题。而动态规划算法对每个子子问题只求解一遍,将其保存在一个表格中,避免每次都计算。

    钢条切割问题

    我们第一个动态规划问题是钢条切割问题。

    分治法

    我们来先看一下简答的分治法:

    //
    //  main.m
    //  CUT-ROD
    
    #import <Foundation/Foundation.h>
    
    @interface CUT_ROD:NSObject;
    
    -(NSInteger)CUT_ROD:(NSArray*)P length:(NSInteger)n;
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSArray *c = @[@1,@5,@8,@9,@10,@17,@17,@20,@24,@30];
            CUT_ROD *a = [CUT_ROD new];
            NSInteger result = [a CUT_ROD:c length:10];
            NSLog(@"result is %ld",result);
        }
        return 0;
    }
    
    @implementation CUT_ROD
    //简单的分治法,会重复计算子子结果
    -(NSInteger)CUT_ROD:(NSArray *)P length:(NSInteger)n{
        if (n==0) {
            return 0;
        }
        NSInteger q = -1;
        for (NSInteger i=0; i<n; i++) {
            q = q>([P[i] integerValue]+[self CUT_ROD:P length:n-i-1]) ? q:([P[i] integerValue]+[self CUT_ROD:P length:n-i-1]);
        }
        return q;
    }
    
    @end
    
    

    上述代码使用递归来实现的,但是每次都要递归的重复来计算相同的子子问题,如图所示:

    所以我们就在想,只要把求解得到的子子问题答案记录下,等到下次要用的时候直接调用它,就可以省下大量的时间。所以我们下面采用动态规划来写一遍。

    动态规划

    动态规划是一个典型的时空权衡的例子,付出额外的内存空间来降低计算时间。

    第一种方法:带备忘的自顶向下算法

    //动态规划 带备忘的自顶向下
    -(NSInteger)MEMOIZED_CUT_ROD:(NSArray *)P length:(NSInteger)n{
        NSMutableArray *logArr =[[NSMutableArray alloc] initWithCapacity:11];
        for (int i = 0; i<=10; i++) {
            [logArr addObject:@(-1)];
        }
        return [self MEMOIZED_CUT_ROD_AUX:P length:n log:logArr];
    }
    -(NSInteger)MEMOIZED_CUT_ROD_AUX:(NSArray*)p length:(NSInteger)n log:(NSMutableArray*)r{
        NSInteger q;
        if ([r[n] integerValue]>=0) {
            return [r[n] integerValue];
        }
        if (n==0) {
            q = 0;
        }else{
            q = -1;
            for (NSInteger i = 0; i<n; i++) {
                q = q>[p[i] integerValue] + [self MEMOIZED_CUT_ROD_AUX:p length:n-i-1 log:r] ? q : [p[i] integerValue] + [self MEMOIZED_CUT_ROD_AUX:p length:n-i-1 log:r];
            }
        }
        [r replaceObjectAtIndex:n withObject:@(q)];
        return q;
    }
    

    MEMOIZED_CUT_ROD方法引入r数组,记录每个长度的最优收益值,下面的方法将记录的值存入r数组中,并调用存入r的子子问题所得到收益值。

    第二个方法,字底向上版本,这个版本更为简单:

    -(NSInteger)BOTTOM_UP_CUT_ROD:(NSArray *)P length:(NSInteger)n{
        NSMutableArray *arrLog = [[NSMutableArray alloc] initWithCapacity:11];
        for (int i = 0; i<=10; i++) {
            [arrLog addObject:@(0)];
        }
        NSInteger q = -1;
        for (int j=0; j<n; j++) {
            q = -1;
            for (int i=0; i<=j; i++) {
                NSLog(@"%li",q);
                q = q > ([P[i] integerValue] + [arrLog[j-i] integerValue]) ? q : ([P[i] integerValue] + [arrLog[j-i] integerValue]);
                NSLog(@"%li",q);
            }
            [arrLog replaceObjectAtIndex:j+1 withObject:@(q)];
        }
        return [arrLog[n] integerValue];
    }
    

    依次来求解长度从1到n的最大收益。

    总结

    当我们思考一个动态规划的问题时,最好弄清涉及的子问题与他们的依赖关系。如上图的依赖图为:

  • 2017/10/23 posted in  算法理论