Compass.h
1 | #import <UIKit/UIKit.h> |
Compass.m
1 |
|
1 | #import <UIKit/UIKit.h> |
1 |
|
我自己起的名字,所谓指南针模式,就是MKMapView的MKUserTrackingModeFollowWithHeading模式,只不过MKMapView只能够让用户点自身进入Tracking模式
而且在打开Tracking模式的时候,Apple会十分体贴的帮你Zoom到用户点
导致有些需求无法规避这个动画。
1 | [mapView setUserTrackingMode:MKUserTrackingModeFollowWithHeading animated:animate]; |
如果想要模仿FollowWithHeading有两个方法,并且各有优劣,但是无论使用何种方法,都是通过LocationManage获取到设备的Heading值,然后根据Heading值对MapView进行设置
方法 | 实现 | 优点 | 缺点 |
---|---|---|---|
Transform | 根据Heading值改变MapView的Transform | 简单,Overlay和Annotation始终保持TransformIdentity | 需要大于ScreenView的MapView,标志物不会自动调整方向 |
Camera | 根据Heading值改变MapView.Camera镜头状态 | Overlay和Annotation以及字体会自动调整方向 | Annotaiton如果需要保持Heading,需要更复杂的计算 |
通过设置地图的layer来达到Tracking旋转效果
1 | - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { |
需要配合函数 transformInTransformHeadingModeByDegrees 来计算旋转的Transform,由指南针追踪工具提供
通过设置地图的Camera来达到Tracking旋转效果
1 | - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { |
需要配合函数 cameraHeadingInCameraHeadingModeByDegrees 来计算旋转的Heading,由指南针追踪工具提供
1 | + (CLLocationDegrees)degreesRangeProcess:(CLLocationDegrees)degrees { |
在不同的指南针模式下,我们始终想让某一个标志物锁定朝向,不会随着 设备的旋转导致的地图旋转而产生偏向 可以使用由指南针追踪工具提供的函数来设置Transform
调用苹果自带地图导航的相关类叫 MapItem,导航的英文不叫Navigation而是叫Direction,相关官方文档为Asking the Maps App to Display Directions
MapItem一共有两个方法
方法名 | 作用 |
---|---|
+ openMapsWithItems:launchOptions: | 跳转到自带地图并且打开一组Item |
- openInMapsWithLaunchOptions: | 跳转到自带地图,打开这个Item |
可以看到两个方法都提到了Options,具体的Options包括以下
| Option | 取值 | 可选项 |
| :————- | :————- |
| MKLaunchOptionsDirectionsModeKey | 导航方式,有四个选项,比如步行还是开车官方文档 | MKLaunchOptionsDirectionsModeDriving,MKLaunchOptionsDirectionsModeWalking,MKLaunchOptionsDirectionsModeTransit,MKLaunchOptionsDirectionsModeDefault |
| MKLaunchOptionsMapTypeKey | 地图模式,采用NSNumber | 对应MKMapType的枚举 |
| MKLaunchOptionsMapCenterKey | NSValue | 把Coordinate编码成NSValue |
| MKLaunchOptionsMapSpanKey | NSValue | MKCoordinateSpan编码成NSValue |
| MKLaunchOptionsShowsTrafficKey | 交通状况,BOOL值 | 0 1 |
| MKLaunchOptionsCameraKey | 一个Object | MKMapCamera的实例 |
是MapItem的一个对象,包含了基本信息例如电话、URL等等,用于实例化MapItem,本身可以通过 initWithCoordinate: 实例化
实例化 | 备注 |
---|---|
+ mapItemForCurrentLocation | 返回当前设备的MapItem,是一个单例 |
- initWithPlacemark: | 通过PlaceMark进行实例化 |
从当前位置导航到某个目的地
1 | CLLocationCoordinate2D drone = _mapKit.drone.coordinate; |
不推荐使用该方法,但是也是我想出来的奇技淫巧,所以就记下来了
在[文章三][link01]中我们可以通过View Tree去找到ContentView,所以说也可以通过该View找到MKMapKit默认存在的手势接收器 _MKUserInteractionGestureRecognizer,如下代码
1 | - (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views { |
通过对其State添加KVO即可接收到地图手势的触发状态,并且进行逻辑判断
1 | //Normal Gesture |
通过添加新的GestureRecognizer来监听手势,通过设置代理 gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 来同时触发
1 | - (void)monitorGesture { |
在有些需求中,要求一个Annotation上添加一个View并且要处于另外一个Annotation的下面,因为Annotation本身只有一个View接收Image,想要实现这个效果旧代码采取了
感觉不是优秀的处理方法,所以通过IDE观察View Tree,可以发现,所有的AnnotationView都加在一个共同的ContentView上,整个MapView的结构关系为
1 |
|
所以我们可以简单的通过superView来获得 AnnotationContainerView 如果在这上边添加View,就可以保证其处于所有AnnotationView底层
1 | //保存一个指向ContainerView的弱指针 |
如果仅仅是完成第一段所说的需求,也可以通过监控代理,把每次特定的View使用函数置底,来做到让其显示在所有View底部
1 | UIView *view = [_mapView viewForAnnotation:_annotation]; |
通过 mapView:viewForAnnotation: 获得Annotation
1 | //指向BlueDot的Weak指针 |
通过 mapView: didAddAnnotationViews: 直接获得AnnotationView
1 | //指向BlueDotView的Weak指针 |
位置比较可以直接比较Frame,但是在不同Scale下,并不是太稳定,所以选择通过CGPoint的位置反推Coordinate,然后通过CLLocationDistance来确定两者是否重合
1 | - (void)autoCorrectLocationOffset { |
由于自动纠偏探测依赖于不稳定的ViewTree,所以很可能出现在卡顿的时候自动纠偏方法被连续调用两次(多出现于旧款iPad),所以需要制定一个可靠的纠偏策略,目前采用的策略:
BlueDot的AnnotationView虽然不一定会出现在MapView上,但是不管MapView.showUser = ?多少,MapView的用户Annotation对象 MKUserLocation 永远会存在于地图上
如果想使用自己的Image来作为用户的AnnotationView,可以不再一开始就隐藏MapView.showUser,使用其作为参考点后再设置隐藏,因为BlueDotView实际被Add到MapView上时,并未进入Screen范围
用户的AnnotationView与BlueDotView时,实际是存在自己的AnnotationView的,可以采用赋值Image为nil的形式让其以透明样式存在
由于你懂的原因,国内有些GPS坐标需要转换,国内的GPS体系名称叫 GCJ02 而国际通用的叫 WGS84 下面是魔法代码
1 |
|
多线程的基本知识我是参考了以下文章
本文在以上文章的基础上,说说我自己的理解(思维回路可能比较奇怪),对一些没有提到的细节进行补充
多线程有几种实现方式,这是好多面试题里问的,一般来说网上的答案是这样三个或者文章Java实现多线程的四个方式这样的
1 | //一般是这三个 |
这些方式有时候是三个,有时候是四个,他们有什么区别呢?先说结论: 多线程只有一个实现方式,就是实现Runnable接口, 并且使用Thread进行开启
正确的理解方式 Thread类本身就是一个实现了Runnable接口的类,继承它相当于实现了Runnable 好处是在于不用使用Thread去包装Runnable了
1 | public class MyThread extends Thread { |
需要使用Thread进行包装才能开启线程
1 | class RunnableDemo implements Runnable { |
正确的理解方式是 FutureTask本身实现了RunnableFuture,RunnableFuture继承于Runnable和Future,FutureTask可以通过Callable进行实例化,实例化的对象相当于实现了Runnable 得到了FutureTask还需要通过Thread包装才能开启线程
1 | //1. 新建一个实现了Callable的类 |
先来看单词
单词 | 意思 | 意义 |
---|---|---|
Thread | 线程 | 所谓多线程,就是指的多个Thread |
Runnable | 可以跑,即可以执行 | 每个Thread都需要一个可以执行的东西 |
Callable | 可以调用 | 可以调用的意思,是可以回调,相当于JS里的callback,iOS里的block |
Future | 未来 | 异步任务,配合回调函数,相当于JS里的Promise |
从上表分析
他们四个都属于Concurrent的Package,也就是说都属于并发(Concurrent)包
每个Thread中有一个Runnable成员target
当调用了Thread的start方法之后,Thread会进入可执行状态(Runnable),然后在被CPU执行时,Thread调用自己的run方法,方法内执行Target的run方法
Future英文直译是未来的意思,也就是不是当下发生的事情,而Callable代表可以调用
综上所属,所谓的第三种可以通过Callable实现多线程,就是扯淡。。。 只不过FutureTask刚好就是一个实现了Runnable的Object,所以可以用来实例化Thread,而FutureTask的实例化恰好需要Callable而已
ExecutorService是一个Interface,继承于Executor,其可以通过静态方法获取到实例,好像俗称 线程池 呃 我不确定
1 | ExecutorService executorService = Executors.newCachedThreadPool(); |
其本质是一个通过Future、Callable、Runnable来规划并发任务的容器,Future负责任务管理,Runnable负责任务逻辑代码,Callable负责返回值
Executor这个Interface只有一个方法,就是execute(Runnable command),执行某个可以Runnable的命令,而ExecutorService则在继承其基础上,提供了3个主要方法
1 | //提交一个Callable的对象,并且通过Future拿到返回值 |
根据上文陈述, Runnable接口只是负责线程Thread中的Target执行什么 所以不具有返回值,如果想要得到ExecutorService执行的返回值,必须使用Callable接口,并且使用Future.get()获得,所以以上3个submit分别做了以下操作
方法 | 取得返回值原理 |
---|---|
submit(Callable task) | 提交的就是一个Callable对象,所以可以得到返回值 |
submit(Runnable task, T result) | 提交的Runnable对象不具有返回值,在ExecutorService,内部使用一个FutureTask对象,用T result来实例化FutureTask的Callable对象,从而得到期望的T范型返回值 |
submit(Runnable task) | Runnable没有返回值,那就不要返回值了,执行完直接返回null |
可以看出,之所以submit(Runnable task, T result)可以获得返回值,是因为在ExecutorService内部使用了FutureTask对象,这个对象内部有callable对象来确定返回值。这也说明来Java实现多线程的四个方式文章中的所谓第四个方式是没看源码
在文章林炳文Evankaka的多线程解析中讲到了一个经典的例题
使用到了Object的wait()和notify(),这里进行补充解析来阐明什么叫Object锁,基本原理请参考林炳文的文章
1 | /** |
在程序中有一行注释
1 | Thread.sleep(100); //确保按顺序A、B、C执行 |
十分重要,如果不使用Thread.sleep的话,会输出结果ACB循环而不是ABC,为什么是这样的呢?
1 | ACBACBACBACBACBACBACBACBACBACB |
我们在分析图标中用不同的符号代表状态
不加sleep(100)的情况
对象 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
A | c a | c !a | ?c !a | c ~a | c ~a | c ~a | c a | c a |
B | ~a | a ~b | a ~b | a ~b | a b | a !b | ?a !b | ~a |
C | b ~c | b ~c | b c | b !c | ?b !c | b ~c | b ~c | b ~c |
输出 | null | A | A | AC | AC | ACB | ACB | 循环 |
加了sleep(100)的情况
对象 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
A | c a | c !a | ?c !a | ?c !a | ?c !a | ?c !a | c !a | c a |
B | sleep | sleep | a b | a !b | ?a !b | ?a !b | ?a !b | ?a |
C | sleep | sleep | sleep | sleep | b ~c | b !c | ?b !c | ?b |
输出 | null | A | A | AB | AB | ABC | ACB | 循环 |
在第8步进入 B对象Thread执行了a.wait,而C对象线程执行了b.wait 之后等同于第一步的两者都在sleep,经过以上分析,我们可以得知sleep就是通过这种方式保证了ABC的顺序
如果将sleep改为1000毫秒,可以清楚的看到,第一组ABC打印使用了3秒以便进入步骤8,剩余的则是瞬时完成的
除了使用Object锁自己进行线程控制之外,Concurrent包内部也提供了一些控制器,比如文章CountDownLatch与CyclicBarrier中提到的
这个文章很早就写好了,一直没有发表,当时为了Java而写的,后来在iOS里也发现了相似的机制,同样具有 synchronized 关键词和上文的两个控制器,但是iOS为了追求效率,大部分称需要喜欢自己手动控制锁。。。所以就很容易死锁。。。后台不一样,后台保证稳定性是第一的,所以更注重synchronized的理解
通过文章MacOS 10.12 终端命令行下使用Shadowsocks我们得知Shadowsocks-NG可以提供HTTP和HTTPS代理了,那么我们可以单独为某些命令配置代理
参考文章npm的配置文件npmrc,我们可以在Mac下新建一个配置文件
1 | vim ~/.npmrc |
在文件内写入
1 | proxy = http://127.0.0.1:1087 |
然后保存退出即可,如果想使用命令直接配置,参考文章命令配置npmrc,其中也对配置文件进行更详细的解释
首先Git分为3种协议模式
协议 | 是否可以使用HTTP代理 |
---|---|
git:// | 不能直接使用 |
http:// | 可以 |
https:// | 可以 |
1 | //配置 |
git的配置文件位于
1 | vim ~/.gitconfig |
如果我们进行了代理配置,可以看到
1 | [core] |
方校长今年还说想为祖国再尽一份余力,真是可喜可贺啊
有一个新的Shadowsocks的客户端叫 Shadowsocks-NG 解决了一个长久以来的痛点,Shadowsocks没有HTTP代理,导致我们需要使用polipo等软件进行协议转换
现在其在1087端口提供了http代理服务,我们可以通过在命令行直接设置代理,来使用SS服务了
1 | //开启代理 |
1 | vim ~/.bash_profile |
在文件内加入
1 | # Custom Add not System |
就可以在命令行内通过 proxy-on 和 proxy-off 来开启关闭代理
以上快捷启动配置只能在User的命令行里进行使用,如果想对sudo操作挂proxy,需要先使用命令
1 | sudo -s |
进入bash 3.2# 然后手动设置
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true