重启尘封的博客
时隔2年半,俺又回来更新博客了,以后每周会在博客上发布上一周的周报~
iOS网络请求 NSURLConnection
iOS网络请求 NSURLConnection
URLConnection的几种用法
123456/*触发*/- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self async];}
1234567891011121314151617181920212223/** Get请求-同步 */- (void) sync{ NSURL* url = [NSURL URLWithString:@"https://tianqiapi.com/api?version=v1&appid=53827541&appsecret=sIY6i0fQ"]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL: url]; NSHTTPURLResponse * response = **nil**; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse: &response error: **nil**]; NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]); NSLog(@"%zd",response.statusCode);}
12345678910111213141516171819202122/** Get请求-同步 */- (void)async{ NSURL* url = [NSURL URLWithString:@"https://tianqiapi.com/api?version=v1&appid=53827541&appsecret=sIY6i0fQ"]; NSURLRequest *request = [[NSURLRequest alloc]initWithURL: url]; [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * *connectionError) { NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; result = [self stringByReplaceUnicode:result]; NSLog(@"%@",result); NSHTTPURLResponse* res = (NSHTTPURLResponse*)response; NSLog(@"%zd&qu ...
iOS学习计划(实验)
iOS学习计划(实验)
一个不成熟的iOS初学者学习规划
了解全局
确定范围掌握基础知识 完成一个天气页面
UI布局 LinearLayout textview icon imageview menuitem button
网络请求
存储
Objective-C
定义目标完成天气页面
了解需要的技术点
寻找资源信息来源
图书 ios 编程实战 ios高级编程 effective-OC
博客文章 sf非常详细
在线视频 Lynda ios14
专家
源代码
示例项目
在线文档 Document Archieve raywenderlick
创建学习计划学习oc
blocks
学习UI布局 UIKit 自动布局
学习操纵页面路由
学习网络请求(ps:找一个天气api)
学习sqlite在ios上的使用
完成天气页面
筛选资源以下步骤重复执行
开始学习 浅尝搁置获得足够多的与所学主题相关的东西(最小知识量:参考材料,摘要,简介)
动手操作亲自操作和亲身体验,通过探索和实践进行学习。
在操作的过程中产生各种问题?这些问题引导着你走向真正重要的方向。
回过头寻找问题的答案,learn more
采用上一步的学到的知识,不用担心结果,先创建一个小项目来测试这一步的效果。
把那些暂时还没有答案的问题记录下来,你在下一步中会有机会找出这些问题的答案。
全面掌握 学以致用
找回失去的好奇心
在上一步中积累的问题,可以通过阅读相关主题的资料来回答这些问题。你可以通览已有的资料,仔细查找通过操作发现的问题有关的内容。
阅读文字
观看视频
与他人交流
请记住,你依然没有必要把收集到的资料全部仔细看一遍。你只需要阅读或观看与当前所学相关的部分。
最后,请不要忘记你在第三步定义的成功标准。把自己正在学习的内容与最终目标关联起来。你掌握的每个模块,都应该以某种方式推动你向着终极目标前进。。
乐为人师,融会贯通
你告诉我的,我都忘了。你教会我的,我都记得。让我乐在其中,我就一定能学会。
—本杰明·富兰克林
走出舒适区,将自己学到的知识教给别人。要想确定你确实掌握了某些知识,这是唯一的办法。同时,也是查缺补漏的好办法;
在这一过程中,你要切实剖析并理解自己所学的知识,将其内化到自己的思想;
同时,你也要能够同他人能够理解的方式精心组织这些信息,这也有助于提升你的理解能力。
写博客
制作视频
与朋友/爱人探讨
发表演讲
在线论坛回答问题
A Good Start
2020年已经结束半个月, 现在来补个总结。
深圳之行 寒假还没开始, 疫情在武汉蔓延开来,我选择去深圳和爷爷一起度过这个春节。在这之前我参与了一个项目,工期很赶,所以只准备去5天就回学校。结果疫情突然变得严重, 学校宣布禁止返校,项目被迫只能停工。我也就直接在深圳住了下来,渐渐融入了这里的生活。一天,站在莲花山顶眺望南山,也许以后就会去那里工作生活,我开玩笑似的和家人说,(这也是这个寒假再次来到深圳的原因之一)。
第一次比赛 在6月份接近尾声的时候,比赛网申开始了,我们积极准备材料进行答辩,虽然过程很坎坷, 最终也拿到了不错的成绩,大家都很满意自己的结果。这是第一次参加团队比赛,从开始的一无所有到后来队伍逐渐扩大,项目慢慢有了起色,大家磨合的越来越好,每个人都从中得到了自己想要的东西。可惜之后学校调整了保研政策, 我也受到影响,开始从新考虑就业这条道路。
实习之路 在4、5月份的时候,第一次投递实习,因为自己准备的太仓促,实力又很弱,很遗憾错过了实习的机会。在11月份的时候,开始重新准备寒假的实习,背了不少八股文,也刷了300题leetcode。经过几次面试之后,感觉自己掌握了面试的节奏,以后也要经常去面试,保证自己的状态。最终很幸运拿了鹅厂的offer又一次去了深圳。
杂谈 刚进大学的时候, 被分到和大三的学长一个寝室, 我对他们的生活状态, 学习氛围向往不已:每个人都忙于自己的学习工作;晚上会寝室一起开黑打lol;熄灯之后一起吹吹牛;当时猜想自己的大学生活也会这么的美好。现在我也到了这个节点,再一次体会当时学长的生活,确是不一样的感觉~
未来 To be continued!
简简单单的Retrofit源码分析
Retrofit做了什么
Retrofit 对 OkHttp进行了一层封装, 对网络层进行了解耦, 主要是通过注解,泛型,动态代理, 还有大量的设计模式来实现的.
Retrofit使用流程分析
构建一个Retrofit对象
123456789val retrofit = Retrofit.Builder()//Retrofit2的baseUrl 必须以 /(斜杆)结束,抛出一个IllegalArgumentException.baseUrl("https://www.wanandroid.com/").addConverterFactory(GsonConverterFactory.create()).build()
通过建造者模式构建对象可以灵活添加、修改功能如自定义OkHttpClient, 自定义转换器, 自定义适配器
通过Retrofit对象获取到api接口的代理对象
12//2. 获取WanAndroidApi接口的代理对象val wanAndroidApi = retrofit.create(WanAndroidApi::class.java)
使用动态代理模式返回一个代理对象, 这里的Api需要提前定义好.
123456789101112131415161718return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<?>[] {service}, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable { if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args);// Object的方法不属于接口, 直接调用 } args = args != null ? args : emptyArgs; return platform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args);// 在这里去解析注解信息, 然后调用 } });
通过代 ...
简简单单的OkHttp源码分析
OkHttp介绍
OkHttp是目前Android使用最频繁的网络请求框架(目前的Volley, Retrofit底层都是使用的OkHttp), 框架由Square公司开源, 在Android4.4之后HttpUrlConnection也默认使用OkHttp的实现在OkHttp4之后改为kotlin实现
优势所在
支持Socket连接池复用TCP连接
支持Http2.0协议
支持Gzip压缩格式
支持缓存控制
请求失败时的自动重试和重定向机制
OkHttp的分发器对于同步请求, 直接加入runningAsyncCalls队列当中
而同步请求对于请求次数不超过64次, 同主机不超过的5次的请求直接加入到runningAsyncCalls队列当中, 如果不符合条件则加入到readAsyncCalls中
默认的五个拦截器的职责重试与重定向拦截器在发生一次请求超时的情况下, 去寻找可用的ip重新请求服务器.某次请求返回了30x时, 并且在响应报文头的Location字段中有重定向的url, 则根据这个url重新构建一个Request
重试
重定向
桥接拦截器为request补充请求头等信息提供cookie, 如果用户需要cookie会在这里进行处理
缓存拦截器请求前查询是否有可用缓存, 如果缓存有效, 则直接返回缓存
连接拦截器与服务器完成TCP连接
请求服务拦截器与服务器通信;封装请求数据与解析响应对于已经取消的请求结果不再返回客户端
如果在通过重试与重定向拦截器之后取消了任务, 取消是否无效仍然会去请求, 但是用户不会接收到响应
简简单单的Handler源码分析
Handler机制如何保证线程之间的通信
Handler采用了内存共享的方案来实现线程间的进程通信, 为什么这么说呢? 接下来我会简单的分析一下.
在Handler机制中有下面几个类起到关键作用
MessageMessage是Handler机制中最基础的类, 也是需要”new”最多的类, 由于在Java中大量创建对象的操作会引起内存的碎片化, 可能会大对象(例如Bitmap)的空间申请造成困难从而诱发JVM进行频繁的GC以致于影响app性能, 当然还存在内存抖动等问题.对此, Google的Android工程师是通过一个对象池复用机制来减少以上问题. 所以当我们需要一个Message对象的时候就可以通过Message.obtain方法. 当我们”使用完”一个Message的时候, 也就是looper准备去获取下一个Message之前会将Message回收掉.
1msg.recycleUnchecked();
MessageQueueMessageQueue这个类是实现整个Handler机制的关键, 也是内存共享的核心, 在生产消费者模型中属于仓库的位置. 而且, 在它的next方法和enqueueMessage方法中调用了native方法来让线程让出时间片和尝试唤醒线程保证了线程的正常运转, 而在native层c++代码会去调用linux系统的相关服务,这里就不再深入了.
HandlerHandler是一个Message的起始也是终止的地方. 如果把Message的传递简单看成生产消费者模型, 那么它既是生产者也是消费者.关于Handler对Message消息的处理进行了三层分级
检查 msg.callback
检查 mCallbackmCallback.handleMessage(msg);
handleMessage这种类似责任链模式的处理方式极大的提高了程序的灵活性, 在View的事件分发过程中也有体现.
LooperLooper它是一个比较特殊的类, 这里说的是它的设计思想很巧妙, 也可以说它是ThreaLocal类在Android中的最佳实践.因为它的一系列设计保证了Looper—>MessageQue的线程隔离和线程内单例. 这里不得不说一下ThreadLocal这个类, 由于Thread类中内置了ThreadLocalMap对象, 让它天然就具有实现线程的隔离的可能.它利用了ThreadLocalMap的Key(ThreadLocal变量本身, 这里采用了hash函数去效验)是唯一的这一特性, 在myLooper方法中使用了一种伪单例的方式(直接抛出错误)禁止开发者去重复创建它的实例.这样以来它在私有构造方法中实例化的MessageQueue也得到了唯一性的保证.prepareLoop方法loop方法
从sendMessage/postMessage出发不管是从sendMessage从还是从post方法最终都会调用到Handler.enqueueMessage方法, 接着会调用Handler对应MessageQueue.enqueueMessage方法(Looper和MessageQueue都与Handler存在1对>=1的关系). 而且这个入队方法考虑了线程安全问题(毕竟Handler本身就是用与线程通信嘛)对大部分的代码都加了sycronized锁.
在队列的另外一头, looper所在的线程需要要去开启loop去轮询消息队列.所以首先这里先从Looper.loop方法入手, loop ...
Hello World
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
Quick StartCreate a new post1$ hexo new "My New Post"
More info: Writing
Run server1$ hexo server
More info: Server
Generate static files1$ hexo generate
More info: Generating
Deploy to remote sites1$ hexo deploy
More info: Deployment
Java内存模型
as-if-serial属性
菩提本无树, 何处惹尘埃。
即时编译器(和处理器)需要保证程序能够遵守as-if-serial属性。通俗地说, 就是在单线程情况下, 要给程序一个顺序执行的假象。即经过重排序的执行结果要与顺序执行的结果一致。
另外, 若两个操作之间存在数据依赖, 那么及时编译器(和处理器)不能调整它们的顺序, 否则将会造成程序语义的改变。
happens-before 关系
问渠那得清如许? 唯有源头活水来。
描述两个操作的内存可见性的。如果操作X happens-before 操作Y, 那么X的结果对于Y可见(Y观测X的结果)
Java内存模型定义的线程间happens-before关系
1。解锁操作 happens-before 对该所锁的加锁操作(时序)2。volatile字段的写操作 happens-before 对该volatile字段的读操作(时序)3。线程的启动(start) happens-before 线程的第一个操作4。线程的最后一个操作 happens-before 线程的终止事件(即其他线程通过Thread.isAlive() 或 Thread.join()判断该线程是否中止)5。线程对其他线程的中断操作 happens-before 被中断线程收到的中断事件(即被中断线程的InterruptedException异常, 或者第三个线程针对被中断线程的Thread.interrupted 或者 Thread.isinterrupted调用)6。构造器中的最后一个操作 happens-before 析构器的第一个操作
另外happens-before关系还具备传递性.
Java内存模型的底层实现
Java内存模型是通过内存屏障(memory barrier)来禁止重排序的.
对于即使编译器来说, 它会针对每个happens-before 关系, 向正在编译的目标方法中插入相应的读读,读写,写读,写写内存屏障。
这些内存屏障会限制即时编译器的重排序操作。以volatile字段访问为例, 所插入的内存屏障将不允许volatile字段写操作之前的内存访问被重排序至其后; 也将不允许bolatile字段读操作之后的内存访问被重排序至之前。volatile字段的内存屏障是什么类型?
即时编译器会根据具体的底层体系架构, 将内存屏障替换成具体的CPU指令。以我接触最多的X86_64架构来说, 读读,读写以及写写内存屏障是空操作(no-op), 只有写读内存屏障会被替换成具体指令
这样理解重排序Java代码本应该按源码顺序执行, 但是编译器对部分代码会进行优化, 提高执行效率(cpu 或 内存)
然而, 禁止重排序是处于某种目的(业务逻辑/happens-before)开发人员对编译器的代码重排序进行一定的限制。
对于即时编译器来说, 它会针对happens-before关系, 向正在编译的目标方法中插入相应的内存屏障(读读,读写,写读,写写)
volatilevolatile字段写操作之后的写读内存屏障需要用具体指令来替代.(HotSpot 所选取的具体指令是 lock add DWORD PTR[rsp],0x0, 而非mfence[3].)在具体指令的效果, 可以简单理解为强制刷新处理器的写缓存。写缓存是处理器用来加速内存存储效率的一项技术。在碰到内存写操作时, 处理器并不会等待该指令结束, 而是直接开始下一指令, 并且依赖于写缓存将更改的数据同步至主内存之中。强制刷 ...
Java反射API的用法
获取Class对象
Class.forName获取Class对象
getClass()方法
直接使用类名+”.class”. 对于基本数据类型, 它们的包装类型拥有一个名为”TYPE”的final静态字段, 指向该基本数据类型对应的Class对象
integer.TYPE 指向 int.class对于数组类型 可以使用类名+ “[].class”来访问, 如int[].class
拿到Class对象后
使用newInstance()生成一个实例(需要一个无参构造器)
使用isInstance(Object)来判断一个对象是否是该类的实例, 语法上等同于instance of 关键字(JIT 优化时会有差别?)
使用Array.newInstance(Class,int)来构造该类型的数组
使用getFIelds()/getConstructors()/getMethods() 来访问该类型的成员. 带Declared的方法不会返回父类成员,但是会返回私有成员
拿到类成员后
使用Constructor/Field/Method.setAccessible(true)来绕开Java语言的访问限制(如private).
使用Constructor.newInstance(Object[])来生成该类的实例
使用Field.get/set(Objcet)访问字段
使用Method.invoke(Object, Object[])来调用方法
反射调用栈
默认情况下反射调用为委派实现, 委派给本地实现来进行方法实现来进行方法调用. 在达到阈值超过15次(第15次)委派实现会将委派对象DelegatingMethodAccessorImpl切换至动态实现(自动生成字节码(耗时)), 它将直接使用invoke指令来调用目标方法
反射性能开销原因
不定长参数导致的Object数组
基本数据类型的自己装箱拆箱
方法内联 (将本方法内调用的方法与自己一起编译)