ios 串口开发

2016-04-26 18:11:20 xuexiiphone 阅读数 3618

+(int)PKOpenSerial

{

    int fd = open("/dev/tty.iap", O_RDWR | O_NOCTTY| O_NONBLOCK);//

    if(fd == -1)

    {

        printf("open serial error!");

    }

    if (ioctl(fd, TIOCEXCL) == -1)

    {

        printf("Error setting TIOCEXCL on %s - %s(%d).\n",

               "/dev/tty.iap", strerror(errno), errno);

    }

    struct termios options;

    struct termios oldoptions;

    tcgetattr(fd,&oldoptions);

    options = oldoptions;

    //   cfmakeraw(&options);//配为原始模式

    //配置波特率为115200

    cfsetispeed(&options,B115200);

    cfsetospeed(&options,B115200);

//    配置串口属性

    options.c_cflag |= (CLOCAL | CREAD);

    options.c_cflag &= ~PARENB;

    options.c_cflag &= ~CSTOPB;

    options.c_cflag &= ~CSIZE;

    options.c_cflag |= CS8;

//    启用设置

    tcsetattr(fd,TCSANOW,&options);

    return fd;

}


2016-08-23 00:30:02 xoperxoper 阅读数 10890

1 前言

Android和iOS是移动端的两大平台,Android以它的开源、易上手、开发成本低而受到广大开发者的青睐,而iOS作为苹果的封闭系统,以它的简单、流畅高效、高冷等特点也吸引了一大批果粉开发者,笔者在学校进行了近2年的Android 开发,现在因为工作原因转到了iOS,刚好学习研究iOS。目前学习了iOS刚好一个月,有一些心得体会,遂对比Android,并根据一些资料结合自己的理解进行了总结,力求做到全面。因为笔者对Android熟悉一点,所以笔者某些方面是站在Android的角度上分析。

2 View

不管是移动端、桌面端还是web端,UI一直是非常重要的,作为MVC的一部分,View承担了向用户展示界面的责任。

2.1 创建和布局

Android 有两种方式绘制View,一是使用xml文件,你可以在xml文件中用代码声明你的界面,也可以直接在可视化的界面上拖拽,控件可以通过R文件生成唯一对应的id,在Java 代码中就能通过id来找到控件并处理逻辑。二是在Java中使用代码绘制View和布局。笔者之前做过的项目,和实习是参加的项目基本都是用xml文件(代码声明),很少会直接使用代码。

iOS提供了三种方式来布局和绘制View,一是使用storyboard,二是使用xib文件(nib),三是直接在代码中布局。前两种方式是不需要代码的,将View和Controller剥离开来,而且不需要在类似xml的源码中添加代码,只需要在视图上鼠标操作就可以。第三种是在OC 中使用代码来绘制View与布局。使用代码布局更自由,而且能达到一切想要的效果。但是目前的问题是,在现在的项目代码中多数使用第三种方式布局和绘制View,我们的代码并没有将View 和Controller分离开,这对于MVC这种模式来讲,并不是好的。但是对于一些复杂的动态布局,只设置frame值可能不够,所以iOS新增了Auto layout自动布局,就是给View添加约束,大部分约束有点像Android里的相对布局。

还有一个小点让笔者不能适应的是,View的长宽属性中没有wrap_content这个概念,这使得在创建动态高度的自定义cell时,你需要自己计算每个cell的高度,这个折磨了我一段时间。

这里有个问题,为什么iOS的绘图性能一直优于Android,我觉得其中一个原因就是因为它简单的布局系统不会因为布局的复杂性增强而增加计算量,而Android不一样。

上面说到的5种布局都是ViewGroup的派生类,一个ViewGroup的生命周期经历3个阶段,分别是measure,layout和draw。在大部分情况下,ViewGroup都会自动的为它的子View撑开足够的空间,给子View计算出建议的宽和高和测量模式,决定子View的位置 。所以ViewGroup的子控件都是需要再次measure和layout的,在这就消耗了时间。在iOS中,如果不采用autolayout,使用View的frame那么所有位置(x,y,width,height)都已经确定了。所以iOS就不用进行measure和layout了,那么iOS只用尽管draw就行了。

2.2 控件

对于Android 和iOS,两个平台提供的官方的UI控件基本类似,基本能满足搭建一个简单App的需要。在iOS中基本都能找到和Android对应的控件,不同的是它们的API不同,使用方法不同,某些控件的外观能明显看出苹果的设计风格。(Android中的Toast其实很好用,但是不知道iOS中为什么没有)。

这里想重点说明的是TableView和ListView,列表是使用频率比较高的控件,Android的ListView使用了适配器Adapter来处理ListView的显示,这里使用了适配器模式。而iOS使用DataSource和delegate来代替Adapter的作用,可以通过dataSource 和delegate来设置每一行的外观样式,每一行的高度等等。

还需要补充说明的是,作为苹果这样一个使用闭源框架和设计主导的公司,其特点其实在控件中有所体现。Google的感觉就是扔给你一堆控件你可以随意组合,自由搭配。虽然都是给你一堆控件,但是苹果已经给你制定好了一些规范,并推荐应该怎样做。例如用UINavigationController创建一个需要层次化流程的界面,用UITarBarController创建我们大多数App 的主要功能页,用UIPageControl创建引导页或者广告栏,用UISearchBar创建搜索栏。Android中虽然有类似的比如TabHost这种控件,但是想要创造UITarBarController和UINavigationController搭配的效果,还是需要很多代码的。就这点来看,iOS方便了很多,只需要一些简单的官方控件就能构建起一个App。

3 Controller

Controller本职工作是控制UI和处理逻辑,其中UI Controller作为对View的Controll是很关键的,另外数据传递和IPC也算逻辑的一部分,所以放到这一节。

3.1 UIController

学习Android 的过程中,扑面而来的概念是Android的四大组件,而最重要的组件莫非Activity,强调一个Activity就是一个界面,而笔者在IOS 的学习过程中(看了《疯狂iOS》、《精通iOS开发》)都没有强调这个概念,而且对界面的跳转也没有做详细说明,也没有对UIViewController的管理做说明,笔者很是笔者一直在iOS 中寻找与Android对应的四大组件,首先UIViewController其实就是对应的Activity,UIViewController生命周期也与Activity的很类似。

Activity在onCreate()中初始化操作, 在onResume()中可以加一些改变界面和状态的操作。
UIViewController在 -viewDidLoad中初始化操作, 在-viewWillAppear 中可以加一些改变界面和状态的操作。

UIViewController整个生命周期:-viewDidLoad–> -viewWillAppear–> -viewDidAppear –> 运行态–> -viewWillDisAppear–> -viewDidDisAppear-> -viewDidUnload。

Android的Activity是由Activity栈进管理,当来到一个新的Activity后,此Activity将被加入到Activity栈顶,之前的Activity位于此Activity底部。可以设置Activity的taskAffinity和launchMode,以改变Activity入栈的形式。

iOS没有UIViewController启动模式,但提供有三种对视图的管理方式:

  • UITabBarController:以平行的方式管理视图,各个视图之间往往关系并不大,每个加入到UITabBarController的视图都会进行初始化即使当前不显示在界面上,相对比较占用内存。
  • UINavigationController:以栈的方式管理视图,各个视图的切换就是压栈和出栈操作,出栈后的视图会立即销毁
  • UIModalController:以模态窗口的形式管理视图,当前视图关闭前其它视图上的内容无法操作。调用presentViewController进入一个新的视图,调用dismissViewControllerAnimated会回到上一个视图。

另外Android可以自由控制一个Activity ,想要销毁一个Activity的时候,就调用finish函数。而iOS 使用了ARC,并不主动提供销毁UIViewController的方法,只有等内存不足时,自动销毁非前台的UIViewController,这点是不一样的。但是我们可以让通过以下操作UIViewController提前自动释放。
1) 将任何引用到该controller的变量设置为空。
2) [controller.view removeFromSuperview]
3) controller.view = nil
4) controller = nil

3.2 数据传递

我们这里的数据传递主要是指的在一个App中进行数据传递,即在一个进程内的数据传递。Android里数据传递主要由Intent来承担,主要的数据传递方式如下:

  • Intent,又叫意图或者目的,是不同组件之间相互通讯的纽带,封装了不同组件之间通讯的条件。可以通过Extra向Intent中放入一些基本数据,也可以利用Bundle通过Serializable接口或者Parcelable接口非基本数据或者对象,使用起来非常简单和灵活。通常的Activity 跳转都是使用Intent。
  • 利用全局静态数据,public static成员变量,或者单例模式,这个很容易理解,就是通过一个全局变量去传递数据。
  • 使用文件,这个没什么好说的,保存到文件,然后在另一个地方从文件中取出。
  • 广播BroadCast,是Android四大组件之一,应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。要实现广播,首先需要注册一个BroadCastReceiver,通过sendBroadcast这个方法来发送,之后在BroadCastReceiver的onReceiver接收消息。

    iOS的数据传递方式有以下这些:

  • 对象的@property属性值,这种方式主要用于在界面跳转时的数据传递。

  • 给App的Delegate设置成员变量,在代码中可以通过获得当前App的delegate而获得成员变量,这种方式和设置全局变量类似。
  • 通过extern定义全局变量,或者单例模式,这点和Android一样。
  • 使用文件,或者NSUserdefault来传递,原理和Android一样。
  • NSNotificationCenter,与Android BroadCast对应,可以看作iOS的广播。观察者通过addObserver向NSNotificationCenter注册特定的消息通知和特定的接收函数,发送者通过postNotificationName向NSNotificationCenter发送特定的消息,之后观察者在接收函数中处理消息。与Android的BroadCast有一点不同的是NSNotificationCenter只在App内,Android BroadCast是可以在系统级。

3.3 IPC

与iOS相比,Android的进程间通信方式有很多,如ContentProvider,Activity,广播和AIDL等,不过这些方式的本质都是binder。

  • Content Provider是Android四大组件之一,它提供了一种在多个应用程序之间数据共享的方式。应用程序可以利用Content Provider完成查询数据,修改数据,添加数据,删除数据。需要创建ContentResolver对象与ContentProvider进行交互,通过指定的URI确定要访问的ContentProvider数据集。
  • Activity很好理解,就是可以在自己的App跳转到别的程序的界面。但跨进程访问并不需要指定Context对象和Activity的Class对象,而需要指定的是要访问的Activity所对应的Action,有些Activity还需要指定一个Uri。
  • 广播,前面说过广播可以在系统级,所以广播能跨进程通信,一些系统通知经常会用到它,例如通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项等等。
  • AIDL实际上是一种接口定义语言。通过这种语言定义接口后,Eclipse插件会自动生成相应的Java代码接口代码。它需要创建AIDL文件和一个Service来配合使用。
  • Binder。这是最底层的进程通信了。主要是有ServiceManager这样一个东西来控制,所有的服务都要通过它来注册,提供它的binder地址,其它程序如果需要哪种服务就向它申请,在ServiceManager获得了对端地址之后,你们两就通过Binder驱动进行通信了。

对于Binder推荐浏览一篇博客,写的非常详细:
http://blog.sina.com.cn/s/blog_73799d980101sa48.html

而对于iOS来说,为了避免数据冲突和更好的提供更好的安全机制,iOS提供了沙盒机制。尽管如此,iOS还是提供了若干今晨间通信机制,如URL Scheme、剪切版、CoreCFMessagePort和CFNotificationCenter等等。

  • URL Schema就是iOS内的应用调用协议,应用A可以声明自定义的调用协议,就如http/https那样,当另一个应用B打算在应用内打开应用A时,可以打开使用A自定义的协议开头的URL来打开A,除了协议头,URL中还可以附加其它参数。
  • 剪切版很好理解,用户可以跨应用拷贝了一段文字,图片,文档等。在代码中对应的类是:UIPasteboard。
  • CFMessagePort,通过mach port实现的。它需要消息接收者和消息发送者。消息发送者需要通过CFMessagePortCreateLocal、CFMessagePortCreateRunLoopSource、CFRunLoopAddSource来注册监听,并自己实现回调方法。发送者通过CFMessagePortCreateRemote生成一个Remote的CFMessagePortRef,并通过CFMessagePortSendRequest发送数据。遗憾的是iOS7以后不能用这个方法了。
  • CFNotificationCenter通知可以是系统级别的,它的原理和NSNotificationCenter一样。CFNotificationCenter只提供类方法CFNotificationCenterGetDistributedCenter获取通知发布中心,需要通过CFNotificationCenterAddObserver添加所要指定监听消息名和观察者到通知发布中心,当消息接收到的时候函数指针指向的函数将被执行一次。发送着只需要通过CFNotificationCenterPostNotification发送消息名、对象还有user info就可以了。

4 Model

4.1 存储

Android存储数据的方式有以下几种:

  • Shared Preferences:是用来存储一些Key/Value类似的成对的基本数据类型。它只能存储基本数据类型,也即int, long, boolean, String, float。它是采用了XML格式将数据存储到设备中,在DDMS中的File Explorer中的/data/data/shares_prefs下。一般我们用Shared Preferences保存一些简单的用户数据。
  • 内部存储与外部存储:内部存储指的是手机内置的存储空间,使用内部存储主要有二个方式,一个是文件操作,一个是文件夹操作。外部存储就是读写sdcard上的文件。
  • Sqlite3:是一款开源的嵌入式关系型数据库,可移植性好、易使用、内存开销小,一般手机上的小型存储都会用到sqlite。android.database.sqlite包封装了很多SQLite操作的API,如insert、delete、update等等。但是我更喜欢用execSQL(String sql)来直接执行sql命令。

iOS都是沙盒存储,数据都在App的目录下,iOS的存储方式有以下几种:

  • NSUserDefaults与Android的SharedPreferences原理相同。用来保存应用程序设置和属性、用户保存的数据。NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。
  • NSKeyedArchiver:归档采用归档的形式来保存数据,该数据对象需要遵守NSCoding协议。对象对应的类必须提供encodeWithCoder和initWithCoder方法,对对象进行编码和解码。
  • 使用属性列表:保存数据到documents目录,这个一般用来保存一些简单的应用数据。
  • Sqlite3:iOS上也使用Sqlite,不过语言没有使用OC,而是用的C语言,而且没有提供那么多API,需要自己执行sql命令。
  • Core Data:是对SQLite的封装,提供了更高级的持久化方式。在对数据库操作时,不需要使用sql语句。在CoreData中主要使用的几个类。 NSManagedObjectModel 相当于实体,不过它包含了实体间的关系;NSManagedObjectContext操作实际内容,数据的增删改查;NSPersistentStoreCoordinator 相当于数据库的连接器 NSFetchRequest 相当于查询语句;NSPredicate相当于查询条件。其实感觉有点像Android中的Content Provider的形式。

4.2 垃圾回收

对于垃圾回收,由于Android使用Java语言,所以就自动沿用了Java的GC机制,Java有两种方式进行垃圾标记,一种是引用计数,一种是可达性分析算法,但一般选择可达性分析算法,因为引用计数会有循环引用问题。对于垃圾清理有四种方式:标记清理、复制清理、标记整理和分带收集。不同的GC器会选择不同的清理方式。Android有自己的虚拟机,5.0以前是Dalvik,以后是ART,不同的虚拟机也有不同的GC器,不同的GC器也会选择不同的清理方式。

对于iOS,iOS 5以前都是使用OC的手动回收机制,也就是引用计数,它的规则是这样的:如果需要持有一个对象,那么对其发送retain,如果之后不再使用该对象,那么需要对其发送release或者autorelease,每一次对retain,alloc或者new的调用,需要对应一次release或autorelease调用。所以我在网上很多博客里看到的iOS的代码都还有release和autorelease。

iOS5以后苹果引入了ARC内存管理机制,这是一种编译期的内存管理方式,从此不再需要手动释放对象了,要注意的是这里的ARC并不是GC,而只是由编译器自动添加retain和release代码,归根到底还是引用计数。ARC带来的好处是代码变得简单,代码的总量变少了,代码高速化,由于使用编译器管理引用计数,减少了低效代码的可能性。

尽管两个平台现在都已经有了自动处理垃圾的机制,但是我们还是需要小心处理某些对象和过程,以防发生内存泄漏。
Android可能的内存泄漏,例如使用完Bitmap之后没有recycle,使用完Sqlite数据库后没有关闭数据库,ListView中没有使用ViewHolder,使用完广播没有注销,使用Thread没有置空。
iOS里例如OC上的循环引用,NSTimer 会持有target,block循环引用变量,使用了NSNotificationCenter后没有注销,View中存在大量对象,显示结束时,没有及时置空,图片加载的复用等等。

5 多线程

Android中有主线程也叫UI线程,如果我们在UI线程进行了长时间(大于5s)的耗时操作,那么就会出现ANR(Application Not Responding)。iOS中也有主线程的概念,尽管没有ANR,但是长时间的占用主线程进行耗时操作将会带来非常差的体验感,所以Android和iOS都需要异步处理。

5.1 工具

Android与异步相关的工具有以下几种:

  • Runnable/Thread 是Java中的,可以扩展继承Thread类来创建一个线程,也可以继承Runnable接口来创建一个线程,再结合Android中的Handler即可以在子线程中更新主线程UI了。
  • ExecutorServie线程池也是Java中的概念。它的作用是通过重复利用已经创建的线程来避免创建和销毁线程造成的消耗,同时提高响应速度和线程的可管理性。当Android中有一些不需要更新UI的操作,通常采用这个方法。
  • IntentService它是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样。当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行。它处理业务流程非常方便。
  • AsyncTask是Android提供的轻量级的异步类,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程。它最重要的两个方法,一个是doInBackground(params):处理耗时事务,并把结果返回。一个是onPostExecute(result)可以使用在doInBackground得到的结果处理操作UI。很多人都推荐使用它处理需要更新UI的操作。
  • Service作为四大组件之一,用于在后台管理和处理一些耗时的逻辑线程或者某些需要长期运行的线程。这里要说明的是Service不是运行在后台线程的,它仍然是运行在UI主线程,所以如果要用它处理耗时任务,你还是得在其中创建新线程。它的作用是能在后台对线程进行良好的管理,对外提供AIDL接口。Service的一个经常的应用是保持着心跳连接。

iOS中异步工具有以下几种:

  • NSThread这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。NSThread轻量级,使用简单需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销。所以我们一般不会用这个。
  • GCD是苹果为多核的并行运算提出的解决方案,会自动合理地利用更多的CPU内核,最重要的是它会自动管理线程的生命周期,完全不需要我们管理,我们只需要告诉干什么就行。它使用C语言,由于使用了Block,使得使用起来更加方便,而且灵活。很多App都会使用这套方案。它有两个概念:任务和队列。
    任务分同步和异步。同步任务会在当前线程执行,不会另开线程,会阻塞当前线程,直到block中的任务执行完毕(block在当前线程中执行)。异步任务会另开线程,在别的线程执行。当前线程会直接往下执行,不会阻塞当前线程。(block在新线程中执行)。队列用于存放任务。一共有两种队列,串行队列一次只执行一个线程,FIFO。并行队列:一次可以执行多个线程。
  • NSOperation和NSOperationQueue。NSOperation 是苹果公司对 GCD的封装,完全面向对象,所以使用起来更好理解。NSOperation 和NSOperationQueue分别对应GCD的任务和队列,它的用法和GCD很像。与GCD不同的是它的任务能被取消,队列可以暂停、恢复,NSOperation还可以被子类化。网络上支持GCD和NSOpration都大有人在,各说各的理,总的来说这两种多线程方式都是被现在的开发人员接收的。

5.2 looper &runloop

Android里的消息处理机制:消息是存放在一个消息队列中,应用程序的主线程围绕这个消息队列进入一个无限循环的,直到应用程序退出。Looper就是消息处理机制的关键,每一个线程都有唯一的Looper,主线程中的Looper默认开启。如果队列中有消息,应用程序的主线程就会把它取出来,并分发给相应的Handler进行处理;如果队列中没有消息,应用程序的主线程就会进入空闲等待状态,等待下一个消息的到来。

这里要注意的是其它线程不是默认开启的。但是如果你需要线程在处理完一些事情后,不要自己停止,那么就需要使用Looper。Looper由四个部分组成:

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
这里写图片描述
详细介绍见:
http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html

iOS也有类似的东西名叫runloop,它的作用其实和looper 是一样,每个线程只有一个Runloop,在主线程中默认启动,一直循环接收消息。当你需要在其它线程干一些其它事时也可以自己启动。不能自己创建RunLoop,CFRunLoopGetMain和 CFRunLoopGetCurrent可以获得Runloop。RunLoop负责处理两种消息事件,输入源事件和计时器事件。

这里写图片描述

它具体的作用是维护线程的生命周期,让线程不自动退出;创建常驻线程,执行一些会一直存在的任务。
详细介绍见:http://blog.ibireme.com/2015/05/18/runloop/

6 语言(Java vs OC)

Android使用Java编写,iOS使用OC,两大语言一个在Java虚拟机上静态执行,另一个动态执行,虽然原理不同,但是也有很多相似的地方。

6.1 基础

类似点:
 Java的顶层类是Object,OC的顶层类是NSObject。
 Java的toString()对应OC的description。
 Java有匿名内部类和闭包,OC有block,但block可以访问外部代码。
 Java有String、List、Set、Map等数据结构,OC有NSString、NSArray/NSMutableArray、NSSet/NSMutableSet、NSDictionary/NSMutableDictionary。
 Java有各种基础类型的包装类,OC也有。
 Java用new创建对象,OC用alloc init创建对象。
 Java有强、软、弱、虚四种引用类型,OC使用weak, strong用来修饰变量。
不同点:
 首先Java是静态的,OC是动态的且使用消息传递。
 Java有空指针异常,OC没有。
 Java 语法简洁,OC稍微有点啰嗦。
 Java有命名空间和包管理,OC没有,所以需要加类前缀。
 Java使用引用,OC使用指针。
 Java类写在一个文件里,OC有头文件
 OC方法名可以进行拆分 ,Java不可以。
 OC 有KVO、KVC。
 OC中有category,也叫非正式协议、类目。可以对现有的类进行扩展。

6.2 面向对象

面向对象三个特点是:封装,继承,多态。

另外Java的类成员变量需要自己写setter和getter,而OC中可以通过@ property字段来声明变量,就避免了写繁琐的setter和getter了。另外在OC的对象方法中,对成员变量的获取一般不用getXXX,而直接写成员变量的名称来作为getter方法的函数名。

继承:Java和OC继承是类似的,都有父类子类,子类继承父类的变量和方法,而不允许多重继承,this 对应self ,不同的是Java有重写和重载,OC 只有重写。

而OC中也有类似接口一样的东西,它的名字叫协议(protocol)。协议的字面意思就是你要遵守一组特定协议。用关键字@protocol来声明一个协议。所有绑定该协议的类也要实现该协议所声明的方法,只不过有些方法是必须实现的,有些是可选的,协议中用@required @optional来区分。达到的效果和Java接口是类似的。

6.3 高级特性

Java有反射机制,它允许程序创建和控制任何类的对象,无需提前硬编码目标类。它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。不过因为Java的方法是与class静态绑定的,所以不能改变方法。

然而OC与Java最大的不同是OC是动态的,因此方法、类和对象可以在运行时确定和修改,所以诞生出了Method Swizzing,它提供以下方法。

  • class_replaceMethod 替换类方法定义
  • method_exchangeImplementations 交换两个方法实现
  • method_setImplementation 设置一个方法实现

Method Swzzing能够做到修改系统API和面向切面的编程,这是非常强大的,可以利用它进行动态换肤,针对特定的版本的系统库函数打Patch,服务器动态修改客户端逻辑等等。

7 未来

  • Material Design vs Apple Design

Material Design是Google2014 年提出的一套UI风格,中文名叫质感设计。这套风格定义了一系列的设计理念,包括主题、颜色、阴影、布局和动画等等。这套风格不仅包括移动端还包括web端。Android
5.0上大量的使用了Material Design风格的东西,还推出了官方的Material Design控件包,使用这个控件包可以很方便的搭建出一个具有Material Design风格的App。Material Design风格的App在体验上比现在普通的App要好很多。Android 6.0上又对这个包进行了扩展。凭借这套设计,Google力求统一用户UI的风格。遗憾的是现在市面上常用的android App 只有很少的几款使用了这个设计,笔者发现的只有格瓦拉、印象笔记、知乎。

Apple一直都是一家设计著称的公司,每一款iPhone、每一款pad都能吸引成千上万的人购买,可见大众对Apple Design的认同。在系统上也一样,iOS一直以流畅精美著称,系统中每一个控件或者都是Apple仔细斟酌的而设计的。每次的更新中iOS 也在对自己的UI进行不断优化,并提供新的控件使用,如最新的UIStackView(这个其实类似Android的线性布局),Widget等等。

  • Kotlin vs Swift

Kotlin,可能大多是人对这门语言并不熟悉,它被成为Android上的swift。Kotlin由JetBrains设计开发并开源,虽然不是Google官方推出的语言,但是它一样被很多人看好。与Java相比,Kotlin的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。它与Java高度可互操作,可以同时用在一个项目中。前段时间Oracle还就Java一事从Google那儿拿了不少赔偿费,Google会不会使用新的语言来代替Java呢?我们不得而知。

Swift是苹果新推出的用来代替OC的新语言,它一发布便被很多开发者迅速的接受并使用,它与OC相比更简单、灵活、执行速度快、支持更多高级特性,但是因为项目代码遗留的原因,大部分大公司的iOS都还没开始使用Swift。但是苹果官方是支持Swift的,并且将Swift开源,大部分API都在往Swift上靠,趋势已经很明朗了,未来Swift必然会替代Objective-C,但现在还是OC的时代。

  • React Native

React Native是Facebook推出的一套开源框架,它的目标是Learn once,write anywhere。React Native能够让你在使用Javascript和React的基础上获得完全一致的开发体验(android、iOS、web和服务器都使用的一样的语言),构建世界一流的原生APP。当然现在它在移动端火爆的原因不是因为语言统一,而是它能够像网页一样,随时更新,随时变化,这是现在的Android和iOS很难做到的。由于使用一个语言,将再也没有前端,终端,后台的区分,开发人员所关注的就是做一整套应用程序,所以现在新的创业公司或者个人会比较倾向于React Native。但是由于React Native技术刚刚出来不久,还有很多待改进的和bug,组件不全第三方组件也不全,性能并不如Native的,很多公司还是小心尝试,不过还是有一些大公司用它来完成部分页面。未来React Native会不会成为移动端的主流框架,这还需要看发展。但是这是一个互联网的时代,互联网生态必然会积累出更加优秀的框架,不管是React Native还是其它框架,它们一定会支撑更加伟大的产品出现。

8 写在最后

Android和iOS作为移动端的OS,市场占有率超90%。一个属于Google一个属于Apple,在过去的几年中它们斗志斗勇,每个版本的发布都让开发者们兴奋好一段时间。

笔者学习了iOS一个月,总的来说觉得iOS和Android还是有很多共通之处的,比如:Activity 和UIViewController,相对布局和auto Layout,大部分控件,BroadCast和广播,looper和runloop等等,Java和OC也有很多共通性,虽然出自两个公司,但是很多思想很相相似。对不不同的东西,上文已经叙述的足够多,从宏观上讲iOS和Android最大的不同应该是一个底层是linux系统,一个是苹果特有的封闭系统。苹果特有的系统,能够保证iOS和Android相同的配置下,在显示、动画和运行效率上高出好几个档次,这是Android需要考虑优化的。另外还有一点是,两个系统一个开源一个闭源,这就像这就像搭积木的时候,一个给你各种各样积木块,你想怎么搭就怎么搭,你还可以拆开积木块,自己创造新的积木,但是搭出来的东西是好是坏,是丑是美自己负责。另一个只提供部分的改善过的积木块,并提供一些标准化规则和建议,让你能按照规则搭建,虽然不自由,但是能保证你最后做出来的东西是精美的。我个人来说更喜欢自由,因为自由往往代表着想象力和创造力,而且现在讲求共享,共享的东西往往能够产生出更多的东西。

未来是Android一统江湖,还是iOS独领风骚,又或者像现在这样二分天下一直竞争下去,我们拭目以待!

由于笔者水平有限,如果文章中有错误、认识存在偏差或者不全面的地方,还请各位前辈不吝赐教!!!

2017-05-27 09:19:38 ZCLengendary 阅读数 1353

 本文默认读者对蓝牙开发有基础的了解, 与外设的交互使用 BabyBluetooth.

      一. 总结的要点如下:

1. iOS 蓝牙与外设连接的步骤.

2. 外设过滤, 服务, 特性.

3. 单模,双模蓝牙.

4. 外设的 UUID.


二. 实际应用场景:

通过 APP 控制荣泰按摩椅, 方便用户切换按摩模式.


三. 第一点对应 OC 代码

1. 在 BabyBluetooth 库中, BabyBluetooth Class 封装了与蓝牙外设交互的所有方法,通过舒适化 BabyBluetooth ,实现代理方法,就可以与外设进行通信.

    //1 初始化
    //2 扫描设备
    //3 连接外设
    //3.1 查找服务
    //3.2 查找到Characteristics并筛选
    //4 写入数据
    //5 读取转换数据

2. 在搜索到蓝牙设备后,要知道每个蓝牙设备都会不少于一个的服务,每个服务都会有不少于一个的特性,我们与外设进行通信,是要约定好这个唯一的特性后,才可正确得进行串口通信.其中特性的属性有读,写两种,我们根据硬件工程师提供给我们的标识,可以获取到这两个需要的特性.

//连接Peripherals成功的委托
    [weakSelf.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) {
        NSLog(@"已连接的设备:____%@", peripheral);
        //搜索指定服务
        [peripheral discoverServices: @[[CBUUID UUIDWithString:SERVICE_UUID]]];
        self.isConnected = YES;
        
    }];
//查找服务
    [weakSelf.baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error) {
        
        for (CBService *service in peripheral.services) {
            if([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]]){
                [peripheral discoverCharacteristics:nil forService:service];
            }
        }
    }];
//查找到Characteristics的block
    [weakSelf.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
        
        for (CBCharacteristic *c in service.characteristics) {
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_RX]]) {
                NSLog(@"CHARACTERISTIC_RX:_____%@", c);
                self.characteristicR_x = c;
                if (c.isNotifying) {
                    [self.baby cancelNotify:peripheral characteristic:c];
                } else {
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                }
            }
            
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_TX]]) {
                
                self.characteristicT_x = c;
                NSLog(@"CHARACTERISTIC_TX:_____%@", c);
                if (c.isNotifying) {
                    [self.baby cancelNotify:peripheral characteristic:c];
                } else {
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                }
            }
        }
    }];


3.蓝牙模块有单模,双模之分,蓝牙双模是指其支持传统蓝牙的Basic Rate(BR)和增强数据率(EDR)工作,也支持最新的低功耗(LE)标准。

4.有一些公司的外部设备的名字都是一样的但是进行连接或者其他操作的时候没必要知道具体的是哪一台设备那么就可以使用蓝牙外设的UUIDString(peripheral.identifier.UUIDString)来作为唯一标识。但是需要注意的一点不同的中心设备也可以说是不同的手机对于同一台蓝牙设备获取到的UUIDString是不一样的。举例说明一下对于同一台蓝牙设备我的手机进行扫描然后读取它的UUIDString,和你的手机进行扫描获取到的UUIDString是不同的。


除了这些,在实际测试中,出现了这种情况:一些型号的按摩椅带有蓝牙音箱,因此,想要通过按摩椅播放手机中的歌曲,又要通过手机 APP 控制按摩椅需要进行两次蓝牙连接.首先在 APP 中发现蓝牙设备并连接,这里是对按摩椅进行控制;其次在设置中发现设备并连接,这里相当于连接了蓝牙音箱,连接后在设置中会出现两个蓝牙名称,如下图:




关于这一点,客户在进行测试的时候提出了这样一个问题:为什么需要两次蓝牙连接?而不是通过 APP 直接完成两次连接要达到的目的? 最佳合理的解释是: Apple 并没有赋予 CoreBluetooth 的接口最高的权限,所以通过 APP 进行的连接只可与蓝牙设备进行串口通信,而想要实现最高权限所能完成的功能,还是要依靠 iOS 系统的!


附: demo 主要代码

//
//  ViewController.m
//  BLE_DEMO
//
//  Created by 张闯 on 17/5/10.
//  Copyright © 2017年 张闯. All rights reserved.
//

#import "ViewController.h"
#import "DeviceViewController.h"


@interface ViewController () <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong)  BabyBluetooth       *baby;
@property (nonatomic, strong)  UITableView         *tableView;
@property (nonatomic, strong)  NSMutableArray      *dataSource;

@end

@implementation ViewController

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.baby cancelAllPeripheralsConnection];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.title = @"设备列表";
    
    self.tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:_tableView];
    
    self.dataSource = [NSMutableArray array];
    //初始化
    self.baby = [BabyBluetooth shareBabyBluetooth];
    //设置蓝牙代理方法
    [self babyDelegate];
    //扫描外设
    self.baby.scanForPeripherals().begin();
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshBLE)];
}

- (void)refreshBLE {
    [self.dataSource removeAllObjects];
    [self.baby cancelAllPeripheralsConnection];
    self.baby.scanForPeripherals().begin();
}


//设置蓝牙委托
- (void)babyDelegate{
    
    //设置扫描到设备的委托
    __weak typeof(self) weakSelf = self;
    [weakSelf.baby setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
        __strong typeof(self) strongSelf = self;
        
        if (![strongSelf.dataSource containsObject:peripheral]) {
            [strongSelf.dataSource addObject:peripheral];
            [self.tableView reloadData];
        }
        
    }];
    
    //过滤器
    //设置查找设备的过滤器
    [weakSelf.baby setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) {
        if (peripheralName.length >1) {
            return YES;
        }
        return NO;
    }];
    
    
    //设备状态改变
    [weakSelf.baby setBlockOnCentralManagerDidUpdateState:^(CBCentralManager *central) {
        
        switch (central.state) {
            case CBManagerStatePoweredOn:
                NSLog(@"CBManagerStatePoweredOn");
                [SVProgressHUD showSuccessWithStatus:@"蓝牙已打开"];
                break;
            case CBManagerStatePoweredOff:
                NSLog(@"CBManagerStatePoweredOff");
                [SVProgressHUD showErrorWithStatus:@"蓝牙已关闭"];
                break;
            case CBManagerStateResetting:
                NSLog(@"CBManagerStateResetting");
                break;
                
            case CBManagerStateUnsupported:
                NSLog(@"CBManagerStateUnsupported");
                break;
                
            case CBManagerStateUnauthorized:
                NSLog(@"CBManagerStateUnauthorized");
                break;
                
            case CBManagerStateUnknown:
                NSLog(@"CBManagerStateUnknown");
                break;
        }
    }];
    
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _dataSource.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    cell.textLabel.text = [_dataSource[indexPath.row] name];
    return cell;
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    DeviceViewController *deviceVC = [[DeviceViewController alloc] init];
    deviceVC.peripheral = _dataSource[indexPath.row];
    [self.navigationController pushViewController:deviceVC animated:YES];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

//
//  DeviceViewController.m
//  BLE_DEMO
//
//  Created by 张闯 on 17/5/11.
//  Copyright © 2017年 张闯. All rights reserved.
//

#import "DeviceViewController.h"

#define SERVICE_UUID            @"XXXX"
#define CHARACTERISTIC_TX       @"XXXX"
#define CHARACTERISTIC_RX       @"XXXX"

@interface DeviceViewController ()

@property (nonatomic, strong) BabyBluetooth         *baby;
@property (nonatomic, strong) CBCharacteristic      *characteristicR_x;
@property (nonatomic, strong) CBCharacteristic      *characteristicT_x;
@property (nonatomic, strong) NSArray               *autoTypeArray;
@property (nonatomic, assign) BOOL                  isConnected;
@property (weak,   nonatomic) IBOutlet UILabel      *receivedDataLabel;

@end

@implementation DeviceViewController



- (NSArray *)autoTypeArray {
    if (!_autoTypeArray) {
        _autoTypeArray = @[@"Wake", @"Energize", @"Perform", @"Recover", @"Upper", @"Lower"];
    }
    return _autoTypeArray;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    

    self.baby = [BabyBluetooth shareBabyBluetooth];
    [self babyDelegate];
    
    //连接外设
    self.baby.having(self.peripheral).connectToPeripherals().begin();
    self.baby.characteristicDetails(self.peripheral,self.characteristicR_x);
    
}


//设置蓝牙委托
- (void)babyDelegate {
    
     __weak typeof(self) weakSelf = self;
    //连接Peripherals成功的委托
    [weakSelf.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) {
        NSLog(@"已连接的设备:____%@", peripheral);
        //搜索指定服务
        [peripheral discoverServices: @[[CBUUID UUIDWithString:SERVICE_UUID]]];
        self.isConnected = YES;
        
    }];
    
    //查找服务
    [weakSelf.baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error) {
        
        for (CBService *service in peripheral.services) {
            if([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]]){
                [peripheral discoverCharacteristics:nil forService:service];
            }
        }
    }];
    
    //设置获取到最新Characteristics值的block
    [weakSelf.baby setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error) {
        char rData[999];
        int len = (int)characteristic.value.length;
        [characteristic.value getBytes:rData length:len];
        NSData *d = [[NSData alloc] initWithBytes:rData length:len];
        
        if (d.length == 19) {
            self.receivedDataLabel.text = [self transferData:d];
        }
    }];
    
    
    //查找到Characteristics的block
    [weakSelf.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
        
        for (CBCharacteristic *c in service.characteristics) {
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_RX]]) {
                NSLog(@"CHARACTERISTIC_RX:_____%@", c);
                self.characteristicR_x = c;
                if (c.isNotifying) {
                    [self.baby cancelNotify:peripheral characteristic:c];
                } else {
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                }
            }
            
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_TX]]) {
                
                self.characteristicT_x = c;
                NSLog(@"CHARACTERISTIC_TX:_____%@", c);
                if (c.isNotifying) {
                    [self.baby cancelNotify:peripheral characteristic:c];
                } else {
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                }
            }
        }
    }];
    
    
    //characteristic订阅状态改变的block
    [weakSelf.baby setBlockOnDidUpdateNotificationStateForCharacteristic:^(CBCharacteristic *characteristic, NSError *error) {
        NSLog(@"uid:%@,isNotifying:%@",characteristic.UUID,characteristic.isNotifying?@"on":@"off");
    }];
    
    //写Characteristic成功后的block
    [weakSelf.baby setBlockOnDidWriteValueForCharacteristic:^(CBCharacteristic *characteristic, NSError *error) {
        NSLog(@"t_xCBCharacteristic: ____ %@", characteristic);
    }];

    
    //断开Peripherals的连接
    [weakSelf.baby setBlockOnDisconnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) {
        NSLog(@"%@", peripheral);
        
        if ([peripheral isEqual:self.peripheral]) {
            [SVProgressHUD showErrorWithStatus:@"断开链接了"];
            self.isConnected = NO;
        }

    }];
    
    
}

- (IBAction)send:(id)sender {
    [self writeData:0x01];
}

- (IBAction)auto1:(id)sender {
    [self writeData:0x10];
}

- (IBAction)auto2:(id)sender {
    [self writeData:0x11];
}

- (IBAction)auto3:(id)sender {
    [self writeData:0x12];
}
- (IBAction)auto4:(id)sender {
    [self writeData:0x13];
    
}
- (IBAction)auto5:(id)sender {
    [self writeData:0x14];
}
- (IBAction)auto6:(id)sender {
    [self writeData:0x15];
}

- (void)writeData:(int)senderId {
    
    NSMutableData *data = [NSMutableData data];
    uint8_t tmp[10];
    uint8_t checksum = 0;
    tmp[0] = 0xF0;
    tmp[1] = 0x03;
    tmp[2] = senderId;
    checksum = tmp[1] + tmp[2];
    checksum = ~checksum;
    checksum = checksum & 0x7f;
    tmp[3] = checksum;
    tmp[4] = 0xF1;
    [data appendBytes:(void *)(&tmp) length:5];
    
    //写入数据
    if (self.isConnected) {
        [self.peripheral writeValue:data forCharacteristic:self.characteristicT_x type:CBCharacteristicWriteWithResponse];
    } else {
        [SVProgressHUD showErrorWithStatus:@"连接已断开"];
    }
    
    
}

- (NSString *)transferData:(NSData *)data {
    
    uint8_t *readTempBuffer = (uint8_t*)[data bytes];

    //运行状态
    int nChairRunState = (readTempBuffer[7]) & 0xf;
    //手动判断
    int nChairAutoType = (readTempBuffer[16] >> 2) & 0x0f;
    
    if (nChairAutoType && nChairRunState == 3) {
        return self.autoTypeArray[nChairAutoType-1];
    } else {
        return @"none";
    }
    
}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end



2017-04-27 10:57:02 spicyShrimp 阅读数 1488

iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD 、NSOperationQueue…)

昨天一个同事问我一个问题,我在开发中有很多异步操作,回调都需要时间,且时间都不确定,例如一个网络请求,就是这样的形式,异步发起请求,等待回调,等到获取结果之后进行下一步的操作.
我说,没有任何问题啊.本来耗时操作等就是这么写的啊…
然后他说,我现在有一个新的需求,例如网络请求1结束后请求2等到2回来之后再请求3….层层下去…按照顺序来,我说这个需求不算太难.
但是鉴于这个需求很多人都有可能会用到,于是我打算把它给写下来分享给大家

每一次的异步操作大概可以简化成如下:

-(void)doSomeThingForFlag:(NSInteger)flag finish:(void(^)())finshed{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"do:%ld",(long)flag);
        sleep(2+arc4random_uniform(4));
        NSLog(@"finish:%ld",(long)flag);
        if (finshed) {
            finshed();
        }
    });
}

那么正常情况下的写法就是直接按照顺序来

-(void)nomal{
    [self doSomeThingForFlag:1 finish:nil];

    [self doSomeThingForFlag:2 finish:nil];

    [self doSomeThingForFlag:3 finish:nil];

    [self doSomeThingForFlag:4 finish:nil];
}

那么这样有效果么?我们运行看看…
这里写图片描述
很显然,结果没有想象的那么简单,开始请求就已经是无序了…

没有办法,那么使用嵌套?就是最普通的方式看看,

/**
 逻辑嵌套
 */
-(void)useNested{
    __weak typeof(self)weakSelf = self;
    [self doSomeThingForFlag:1 finish:^{

        [weakSelf doSomeThingForFlag:2 finish:^{

            [weakSelf doSomeThingForFlag:3 finish:^{

                [weakSelf doSomeThingForFlag:4 finish:nil];
            }];
        }];
    }];
}

这里写图片描述

OK ,结果完全按照想要的顺序1->2->3->4
但是这样写会不会就觉得很嵌套的太多了呢?有没有办法不使用这种嵌套来完成这个逻辑呢?
开始构思,首先想到的就是锁,是的,应用开发中有很多所能够完成
iOS作为源自C的更高级语言,自然而然也少不了有各种锁的实现.包含C语言的话,
有OSSpinLock、pthead、@synchronized、NSLock……大概7、8种以上吧..
在不考虑各种锁的性能的情况下,那么是不是所有的都特别适用呢?
我一个一个举例尝试,大致的思路就是创建一个锁,然后通过加锁和解锁的操作来实现串行的需求
首先是使用

pthread_mutex 互斥锁

#import <pthread.h>

/**
 pthread_mutex 互斥锁
 */
-(void)usePthred{
    static pthread_mutex_t pLock;
    pthread_mutex_init(&pLock, NULL);

    pthread_mutex_lock(&pLock);
    NSLog(@"1上锁");
    [self doSomeThingForFlag:1 finish:^{
        NSLog(@"1解锁");
        pthread_mutex_unlock(&pLock);
    }];

    pthread_mutex_lock(&pLock);
    NSLog(@"2上锁");
    [self doSomeThingForFlag:2 finish:^{
        NSLog(@"2解锁");
        pthread_mutex_unlock(&pLock);
    }];

    pthread_mutex_lock(&pLock);
    NSLog(@"3上锁");
    [self doSomeThingForFlag:3 finish:^{
        NSLog(@"3解锁");
        pthread_mutex_unlock(&pLock);
    }];

    pthread_mutex_lock(&pLock);
    NSLog(@"4上锁");
    [self doSomeThingForFlag:4 finish:^{
        NSLog(@"4解锁");
        pthread_mutex_unlock(&pLock);
    }];
}

好吧,轻易的实现了
这里写图片描述

那么既然互斥锁可以,我再试试另一种pthead

pthread_mutex(recursive) 递归锁

/**
 pthread_mutex(recursive) 递归锁
 */
-(void)usePthredResursive{
    static pthread_mutex_t pLock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
    pthread_mutex_init(&pLock, &attr);
    pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用

    static void (^RecursiveBlock)(int);
    __weak typeof(self)weakSelf = self;
    RecursiveBlock = ^(int value) {
        pthread_mutex_lock(&pLock);
        if (value>0) {
            [weakSelf doSomeThingForFlag:5-value finish:^{
                RecursiveBlock(value-1);
            }];
        }
        //        if (value == 4) {
        //            [self doSomeThingForFlag:1 finish:^{
        //                RecursiveBlock(3);
        //            }];
        //        }
        //        if (value == 3) {
        //            [self doSomeThingForFlag:2 finish:^{
        //                RecursiveBlock(2);
        //            }];
        //        }
        //        if (value == 2) {
        //            [self doSomeThingForFlag:3 finish:^{
        //                RecursiveBlock(1);
        //            }];
        //        }
        //        if (value == 1) {
        //            [self doSomeThingForFlag:4 finish:^{
        //                RecursiveBlock(0);
        //            }];
        //        }
        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(4);
}

递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
结果也是可以能够实现的
这里写图片描述

表面上看感觉递归锁貌似是没有问题的 但是其实在这里锁并没有起到作用,这里的锁只是锁住了doSomeThingForFlag:finish: 这个方法而已
其实我们把这些全部去掉看看.

-(void)usePthredResursive{
//    static pthread_mutex_t pLock;
//    pthread_mutexattr_t attr;
//    pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
//    pthread_mutex_init(&pLock, &attr);
//    pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用

    static void (^RecursiveBlock)(int);
    __weak typeof(self)weakSelf = self;
    RecursiveBlock = ^(int value) {
//        pthread_mutex_lock(&pLock);
        if (value>0) {
            [weakSelf doSomeThingForFlag:5-value finish:^{
                RecursiveBlock(value-1);
            }];
        }
//        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(4);
}

这里写图片描述

结果也是一样?是的,没有想的那么高深,他只是上面嵌套的另一种写法而已,所以递归锁并没有效果,它只是锁住方法本身,保证一次只有一个执行而已,如果我们把block的调用放到方法的外面一样没有作用

-(void)usePthredResursive{
    static pthread_mutex_t pLock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
    pthread_mutex_init(&pLock, &attr);
    pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用

    static void (^RecursiveBlock)(int);
    __weak typeof(self)weakSelf = self;
    RecursiveBlock = ^(int value) {
        pthread_mutex_lock(&pLock);
        if (value>0) {
            [weakSelf doSomeThingForFlag:5-value finish:nil];
            RecursiveBlock(value-1);
        }
        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(4);
}

这样的话就是没有达到需求
这里写图片描述

所以我打算放弃递归锁的实现,比如NSRecursiveLock,我直接放弃
联想到这样的方式,我打算再次放弃另外一种锁@synchronized 因为它也只能锁住方法的本身,并控制不了回调的结果

那么就么有方法了么?只能使用递归或者嵌套 或者互斥锁么?

C的方法我又想到了自旋锁

OSSpinLock 自旋锁

#import <libkern/OSAtomic.h>

/**
 OSSpinLock 自旋锁
 */
-(void)useOSSpinLock{
    __block OSSpinLock oslock = OS_SPINLOCK_INIT;

    OSSpinLockLock(&oslock);
    NSLog(@"1上锁");
    [self doSomeThingForFlag:1 finish:^{
        NSLog(@"1解锁");
        OSSpinLockUnlock(&oslock);
    }];

    OSSpinLockLock(&oslock);
    NSLog(@"2上锁");
    [self doSomeThingForFlag:2 finish:^{
        NSLog(@"2解锁");
        OSSpinLockUnlock(&oslock);
    }];


    OSSpinLockLock(&oslock);
    NSLog(@"3上锁");
    [self doSomeThingForFlag:3 finish:^{
        NSLog(@"3解锁");
        OSSpinLockUnlock(&oslock);
    }];

    OSSpinLockLock(&oslock);
    NSLog(@"4上锁");
    [self doSomeThingForFlag:4 finish:^{
        NSLog(@"4解锁");
        OSSpinLockUnlock(&oslock);
    }];
}

这里写图片描述

结果终于回到了想要的局面,OSSpinLock没有让我失望.

至此我就想到了,无上面写的方法基本上就是使用各种锁的实现,来达到需求,在结果回调前把线程给锁住,无法继续新的线程,知道该线程的锁解开

那么我们能不能使用多线程的某些方法来实现呢?比如阻塞线程,比如线程的暂停和恢复
首先想到的就是GCD
类似于OSSpinLock, 我们尝试使用GCD的信号量看看能不能够实现

dispatch_semaphore_t

/**
 GCD single
 */
-(void)useGCDSingle{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"1阻塞线程");
    [self doSomeThingForFlag:1 finish:^{
        NSLog(@"1释放线程");
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"2阻塞线程");
    [self doSomeThingForFlag:2 finish:^{
        NSLog(@"2释放线程");
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"3阻塞线程");
    [self doSomeThingForFlag:3 finish:^ {
        NSLog(@"3释放线程");
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"4阻塞线程");
    [self doSomeThingForFlag:4 finish:^{
        NSLog(@"4释放线程");
        dispatch_semaphore_signal(semaphore);
    }];
}

这里写图片描述

或者使用

dispatch_suspend、dispatch_resume

这里需要注意一些东西

  • dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
    那么想使用这个怎么办呢?
    我们可以这样想.因为这几个方法调用来看,大的方式是是串行队列,那么就创建一个串行队列以供暂停和恢复就好了
/**
 GCD队列的暂停和恢复
 */
-(void)useGCDSuspendAndResume{
    dispatch_queue_t myqueue = dispatch_queue_create("com.charles.queue", NULL);

    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:1 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];


        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:2 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];


        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:3 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];


        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:4 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
}

这里写图片描述

无效!我是哪里想错了么?
暂停恢复,想一想,串行队列嘛,当然要串行的添加啦,

/**
 GCD队列的暂停和恢复
 */
-(void)useGCDSuspendAndResume{
    dispatch_queue_t myqueue = dispatch_queue_create("com.charles.queue", NULL);

    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:1 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });

    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:2 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });

    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:3 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });

    dispatch_async(myqueue, ^{
        dispatch_suspend(myqueue);
        [self doSomeThingForFlag:4 finish:^(NSInteger flag) {
            dispatch_resume(myqueue);
        }];
    });
}

这里写图片描述

果然,开始是我想错了….

那么既然GCD可以,我使用NSOperationQueue呢?

NSOperationQueue

/**
 operationQueue的暂停和恢复
 */
-(void)useOperationQueue{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue setMaxConcurrentOperationCount:1];

    __weak typeof(self)weakSelf = self;
    NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:1 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];

    NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:2 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];

    NSBlockOperation * operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:3 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];

    NSBlockOperation * operation4 = [NSBlockOperation blockOperationWithBlock:^{
        [queue setSuspended:YES];
        [weakSelf doSomeThingForFlag:4 finish:^(NSInteger flag) {
            [queue setSuspended:NO];
        }];
    }];

    [operation4 addDependency:operation3];
    [operation3 addDependency:operation2];
    [operation2 addDependency:operation1];

    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
}

完全参考GCD的思路,创建每一个操作,添加依赖和最大并发数保证大的串行,同是在没操作其中一个暂停队列,完成后恢复运行….
这里写图片描述

OK说了这么多,其实就是通过各种方式来实现我最上面提到的需求而已.
这样的操作真的有用么?
很碰巧,我最近在做蓝牙开发,有这样的类似需求,蓝牙发送指令并接收到设备端返回数据的情况就是一次类似的网络请求,
我碰到的需求是按顺序的设置指令到蓝牙设备端,如果是多个UUID或者characteristic的话,之间不冲突,没有影响,但是可惜的是我要操作的是一个characteristic,我只能这么做,因为如果同一时间发送指令不是按照上面的逻辑的话,就会造成丢包.我可能发送了某一个指令,但是蓝牙没有收到或者未处理就来了新的指令导致我无法完整的操作它.我必须保证1->2->3->4的逻辑顺序,

我很高兴我正好在研究这个,所以我能够即时的给到我同事我的思路,,并且今天把它分享给你们
附上demo的地址
https://github.com/spicyShrimp/specialSync.git

还有我写的系列文章,刚刚开始写,希望多关注一下.
系列:iOS开发-前言+大纲
http://blog.csdn.net/spicyShrimp/article/details/62218521

2020-04-24 11:00:50 george012 阅读数 55

说明

  • WARNING 凡所涉及线材及设备,请自行准备此处仅为当前条件随便购买
  • 使用 串口芯片组:PL-2303

1、准备串口线及对应串口传唤器驱动

1.1线材准备

1.2、对应驱动准备

2、下载可视化连接工具"CoolTerm_Mac"

3、验证是否驱动安装完成

  • 设置连接选项

  • 设置连接选项

  • 在另一台准备好的机器上输入测试字符

  • 在另一台准备好的机器上输入测试字符


PS:


ios开发知识汇总

阅读数 1212