reachability https://www.e-learn.cn/tag/reachability zh-hans ip address change notifications https://www.e-learn.cn/topic/4064993 <span>ip address change notifications</span> <span><span lang="" about="/user/111" typeof="schema:Person" property="schema:name" datatype="">落爺英雄遲暮</span></span> <span>2021-02-07 08:50:59</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><h3>问题</h3><br /><p>I'm using the Reachability class to detect wifi availability. </p> <p>There are some situations when an ipad will be connected to one wifi network,and a user will connect to another available wifi network.</p> <p>During these network transitions reachable-&gt;unreachable-&gt;reachable notifications are not generated.</p> <p>This connection change where the ip address of the ipad changes is what Im trying to listen for. </p> <p>Do notifications for local wifi connection changes exist or will I have to just poll my ip periodically?</p> <br /><h3>回答1:</h3><br /><p>I would personally just poll the IP however frequently you find appropriate (once a second should be fine), using the code found here, and just store the previous occurrences result, see if it changes. </p> <p>What I would really recommend is setting up a simple delegate class that does this for you and will send a custom event to whatever class may need those updates. It should be pretty straight forward, especially considering it seems like you have some experience. </p> <p><strong>UPDATE</strong></p> <p>I have posted some code below that will create a delegate that will call back to whatever class once it detects any change in IP. Note there may be some errors/typos as I am not in front of a computer with XCode currently, but you should get the general idea.</p> <p><strong>IPChangeNotifier.h</strong></p> <pre><code>#import &lt;UIKit/UIKit.h&gt; @protocol IPChangeNotifierDelegate; @interface IPChangeNotifier : NSObject { NSString *prevIP; NSTimer *changeTimer; id changeDelegate; } -(id) initWithTimer:(float)time andDelegate:(id)del; -(NSString*)getIPAddress; -(void) checkForChange; @end @protocol IPChangeNotifierDelegate &lt;NSObject&gt; @optional -(void) IPChangeDetected:(NSString*)newIP previousIP:(NSString*)oldIP; @end </code></pre> <p><strong>IPChangeNotifier.m</strong></p> <pre><code>#import IPChangeNotifier.h #import &lt;ifaddrs.h&gt; #import &lt;arpa/inet.h&gt; @implementation IPChangeNotifier -(id) initWithTimer:(float)time andDelegate:(id)del { changeTimer = [NSTimer scheduleTimerWithTimeInterval:time target:self selector:@selector(checkForChange) userInfo:nil repeats:YES]; prevIP = @""; changeDelegate = del; } -(void) checkForChange { NSString *currentIP = [self getIPAddress]; if (![currentIP isEqualToString:prevIP]) { if ([changeDelegate respondsToSelector:@selector(IPChangeDetected:)]){ [changeDelegate IPChangeDetected:currentIP previousIP:prevIP]; } prevIP = currentIP; } } - (NSString *)getIPAddress { struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; NSString *wifiAddress = nil; NSString *cellAddress = nil; // retrieve the current interfaces - returns 0 on success if(!getifaddrs(&amp;interfaces)) { // Loop through linked list of interfaces temp_addr = interfaces; while(temp_addr != NULL) { sa_family_t sa_type = temp_addr-&gt;ifa_addr-&gt;sa_family; if(sa_type == AF_INET || sa_type == AF_INET6) { NSString *name = [NSString stringWithUTF8String:temp_addr-&gt;ifa_name]; NSString *addr = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr-&gt;ifa_addr)-&gt;sin_addr)]; // pdp_ip0 NSLog(@"NAME: \"%@\" addr: %@", name, addr); // see for yourself if([name isEqualToString:@"en0"]) { // Interface is the wifi connection on the iPhone wifiAddress = addr; } else if([name isEqualToString:@"pdp_ip0"]) { // Interface is the cell connection on the iPhone cellAddress = addr; } } temp_addr = temp_addr-&gt;ifa_next; } // Free memory freeifaddrs(interfaces); } NSString *addr = wifiAddress ? wifiAddress : cellAddress; return addr ? addr : @"0.0.0.0"; } @end </code></pre> <p>You can then simply make whatever class you want a delegate by adding <code>&lt;IPChangeNotifierDelegate&gt;</code> to your interface file and then initialize the notifier by doing something simple like below.</p> <pre><code>IPChangeNotifier *ipChecker = [[IPChangeNotifier alloc] initWithTimer:1.0 andDelegate:self] </code></pre> <p>Also make sure you have the following method included to make sure you can get the change events and do whatever you need. </p> <pre><code>-(void) IPChangeDetected:(NSString*)newIP previousIP:(NSString*)oldIP { // Do what you need } </code></pre> <br /><br /><p>来源:<code>https://stackoverflow.com/questions/18378441/ip-address-change-notifications</code></p></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/ios6" hreflang="zh-hans">ios6</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> </div> </div> Sun, 07 Feb 2021 00:50:59 +0000 落爺英雄遲暮 4064993 at https://www.e-learn.cn ip address change notifications https://www.e-learn.cn/topic/4064989 <span>ip address change notifications</span> <span><span lang="" about="/user/123" typeof="schema:Person" property="schema:name" datatype="">我与影子孤独终老i</span></span> <span>2021-02-07 08:50:40</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><h3>问题</h3><br /><p>I'm using the Reachability class to detect wifi availability. </p> <p>There are some situations when an ipad will be connected to one wifi network,and a user will connect to another available wifi network.</p> <p>During these network transitions reachable-&gt;unreachable-&gt;reachable notifications are not generated.</p> <p>This connection change where the ip address of the ipad changes is what Im trying to listen for. </p> <p>Do notifications for local wifi connection changes exist or will I have to just poll my ip periodically?</p> <br /><h3>回答1:</h3><br /><p>I would personally just poll the IP however frequently you find appropriate (once a second should be fine), using the code found here, and just store the previous occurrences result, see if it changes. </p> <p>What I would really recommend is setting up a simple delegate class that does this for you and will send a custom event to whatever class may need those updates. It should be pretty straight forward, especially considering it seems like you have some experience. </p> <p><strong>UPDATE</strong></p> <p>I have posted some code below that will create a delegate that will call back to whatever class once it detects any change in IP. Note there may be some errors/typos as I am not in front of a computer with XCode currently, but you should get the general idea.</p> <p><strong>IPChangeNotifier.h</strong></p> <pre><code>#import &lt;UIKit/UIKit.h&gt; @protocol IPChangeNotifierDelegate; @interface IPChangeNotifier : NSObject { NSString *prevIP; NSTimer *changeTimer; id changeDelegate; } -(id) initWithTimer:(float)time andDelegate:(id)del; -(NSString*)getIPAddress; -(void) checkForChange; @end @protocol IPChangeNotifierDelegate &lt;NSObject&gt; @optional -(void) IPChangeDetected:(NSString*)newIP previousIP:(NSString*)oldIP; @end </code></pre> <p><strong>IPChangeNotifier.m</strong></p> <pre><code>#import IPChangeNotifier.h #import &lt;ifaddrs.h&gt; #import &lt;arpa/inet.h&gt; @implementation IPChangeNotifier -(id) initWithTimer:(float)time andDelegate:(id)del { changeTimer = [NSTimer scheduleTimerWithTimeInterval:time target:self selector:@selector(checkForChange) userInfo:nil repeats:YES]; prevIP = @""; changeDelegate = del; } -(void) checkForChange { NSString *currentIP = [self getIPAddress]; if (![currentIP isEqualToString:prevIP]) { if ([changeDelegate respondsToSelector:@selector(IPChangeDetected:)]){ [changeDelegate IPChangeDetected:currentIP previousIP:prevIP]; } prevIP = currentIP; } } - (NSString *)getIPAddress { struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; NSString *wifiAddress = nil; NSString *cellAddress = nil; // retrieve the current interfaces - returns 0 on success if(!getifaddrs(&amp;interfaces)) { // Loop through linked list of interfaces temp_addr = interfaces; while(temp_addr != NULL) { sa_family_t sa_type = temp_addr-&gt;ifa_addr-&gt;sa_family; if(sa_type == AF_INET || sa_type == AF_INET6) { NSString *name = [NSString stringWithUTF8String:temp_addr-&gt;ifa_name]; NSString *addr = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr-&gt;ifa_addr)-&gt;sin_addr)]; // pdp_ip0 NSLog(@"NAME: \"%@\" addr: %@", name, addr); // see for yourself if([name isEqualToString:@"en0"]) { // Interface is the wifi connection on the iPhone wifiAddress = addr; } else if([name isEqualToString:@"pdp_ip0"]) { // Interface is the cell connection on the iPhone cellAddress = addr; } } temp_addr = temp_addr-&gt;ifa_next; } // Free memory freeifaddrs(interfaces); } NSString *addr = wifiAddress ? wifiAddress : cellAddress; return addr ? addr : @"0.0.0.0"; } @end </code></pre> <p>You can then simply make whatever class you want a delegate by adding <code>&lt;IPChangeNotifierDelegate&gt;</code> to your interface file and then initialize the notifier by doing something simple like below.</p> <pre><code>IPChangeNotifier *ipChecker = [[IPChangeNotifier alloc] initWithTimer:1.0 andDelegate:self] </code></pre> <p>Also make sure you have the following method included to make sure you can get the change events and do whatever you need. </p> <pre><code>-(void) IPChangeDetected:(NSString*)newIP previousIP:(NSString*)oldIP { // Do what you need } </code></pre> <br /><br /><p>来源:<code>https://stackoverflow.com/questions/18378441/ip-address-change-notifications</code></p></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/ios6" hreflang="zh-hans">ios6</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> </div> </div> Sun, 07 Feb 2021 00:50:40 +0000 我与影子孤独终老i 4064989 at https://www.e-learn.cn HomeFinder Revisited: Finding Ideal Homes with Reachability-Centric Multi-Criteria Decision Making https://www.e-learn.cn/topic/3836662 <span>HomeFinder Revisited: Finding Ideal Homes with Reachability-Centric Multi-Criteria Decision Making</span> <span><span lang="" about="/user/154" typeof="schema:Person" property="schema:name" datatype="">主宰稳场</span></span> <span>2020-10-01 07:00:16</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <p><a href="https://zjuidg.org/source/projects/homefinder/reach.pdf" rel="nofollow">论文传送门</a><br /><a href="https://www.youtube.com/watch?v=WcjMXAfuygg" rel="nofollow">视频</a><br /></p> <span id="OSC_h2_1"></span> <h2><a rel="nofollow"></a>摘要</h2> <p>找到一个理想的家是一个艰难而又艰辛的过程。这一过程中最关键的因素之一是家庭所在地和相关兴趣点(如工作场所和娱乐设施)之间的可及性。然而,这种重要性在现存的房地产系统中是没有被认识到的。通过在寻找理想住宅的背景下描述用户需求和分析任务,我们设计了 ReACH,这是一个新颖的可视分析系统,可以帮助人们根据包括可达性在内的多种标准来寻找、评估和选择住宅。此外,我们开发了一个改进的数据驱动模型,用于用大量的滑行轨迹来近似可达性。这种模式使用户能够交互式地整合他们的知识和偏好,以做出明智和明智的决策。通过与先前的研究的理论复杂性进行比较,我们展示了我们模型中的改进,并通过基于任务的评估证明了所提出系统的可用性和有效性。</p> <span id="OSC_h2_2"></span> <h2><a rel="nofollow"></a>Introduction</h2> <p>挑战:</p> <ul><li>可达性计算的效率</li> <li>日常路径的表达</li> <li>个人偏好的整合</li> </ul><p>解决:</p> <ul><li>设计了一个新的基于图的索引和查询算法的可达性模型,以有效地估计具有大量轨迹的位置的可达性。</li> <li>引入了一个新颖的时间线视图来支持复杂可达性约束的编排、组织和可视化,作为用户日常路径的表示。</li> <li>设计并开发了一个可视分析系统,称为Reachability-Aided Contemporary HomeFinder(ReACH),以帮助用户交互查询,过滤和评估候选家庭,以确定他们的理想家园。</li> </ul><p>贡献:</p> <ul><li>我们在寻找理想家园的背景下描述了用户需求和分析任务</li> <li>我们提出了一个实时数据驱动的计算模型,它带有一个基于图的索引来处理可能的可达区域的交互式查询</li> <li>我们设计了一个新的时间线试图,用于编排、组织和可视化可达性约束。基于这一观点,我们开发了一个新颖的可视分析系统ReACH,帮助用户根据多种标准找到理想的住宅。</li> </ul><span id="OSC_h2_3"></span> <h2><a rel="nofollow"></a>Related Work</h2> <ul><li>Reachability query</li> <li>Location selection</li> <li>Urban visualization</li> <li>Multi-criteria decision making (MCDM)</li> </ul><span id="OSC_h2_4"></span> <h2><a rel="nofollow"></a>Data and Task Abstraction</h2> <p><img alt="" class="b-lazy" data-src="https://img-blog.csdnimg.cn/20200907024047922.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" data-original="https://img-blog.csdnimg.cn/20200907024047922.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /><br /> Data Description<br /></p> <ul><li>Road network data</li> <li>Taxi trajectory data</li> <li>POI data</li> <li>Candidate data</li> </ul><p>Requirements:</p> <ul><li>R1: 涉及候选房的空间属性(如位置和邻域)</li> <li>R2: 涉及候选房的内在属性(如价格和楼层大小)</li> </ul><p>Tasks:</p> <ul><li>交互式编排可达性约束</li> <li>直观地探索计算可达性</li> <li>迭代地细化可达性约束</li> <li>有效地过滤和排列候选房</li> </ul><span id="OSC_h2_5"></span> <h2><a rel="nofollow"></a>System Architecture</h2> <p><img alt="" class="b-lazy" data-src="https://img-blog.csdnimg.cn/2020090702483278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" data-original="https://img-blog.csdnimg.cn/2020090702483278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <span id="OSC_h2_6"></span> <h2><a rel="nofollow"></a>Model</h2> <p><img alt="" class="b-lazy" data-src="https://img-blog.csdnimg.cn/20200907025038498.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" data-original="https://img-blog.csdnimg.cn/20200907025038498.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <span id="OSC_h2_7"></span> <h2><a rel="nofollow"></a>Visual Design</h2> <p><img alt="" class="b-lazy" data-src="https://img-blog.csdnimg.cn/20200907025103713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" data-original="https://img-blog.csdnimg.cn/20200907025103713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p><img alt="" class="b-lazy" data-src="https://img-blog.csdnimg.cn/2020090702513339.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" data-original="https://img-blog.csdnimg.cn/2020090702513339.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsZ3pqaA==,size_16,color_FFFFFF,t_70#pic_center" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <span id="OSC_h2_8"></span> <h2><a rel="nofollow"></a>Discussion</h2> <p>经验教训:</p> <ul><li>灵活性和简单性之间的良好平衡在鼓励大众使用所提出的系统方面起着重要作用。</li> <li>精心设计的动画过渡和多协调视图之间的交互使用户能够立即熟悉系统。</li> <li>视觉编码的熟悉性和直观性对大众可视化的可用性有着至关重要的影响。</li> </ul><p>局限性:</p> <ul><li>数据量的增加可能导致该模型的时间性能下降,特别是当构建的轨迹图相当大以至于无法适应内存时。为了缓解这个问题,我们计划将图形压缩和分布式计算等技术集成到ReACH中。</li> <li>不熟悉该系统视觉设计的普通用户可能仍然会遇到困难,因此可视向导技术可以集成到系统中,以提高其可用性。</li> </ul><p>泛化性:<br /> 该系统也可用于考虑位置可达性的其他位置选择场景(例如,为便利店选择位置)。<br /></p> <span id="OSC_h2_9"></span> <h2><a rel="nofollow"></a>Conclusion</h2> <p>本研究描述了以可达性为中心的选择理想住宅的多准则决策问题。为了帮助用户表达他们的偏好,我们引入了这个问题中涉及的几个可达性概念,包括活动和约束。我们提出了一种新的基于图的挖掘模型,它显著扩展了最先进的方法以实现实时性能。我们以该模型为基础,设计并开发了一个名为 ReACH 的新型数据驱动系统。据我们所知,ReACH 是第一个基于海量城市数据为人们寻找理想家园的交互式可视化系统。</p> <p>提出的系统已经部署在本地工作站上。在未来,我们打算将这个系统部署为网络上的云服务。将改进挖掘模型以降低空间复杂度,从而降低云部署的成本。人们感兴趣的其他类型的数据将被合并到提出的系统中。</p> <div class="alert alert-success" role="alert"><p>来源:<code>oschina</code></p><p>链接:<code>https://my.oschina.net/u/4330033/blog/4551820</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> <div class="field--item"><a href="/tag/poi" hreflang="zh-hans">poi</a></div> </div> </div> Wed, 30 Sep 2020 23:00:16 +0000 主宰稳场 3836662 at https://www.e-learn.cn 2020,最新APP重构:网络请求框架 https://www.e-learn.cn/topic/3762140 <span>2020,最新APP重构:网络请求框架</span> <span><span lang="" about="/user/214" typeof="schema:Person" property="schema:name" datatype="">半腔热情</span></span> <span>2020-08-16 05:20:10</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <p>在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点。在这篇文章中我所提出的架构,并不是所谓的 最好 的网络请求架构,因为我只基于我这个app原有架构进行改善,更多的情况下我是以app为出发点,让这个网络架构能够在原app的环境下给我一个完美的结果,当然如果有更好的改进意见,我会很乐于尝试。</p> <span id="OSC_h2_1"></span> <h2>关于网络请求框架</h2> <p>一个好的网络请求框架对于一个团队来说是十分重要的。如果一个网络请求框架没有封装好,或者是在设计上存在问题,那么在开发上会造成许多问题,就拿这段代码作为例子:</p> <div class="cnblogs_code"> <pre><code>[leaveAPI startWithCompletionBlockWith:^(BaseRequest *<span style="color: #000000;">baseRequest, id responseObject) { </span><span style="color: #008000;">//</span><span style="color: #008000;">check the response object</span> BOOL isSuccess =<span style="color: #000000;"> [leaveAPI validResponseObject:responseObject]; </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (isSuccess) { </span><span style="color: #008000;">//</span><span style="color: #008000;">do something...</span> <span style="color: #000000;"> } }failure:</span>^(BaseRequest *<span style="color: #000000;">baseRequest) { </span><span style="color: #008000;">//</span><span style="color: #008000;">do something...</span> }];</code></pre> </div> <p>上面这段代码存在着不少的问题,比如把请求数据的判断放到了每一个请求中、在leaveAPI的块方法中再次调用leaveAPI、块参数中的baseRequest并没有实质作用等等……针对这些问题我会一一进行修正。</p> <span id="OSC_h3_2"></span> <h3>不要让其他人做请求数据有效与否的判断</h3> <p>在上面的代码中,对<code>resposeObject</code>是否有效的判断被设计成了<code>BaseRequest</code>类中的一个方法,程序员需要在调用网络请求后,再调用该方法对<code>responseObject</code>进行判断,这样的设计存在很大的弊端。</p> <p>在实际应用中,很多时候程序员在调用网络请求后往往会忘记调用该方法对返回结果进行判断,甚至忘记了存在这个方法,自行对<code>responseObject</code>进行判断。首先这造成了大规模的代码重复,另一方面,不同程序员自己编写的判断方法散落在各个请求中,假如app在日后更新过程中改变了这个判断标准,会给修改带来很大困难。</p> <span id="OSC_h3_3"></span> <h3>注意在块方法中的循环调用</h3> <p>上面的代码中,在<code>leaveAPI</code>的块方法中,再次调用了<code>leaveAPI</code>中的方法,这样导致了"retain cycle",实际上正确的调用方法应该是:</p> <div class="cnblogs_code"> <pre><code>[leaveAPI startWithCompletionBlockWith:^(LeaveAPI *<span style="color: #000000;">api, id responseObject) { </span><span style="color: #008000;">//</span><span style="color: #008000;">check the response object</span> BOOL isSuccess =<span style="color: #000000;"> [api validResponseObject:responseObject]; </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (isSuccess) { </span><span style="color: #008000;">//</span><span style="color: #008000;">do something...</span> <span style="color: #000000;"> } }];</span></code></pre> </div> <p>为什么会出现这样的情况,首先主要是因为整个请求框架的注释不清晰,导致其他程序员对方法的理解存在偏差,进而天马行空,发挥自己的想象力来调用方法。另外由于各个API与<code>BaseRequest</code>的设计上存在问题,导致整个网络请求框架的混乱。</p> <span id="OSC_h3_4"></span> <h3>不要在单独的API中实现上传下载操作</h3> <p>在旧的网络请求框架中,<code>BaseRequest</code>一开始的设计中并没有针对上传和下载操作进行处理,而且整个<code>BaseRequest</code>的设计中并没有AOP,这个导致了在日后需要增加上传和下载功能的时候只能将他们写到单独的API中,这个导致了代码重复,代码的复用性降低,如:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">//</span> <span style="color: #008000;">//</span><span style="color: #008000;"> FileAPI.m </span><span style="color: #008000;">// </span><span style="color: #000000;"> ...some methods... </span><span style="color: #0000ff;">#pragma</span> mark - Upload &amp; Download -(<span style="color: #0000ff;">void</span><span style="color: #000000;">)uploadFile:(FileUploadCompleteBlock)uploadBlock errorBlock:(FileUploadFailBlock)errorBlock { NSString </span>*url =<span style="color: #000000;"> self.url AFHTTPRequestOperationManager </span>*manager =<span style="color: #000000;"> [AFHTTPRequestOperationManager manager]; manager.requestSerializer </span>=<span style="color: #000000;"> [AFHTTPRequestSerializer serializer]; manager.operationQueue.maxConcurrentOperationCount </span>= <span style="color: #800080;">5</span><span style="color: #000000;">; manager.requestSerializer.timeoutInterval </span>= <span style="color: #800080;">30</span><span style="color: #000000;">; manager.responseSerializer.acceptableContentTypes </span>= [NSSet setWithObjects:<span style="color: #800000;">@"</span><span style="color: #800000;">application/json</span><span style="color: #800000;">"</span>, <span style="color: #800000;">@"</span><span style="color: #800000;">text/html</span><span style="color: #800000;">"</span>,<span style="color: #800000;">@"</span><span style="color: #800000;">text/json</span><span style="color: #800000;">"</span>,<span style="color: #800000;">@"</span><span style="color: #800000;">text/javascript</span><span style="color: #800000;">"</span>,<span style="color: #800000;">@"</span><span style="color: #800000;">text/plain</span><span style="color: #800000;">"</span><span style="color: #000000;">,nil]; [manager POST:url parameters:[self requestArgument] constructingBodyWithBlock:</span>^(id&lt;AFMultipartFormData&gt;<span style="color: #000000;"> formData) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> upload operation ...</span> <span style="color: #000000;"> }success:</span>^(AFHTTPRequestOperation *<span style="color: #000000;">operation, id responseObject) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> do something ...</span> }failure:^(AFHTTPRequestOperation *operation, NSError *<span style="color: #000000;">error) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> do something ...</span> <span style="color: #000000;"> }]; }</span></code></pre> </div> <p>在<code>FileAPI.m</code>中,上传操作是这样实现的。写下这段代码的时候是使用<code>AFNetworking 2.0</code>,而现在使用的是<code>AFNetworking 3.0</code>,<code>AFHTTPRequestOperationManager</code>也变成了<code>AFHTTPSessionManger</code>,这个时候散落在各个API的上传方法修改起来就变的很麻烦。</p> <span id="OSC_h3_5"></span> <h3>BaseRequest中的设计缺陷</h3> <p>在上文中一直在指出各个API中的缺陷,而也提到很多地方是归咎于<code>BaseReuqest</code>的问题,现在就来看一下它里面的一些缺陷:</p> <p>首先在整个<code>BaseRequest</code>中,它包括了地址的组装、网络环境的判断、请求的发送等等,基本网络请求的所有操作都是由这一个类来实现。这样就导致了整个类十分庞大,在需要添加新的请求类型如我上文提到的上传与下载时,会难以下手,这就导致了我上文提到的种种问题。</p> <p>另一方面<code>BaseRequest</code>中没有针对返回数据的处理,这里的处理是指返回数据的缓存操作、数据过滤操作、请求数据为空的处理操作等等,如果这些问题都交给方法调用者来完成的话,会导致某一模块的代码量暴涨(在本app是VC),而且很多时候数据需要的只是一个默认的缓存操作、默认的过滤操作,这个时候重复性的代码会很多,倒不如把这些操作统一处理好,假如有特殊的API需要进行特殊的配置,再由该API对这些配置进行修改,而不需要把这些默认操作交由其他程序员来完成。</p> <span id="OSC_h2_6"></span> <h2>我是如何设计新的网络请求框架</h2> <p>上文提到了各种各样的不足,所以是时候针对这些不足进行改进了。</p> <p>先看大局,再看细节。首先是整个架构的数据流向:</p> <div class="image-package"> <img class="b-lazy" data-src="https://upload-images.jianshu.io/upload_images/4853563-91c5f38d686cfc91.jpg?imageMogr2/auto-orient/strip" data-original="https://upload-images.jianshu.io/upload_images/4853563-91c5f38d686cfc91.jpg?imageMogr2/auto-orient/strip" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></div> <p><br />整个网络请求框架中最重要的是其中的<code>NetworkManage</code>,它主要是负责整个请求的处理。<br /></p> <div class="image-package"> <img class="b-lazy" data-src="https://upload-images.jianshu.io/upload_images/4853563-0706a2c235dbbab4.jpg?imageMogr2/auto-orient/strip" data-original="https://upload-images.jianshu.io/upload_images/4853563-0706a2c235dbbab4.jpg?imageMogr2/auto-orient/strip" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></div> <span id="OSC_h3_7"></span> <h3>设计中的一些关注重点</h3> <span id="OSC_h4_8"></span> <h4>首先检测网络状态</h4> <p>当一个请求发起的时候,首先它会检测网络是否联通,假如没有联通的时候会直接弹出一个窗口提醒用户需要先连接网络,而不会进行下一步的请求。而在旧的网络请求框架中,很多时候把这段代码放到了vc,现在将它整合进来。</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)addRequest:(BaseRequest*<span style="color: #000000;">)request { </span><span style="color: #008000;">//</span><span style="color: #008000;">TODO: 检查网络是否通畅</span> <span style="color: #0000ff;">if</span>(!<span style="color: #000000;">[self checkNetworkConnection]) { [self showNetworkAlertForRequest:request]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; }</span></code></pre> </div> <p><strong>[self checkNetworkConnection]:</strong></p> <div class="cnblogs_code"> <pre><code>-<span style="color: #000000;"> (BOOL)checkNetworkConnection { </span><span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr zeroAddress; bzero(</span>&amp;zeroAddress, <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(zeroAddress)); zeroAddress.sa_len </span>= <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(zeroAddress); zeroAddress.sa_family </span>=<span style="color: #000000;"> AF_INET; SCNetworkReachabilityRef defaultRouteReachability </span>= SCNetworkReachabilityCreateWithAddress(NULL, (<span style="color: #0000ff;">struct</span> sockaddr *)&amp;<span style="color: #000000;">zeroAddress); SCNetworkReachabilityFlags flags; BOOL didRetrieveFlags </span>= SCNetworkReachabilityGetFlags(defaultRouteReachability, &amp;<span style="color: #000000;">flags); CFRelease(defaultRouteReachability); </span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">didRetrieveFlags) { printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">Error. Count not recover network reachability flags\n</span><span style="color: #800000;">"</span><span style="color: #000000;">); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO; } BOOL isReachable </span>= flags &amp;<span style="color: #000000;"> kSCNetworkFlagsReachable; BOOL needsConnection </span>= flags &amp;<span style="color: #000000;"> kSCNetworkFlagsConnectionRequired; </span><span style="color: #0000ff;">return</span> (isReachable &amp;&amp; !needsConnection) ?<span style="color: #000000;"> YES : NO; }</span></code></pre> </div> <span id="OSC_h4_9"></span> <h4>活性组装请求地址</h4> <p>而在进行完网络联通的判断之后,就会对请求的地址进行组装。组装地址的方法并没有太大的变化,但是在旧的请求框架开发的时候,我注意到一个问题:在增加新需求增加新的接口的时候,往往需要连接到测试服务器上进行调试,这时候就需要将请求的地址改成测试服务器的地址。但这往往引发一些问题,因为测试服务器上可能没有正式服务器的一些数据,在测试时往往没有问题,但是转移到正式服务器上就出现了各种问题,所以我就想能不能改成程序员可以改变API连接的地址,而不改变全局的请求框架,让各个API在请求的时候判断自己是否需要连接到测试服务器。</p> <div class="cnblogs_code"> <pre><code>- (NSString *<span style="color: #000000;">)urlString{ NSString </span>*url =<span style="color: #000000;"> nil; </span><span style="color: #008000;">//</span><span style="color: #008000;">TODO: 使用副地址</span> <span style="color: #0000ff;">if</span> ([self.child respondsToSelector:@selector(useViceUrl)] &amp;&amp;<span style="color: #000000;"> [self.child useViceUrl]){ baseUrl </span>=<span style="color: #000000;"> self.config.viceBaseUrl; } </span><span style="color: #008000;">//</span><span style="color: #008000;">TODO: 使用主地址</span> <span style="color: #0000ff;">else</span><span style="color: #000000;">{ baseUrl </span>=<span style="color: #000000;"> self.config.mainBaseUrl; } }</span></code></pre> </div> <span id="OSC_h4_10"></span> <h4>让API能够独立配置</h4> <p>组装地址完毕之后,就开始根据API自身的设置来进行配置,在旧的请求框架中,API的是直接继承自<code>BaseRequest</code>这个类,导致了<code>BaseRequest</code>需要完成大量的工作,或是存有大量空方法,可读性与稳定性都很差,很多东西也没有办法让API自己进行独立设置。在新的框架中,我选择将API的设置通过一个叫做<code>APIProtocol</code>的协议来完成,API需要配置的内容可以通过实现该协议的方法来进行配置,否则就会直接使用默认配置</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">//</span><span style="color: #008000;">TODO: 检查是否使用自定义超时时间</span> <span style="color: #0000ff;">if</span><span style="color: #000000;"> ([request respondsToSelector:@selector(requestTimeoutInterval)]) { self.manager.requestSerializer.timeoutInterval </span>=<span style="color: #000000;"> [request requestTimeoutInterval]; } </span><span style="color: #0000ff;">else</span><span style="color: #000000;">{ self.manager.requestSerializer.timeoutInterval </span>= <span style="color: #800080;">60.0</span><span style="color: #000000;">; } more methods ...</span></code></pre> </div> <span id="OSC_h4_11"></span> <h4>完善返回数据的基础判断</h4> <p>最后在进行完请求判断后,将会对<code>responseObject</code>的有效性进行判断。关于数据的判断我一开始是打算放在<code>BaseRequest</code>中的,因为一开始的想法是希望能够在<code>BaseRequest</code>中做一个默认的判断,假如API自身需要再度对<code>responseObject</code>进行进一步的判断时,可以通过协议方法来重新编写该API独立的判定方法。但这种方法最终被我弃用了,首先<code>responseObject</code>的基础判断在我看来是不应该放在<code>BaseRequest</code>中的,因为<code>BaseRequest</code>是作为一个请求的”中心”,不应该把数据处理的问题交给它处理。另一方面是因为我们需要设计的是基础判断,它和各个API独立的判断方式不是平行关系,而是层次关系,因为在设计的是每一个API都需要进行的判断,假如在整个app中有很多API需要进行独立判断,就意味着需要编写很多次基础判断逻辑,同时假如在日后需要修改这个基础判断内容,代码也散落在各个地方,这不是我们想要的结果。</p> <p>所以在设计上我最终把这个判断方法放到了<code>NetworkConfig</code>中,新增了一个<code>BaseFilter</code>类,专门用于返回数据的判断,假如我的API需要增加独特的判断方法时,可以直接在请求方法中直接对<code>responseObject</code>进行进一步判断。</p> <p><strong>NetworkConfig.m:</strong></p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">//</span><span style="color: #008000;">NetworkManage.m</span> <span style="color: #0000ff;">if</span><span style="color: #000000;">([self.networkConfig.baseFilter validResponseObject:responseObject]) { request.responseObject </span>=<span style="color: #000000;"> responseObject; [self handleSuccessRequest:task]; } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { NSError </span>*error =<span style="color: #000000;"> [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil]; request.responseObject </span>=<span style="color: #000000;"> responseObject; [self handleFailureRequest:task error:error]; }</span></code></pre> </div> <p><strong>BaseFilter.m</strong></p> <div class="cnblogs_code"> <pre><code><span style="color: #000000;">@implementation BaseFilter </span>-<span style="color: #000000;"> (BOOL)validResponseObject:(id)responseObject { </span><span style="color: #008000;">//</span><span style="color: #008000;">TODO: 检查是否返回了数据且数据是否正确</span> <span style="color: #0000ff;">if</span> (!responseObject &amp;&amp; ![responseObject isKindOfClass:[NSDictionary <span style="color: #0000ff;">class</span>]] &amp;&amp; ![responseObject[<span style="color: #800000;">@"</span><span style="color: #800000;">success</span><span style="color: #800000;">"</span><span style="color: #000000;">] boolValue]) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO; } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">return</span><span style="color: #000000;"> YES; } @end</span></code></pre> </div> <span id="OSC_h2_12"></span> <h2>结语</h2> <p>我相信在软件设计中并不存在最好或者是最正确的架构,因为这是一个很抽象的工作,但我相信我们应该可以设计出一个扩展性良好和简单明了的架构,能够让新加入的程序员快速上手,能够适应软件接下来的开发需要,那这大概是一个好的架构。<br />以上内容就是本篇的全部内容以上内容希望对你有帮助,有被帮助到的朋友欢迎点赞,评论。<br />如果对软件测试、接口测试、自动化测试、面试经验交流。感兴趣可以关注我,我们会有同行一起技术交流哦。<br /><br /></p> <div class="alert alert-success" role="alert"><p>来源:<code>oschina</code></p><p>链接:<code>https://my.oschina.net/u/4275644/blog/4334973</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/mianshi" hreflang="zh-hans">面试</a></div> <div class="field--item"><a href="/tag/inet" hreflang="zh-hans">iNet</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> <div class="field--item"><a href="/tag/javascript" hreflang="zh-hans">javascript</a></div> <div class="field--item"><a href="/tag/mark" hreflang="zh-hans">Mark</a></div> </div> </div> Sat, 15 Aug 2020 21:20:10 +0000 半腔热情 3762140 at https://www.e-learn.cn 面试必问之jvm https://www.e-learn.cn/topic/3742222 <span>面试必问之jvm</span> <span><span lang="" about="/user/145" typeof="schema:Person" property="schema:name" datatype="">与世无争的帅哥</span></span> <span>2020-08-11 14:36:50</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <h1>问题1 说一下jvm内存模型</h1> <h2>问题1.1 jvm内存模型</h2> <p><img alt="在这里插入图片描述" class="b-lazy" data-src="https://img-blog.csdnimg.cn/20200621222917284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTk4NjExMjY=,size_16,color_FFFFFF,t_70" data-original="https://img-blog.csdnimg.cn/20200621222917284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTk4NjExMjY=,size_16,color_FFFFFF,t_70" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /><strong>栈区:</strong></p> <p>栈分为java虚拟机栈和本地方法栈</p> <p>重点是Java虚拟机栈,它是线程私有的,生命周期与线程相同。</p> <p>每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程。</p> <p>通常说的栈就是指局部变量表部分,存放编译期间可知的8种基本数据类型,及对象引用和指令地址。局部变量表是在编译期间完成分配,当进入一个方法时,这个栈中的局部变量分配内存大小是确定的。</p> <p>会有两种异常StackOverFlowError和 OutOfMemoneyError。当线程请求栈深度大于虚拟机所允许的深度就会抛出StackOverFlowError错误;虚拟机栈动态扩展,当扩展无法申请到足够的内存空间时候,抛出OutOfMemoneyError。</p> <p>本地方法栈为虚拟机使用到本地方法服务(native)</p> <p><strong>堆区:</strong></p> <p>堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。</p> <p><strong>方法区:</strong></p> <p>被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)</p> <p>垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。</p> <p>常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性,里面可以存放编译期生成的常量;运行期间的常量也可以添加进入常量池中,比如string的intern()方法。</p> <p><strong>程序计数器:</strong></p> <p>当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。</p> <p>Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。</p> <p>唯一一块Java虚拟机没有规定任何OutofMemoryError的区块。</p> <h2>1.2 jvm堆空间是怎么划分的</h2> <p>通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区最要放新创建对象,From survivor 和 To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1。</p> <p>不过很多文章介绍分为3个区块,把方法区算着为永久代。这大概是基于Hotspot虚拟机划分,然后比如IBM j9就不存在永久代概论。不管怎么分区,都是存放对象实例。</p> <h2>1.3 jvm内存有哪些初始化参数</h2> <p>1.JVM运行时堆的大小</p> <pre><code>-Xms堆的最小值 -Xmx堆空间的最大值 </code></pre> <p>2.新生代堆空间大小调整</p> <pre><code>-XX:NewSize新生代的最小值 -XX:MaxNewSize新生代的最大值 -XX:NewRatio设置新生代与老年代在堆空间的大小 -XX:SurvivorRatio新生代中Eden所占区域的大小 </code></pre> <p>3.永久代大小调整</p> <pre><code>-XX:MaxPermSize </code></pre> <h1>问题2 jvm垃圾回收机制有了解吗?</h1> <p>在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。</p> <h2>问题2.1 java中垃圾收集的方法有哪些?</h2> <ol><li>标记-清除: 这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。</li> <li>复制算法: 为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。 于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)</li> <li>标记-整理 该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。</li> <li>分代收集 现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。</li> </ol><h2>问题2.2 jvm垃圾收集器有哪几种?</h2> <ul><li>Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。</li> <li>ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。</li> <li>Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。</li> <li>Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。</li> <li>Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。</li> <li>CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。</li> <li>G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。</li> </ul><h2>问题2.3 CMS收集器和G1收集器的区别:</h2> <ul><li>CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;</li> <li>G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;</li> <li>CMS收集器以最小的停顿时间为目标的收集器;</li> <li>G1收集器可预测垃圾回收的停顿时间</li> <li>CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片</li> <li>G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。</li> </ul><h2>问题2.4 JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代</h2> <p>Java堆 = 老年代 + 新生代 新生代 = Eden + S0 + S1 1、当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。 2、大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态; 3、如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。 4、老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。 5、Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。</p> <h2>问题2.5 什么情况下会触发fullgc</h2> <ul><li> <p>老年代空间不足 老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。</p> </li> <li> <p>永生区空间不足 JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息: java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。</p> </li> <li> <p>CMS GC时出现promotion failed和concurrent mode failure 对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能 会触发Full GC。 promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在 执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。 对措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕 后很久才触发sweeping动作。对于这种状况,可通过设置-XX: CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间 这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之 前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。 例如程序第一次触发Minor GC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB, 则执行Full GC。 当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否 大于6MB,如小于,则触发对旧生代的回收。 除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java - Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。</p> </li> <li> <p>堆中分配很大的对象 所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。 为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数,即-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了,JVM设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。</p> </li> </ul><h2>问题2.6 怎么判断一个对象是否存活</h2> <p>jvm中有两种方式判断对象是否存活</p> <ul><li>引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。</li> <li>可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。</li> </ul><h2>问题2.7 哪些对象可作为GC Roots对象?</h2> <ul><li> <p>虚拟机栈中应用的对象</p> </li> <li> <p>方法区里面的静态对象</p> </li> <li> <p>方法区常量池的对象</p> </li> <li> <p>本地方法栈JNI应用的对象</p> </li> </ul><h1>问题3 jvm类加载原理</h1> <h2>问题3.1 说一下类的生命周期</h2> <p>java类加载过程:加载--&gt;验证--&gt;准备--&gt;解析--&gt;初始化,之后类就可以被使用了。绝大部分情况下是按这</p> <p>样的顺序来完成类的加载全过程的。但是是有例外的地方,解析也是可以在初始化之后进行的,这是为了支持</p> <p>java的运行时绑定,并且在一个阶段进行过程中也可能会激活后一个阶段,而不是等待一个阶段结束再进行后一个阶段。 <img alt="在这里插入图片描述" class="b-lazy" data-src="https://img-blog.csdnimg.cn/20200626163530105.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTk4NjExMjY=,size_16,color_FFFFFF,t_70" data-original="https://img-blog.csdnimg.cn/20200626163530105.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTk4NjExMjY=,size_16,color_FFFFFF,t_70" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>1.加载</p> <p>加载时jvm做了这三件事:</p> <pre><code> 1)通过一个类的全限定名来获取该类的二进制字节流 2)将这个字节流的静态存储结构转化为方法区运行时数据结构 3)在内存堆中生成一个代表该类的java.lang.Class对象,作为该类数据的访问入口 </code></pre> <p>2.验证</p> <p>验证、准备、解析这三步可以看做是一个连接的过程,将类的字节码连接到JVM的运行状态之中</p> <p>验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会威胁到jvm的安全</p> <p>验证主要包括以下几个方面的验证:</p> <p>  1)文件格式的验证,验证字节流是否符合Class文件的规范,是否能被当前版本的虚拟机处理</p> <pre><code> 2)元数据验证,对字节码描述的信息进行语义分析,确保符合java语言规范 </code></pre> <p>  3)字节码验证 通过数据流和控制流分析,确定语义是合法的,符合逻辑的</p> <p>  4)符号引用验证 这个校验在解析阶段发生</p> <p>3.准备 为类的静态变量分配内存,初始化为系统的初始值。对于final static修饰的变量,</p> <p>直接赋值为用户的定义值。如下面的例子:这里在准备阶段过后的初始值为0,而不是7</p> <p>public static int a=7 4.解析</p> <p>解析是将常量池内的符号引用转为直接引用(如物理内存地址指针)</p> <p>5.初始化</p> <p>到了初始化阶段,jvm才真正开始执行类中定义的java代码</p> <pre><code> 1)初始化阶段是执行类构造器&lt;clinit&gt;()方法的过程。类构造器&lt;clinit&gt;()方法是由编译器自动收集 类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。 2)当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。 3)虚拟机会保证一个类的&lt;clinit&gt;()方法在多线程环境中被正确加锁和同步。 </code></pre> <h2>问题3.2 说一下类加载机制</h2> <p>双亲委派 jvm自带三种类加载器,分别是: 启动类加载器。 扩展类加载器。 应用程序类加载器 他们的继承关系如下图: <img alt="在这里插入图片描述" class="b-lazy" data-src="https://img-blog.csdnimg.cn/20200626163828882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTk4NjExMjY=,size_16,color_FFFFFF,t_70" data-original="https://img-blog.csdnimg.cn/20200626163828882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xwMTk4NjExMjY=,size_16,color_FFFFFF,t_70" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /> 双亲委派机制工作过程如下:</p> <p>当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。</p> <p>当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.</p> <p>当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。</p> <p>为啥要搞这么复杂?自己处理不好吗?</p> <p>双亲委派的优点如下:</p> <p>避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。 为了安全。避免核心类,比如String被替换。</p> <p>&lt;/clinit&gt;&lt;/clinit&gt;&lt;/clinit&gt;</p> <div class="alert alert-success" role="alert"><p>来源:<code>oschina</code></p><p>链接:<code>https://my.oschina.net/lipengxs/blog/4326168</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/jdk" hreflang="zh-hans">JDK</a></div> <div class="field--item"><a href="/tag/watermark" hreflang="zh-hans">watermark</a></div> <div class="field--item"><a href="/tag/shadow" hreflang="zh-hans">Shadow</a></div> <div class="field--item"><a href="/tag/mianshi" hreflang="zh-hans">面试</a></div> <div class="field--item"><a href="/tag/java" hreflang="zh-hans">java</a></div> <div class="field--item"><a href="/tag/sweep" hreflang="zh-hans">Sweep</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> <div class="field--item"><a href="/tag/mark" hreflang="zh-hans">Mark</a></div> <div class="field--item"><a href="/tag/j9" hreflang="zh-hans">J9</a></div> </div> </div> Tue, 11 Aug 2020 06:36:50 +0000 与世无争的帅哥 3742222 at https://www.e-learn.cn 深入理解JVM(③)判断对象是否还健在? https://www.e-learn.cn/topic/3696800 <span>深入理解JVM(③)判断对象是否还健在?</span> <span><span lang="" about="/user/166" typeof="schema:Person" property="schema:name" datatype="">跟風遠走</span></span> <span>2020-07-25 16:16:44</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <span id="OSC_h2_1"></span> <h2>前言</h2> <p>因为Java对象主要存放在Java堆里,所以垃圾收集器(Garbage Collection)在对Java堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(不被引用了)。</p> <span id="OSC_h2_2"></span> <h2>判断对象是否健在的算法</h2> <span id="OSC_h3_3"></span> <h3>1.引用计数算法</h3> <p>引用计数算法,很容易理解,<strong>在对象中添加一个引用计数器,每有一个地方引用它时,计数器值就加一;当引用失效是,计数器值就减一;任何时刻计数器为零的对象就是不可以能再被使用的对象</strong>。<br /> 引用计数算法的原理简单,判定效率也很高。市面上也确实有一些技术使用的此类算法来判定对象是否存活,像ActionScript 3 的FlashPlayer、Python语言等。但是在主流的Java虚拟机里面都没有选用引用计算法来管理内存,主要是使用此算法时,必须要配合大量的额外处理才能保证正确的工作,例如要解决对象之间的相互循环引用的问题。<br /></p> <pre><code class="language-java">public class OneTest { public Object oneTest = null; private static final int _1MB = 1024 * 1024; private byte[] bigSize = new byte[256 * _1MB]; /** * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过。 */ @Test public void testGC(){ OneTest test1 = new OneTest(); OneTest test2 = new OneTest(); test1.oneTest = test2; test2.oneTest = test1; test1 = null; test2 = null; // 假设在这行发生GC,test1和test2是否能被回收? System.gc(); } } </code></pre> <p>分析代码,test1和test2对象都被设置成了null,在后面发生GC的时候,如果按照引用计数算法,这两个对象虽然都被设置成了null,但是test1引用了test2,test2又引用了test1,所以这两个对象的引用计数值都不为0,所以都不会被回收,但是真正的实际运行结果是,这两个对象都被回收了,这也说明HotSpot虚拟机并不是用引用计数法来进行的内存管理。</p> <span id="OSC_h3_4"></span> <h3>2. 可达性分析算法</h3> <p>当前主流的商用程序语言(Java、C#等),都是通过可达性分析(Reachability Analysis)算法来判断对象是否存活的。这个算法的基本思路就是通过一一系列称为“GC Roots” 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索走过的的路径称为“<strong>引用链</strong>”(Reference Chain),如果某个对象到GC Roots 间没有任何引用链相连,或者从GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。<br /><strong>如下图,object10、object11、object12这三个对象,虽然互相有关联,但是它们到GC Roots是不可达的,因此它们会被判定为可回收的对象。</strong><br /><img alt="可达性分析算法" class="b-lazy" data-src="https://img2020.cnblogs.com/blog/772743/202006/772743-20200606175528467-1067132921.png" data-original="https://img2020.cnblogs.com/blog/772743/202006/772743-20200606175528467-1067132921.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /><br /> 在Java程序中,固定可作为GC Roots 的对象包括以下几种:<br /><br /><br /></p> <ul><li>在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个现场被调用的方法堆栈中使用到的参数、局部变量、临时变量等。</li> <li>在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。</li> <li>在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。</li> <li>在本地方法栈中JNI(即通常所说的Native方法)引用的对象。</li> <li>Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(NullPointException、OutOfMemoryError)等,还有系统类加载器。</li> <li>所有被同步锁(synchronized关键字)持有的对象。</li> <li>反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。<br /> 除了这些固定的GC Roots集合以外,根据垃圾收集器以及当前回收的呢村区域不同,还会有其他对象“临时性”的加入,<strong>如果只针对Java堆中某一块儿区域发起垃圾收集时(例如只针对年轻代的垃圾收集),必须考虑到当前区域内的对象是否有被其他区域的对象所引用,这个时候就需要把这些关联区域的对象一并加入GC Roots集合中,来保证可达性分析的正确性。</strong><br /></li> </ul><span id="OSC_h2_5"></span> <h2>重申引用</h2> <p>无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判断对象是否存活都和“<strong>引用</strong>”离不开关系。在JDK1.2之前,Java里对引用的概念是:如果reference类型的数据中存储的数值代表的是另外一块儿内存的地址,就称该reference数据是代表某块内存、某个对象的引用。<br /> 在JDK1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。<br /></p> <ul><li><strong>强引用</strong>是最传统的“引用”的定义,指引用复制,即类似</li> </ul><pre><code class="language-java">Object obj = new Object() </code></pre> <p>这种引用关系。无论在任何情况下,只要强引用关系还存在,垃圾收集器就不会回收掉被引用的对象。</p> <ul><li><strong>软引用</strong>是用来描述一些还有用,但非必须的对象。在系统发生内存溢出前,会先对软引用对象进行第二次回收,如果回收后还没有足够的内存,才会抛出内存溢出的异常。</li> <li><strong>弱引用</strong>也是用来描述那些非必须的对象,但是它的强度比软引用更弱一些,弱引用的对象,只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。</li> <li><strong>虚引用</strong>也称为“幽灵引用”或“幻影引用”,它是最弱的一种引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。</li> </ul><span id="OSC_h2_6"></span> <h2>判断对象是生是死的过程</h2> <p>即使在可达性分析算法中,判断为不可达的对象,也不是“非死不可”的,要真正宣告一个对象死亡,至少要经历两次标记过程:</p> <ul><li>如果第一次对象在进行可达性分析后发现与GC Roots 不可达,将进行第一次标记。</li> <li>随后对此对象进行一次是否有必要执行finalize()方法进行筛选,假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,都视为“没有必要执行”。<br /> 如果对象被判定有必要执行finalize()方法,会将对象放置在一个名为F-Queue的队列中,并在由一条由虚拟机自动建立的、低调度的线程区执行它们的finalize()方法。但并不承诺一定会等待它们运行结束。<br /></li> </ul><p>需要注意的是:任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临第二次回收,它的finalize()方法不会被再次执行。<br /><strong>还有一点就是Java官方已经明确声明不推荐手动调用finalize()方法了,因为它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,并且finanlize()能做的所有工作,使用try-finally或其他方式都可以做的更好、更及时。</strong><br /></p> <span id="OSC_h2_7"></span> <h2>回收方法区</h2> <p>方法区垃圾收集的“性价比”通常比较低,并且方法区回收也有过于苛刻的判定条件。<br /> 方法区的垃圾收集主要回收两部分内容:<strong>废弃的常量</strong>和<strong>不再使用的类型</strong>,回收废弃常量时,如果当前系统没有一个常量的值是当前常量值,且虚拟机中也没有其他地方引用这个常量。如果这个时候发生垃圾回收,常量就会被系统清理出常量池。<br /> 判定一个类型是否属于“不再使用的类”的条件就比较苛刻了,要同时满足如下三个条件:<br /><br /></p> <ul><li>该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。</li> <li>加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的冲加载等,否则通常很难达成的。</li> <li>该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。</li> </ul><p>同时满足了上述的三个条件后,也只是被允许进行回收了,关于是否要对类型进行回收还要对虚拟机进行一系列的参数设置,这里就不赘述了,感兴趣的可以自己去查询。</p> <div class="alert alert-success" role="alert"><p>来源:<code>oschina</code></p><p>链接:<code>https://my.oschina.net/u/4302130/blog/4302727</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/jdk" hreflang="zh-hans">JDK</a></div> <div class="field--item"><a href="/tag/java" hreflang="zh-hans">java</a></div> <div class="field--item"><a href="/tag/osgi" hreflang="zh-hans">osgi</a></div> <div class="field--item"><a href="/tag/python" hreflang="zh-hans">python</a></div> <div class="field--item"><a href="/tag/fqueue" hreflang="zh-hans">FQueue</a></div> <div class="field--item"><a href="/tag/java-ee" hreflang="zh-hans">Java EE</a></div> <div class="field--item"><a href="/tag/jvmti" hreflang="zh-hans">jvmti</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> </div> </div> Sat, 25 Jul 2020 08:16:44 +0000 跟風遠走 3696800 at https://www.e-learn.cn iOS网络请求-AFNetworking源码解析 https://www.e-learn.cn/topic/3591059 <span>iOS网络请求-AFNetworking源码解析</span> <span><span lang="" about="/user/95" typeof="schema:Person" property="schema:name" datatype="">。_饼干妹妹</span></span> <span>2020-04-24 17:47:45</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <p>趁着端午节日,自己没有什么过多的安排,准备花4-5天左右,针对网络请求源码AFNetworking和YTKNetwork进行解析以及这两年多iOS实际开发经验(其实YTKNetwork也是对AFNetworking的深度封装),结合多个实际项目,分别针对这两个网络框架,进行封装使用(可以直接使用)。本篇主要讲解AFNetworking源码解析,都是自己亲自看AFNetworking源码以及心得体会,大约看下来需要20-30分钟。欢迎指正!!!</p> <p> AFNetworking源码地址:<a href="https://github.com/AFNetworking/AFNetworking" target="_blank" rel="nofollow">https://github.com/AFNetworking/AFNetworking</a></p> <p>针对AFNetworking封装:<a href="https://www.cnblogs.com/guohai-stronger/p/9193465.html" target="_blank" rel="nofollow">https://www.cnblogs.com/guohai-stronger/p/9193465.html</a></p> <p>YTKNetwork的源码详解:<a href="https://www.cnblogs.com/guohai-stronger/p/9194519.html" target="_blank" rel="nofollow">https://www.cnblogs.com/guohai-stronger/p/9194519.html</a></p> <p> </p> <p><strong><span style="font-size: 18px;">一.AFNetworking的代码结构:</span></strong></p> <p><img alt="" width="225" height="586" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/2f6d5a6594163f3983f3e6f2007a66da961.png" data-original="https://oscimg.oschina.net/oscnet/2f6d5a6594163f3983f3e6f2007a66da961.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>新的代码结构将AFNetworking.h放到了Supporting Files里面。</p> <p><img alt="" width="386" height="101" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/845c317621608a286e44ab862c24d2e9d70.png" data-original="https://oscimg.oschina.net/oscnet/845c317621608a286e44ab862c24d2e9d70.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>自从AFNetworking结构更改以后,结构可能不够清晰,以前的版本是这样的:</p> <p><img alt="" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/d4108bec8054be3e089f26cac20da17dbd0.png" data-original="https://oscimg.oschina.net/oscnet/d4108bec8054be3e089f26cac20da17dbd0.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>其实没有多少改变,从这张图可以看出:除去Support Files,可以看到AF分为如下5个功能模块:</p> <ul><li>网络通信模块(最核心)(AFURLSessionManager、AFHTTPSessionManager)</li> <li>网络状态监听模块(Reachability)</li> <li>网络通信安全策略模块(Security)</li> <li>网络通信信息序列化/反序列化模块(Serialization)</li> <li>对于iOS UIkit库的拓展(UIKit)</li> </ul><p><strong><span style="font-size: 18px;"> 二.各类讲解</span></strong></p> <p>1.网络通信模块-AFURLSessionManager与AFHTTPSessionManager</p> <p>AFHTTPSessionManager是继承AFURLSessionManager的,相当于对AFURLSessionManager的再次封装。</p> <p><strong>(1)AFURLSessionManager</strong></p> <p>1&gt;通过查看AFURLSessionManager类:</p> <p><img alt="" width="843" height="104" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/19026fd2b279bafe65c39a1be830d996afd.png" data-original="https://oscimg.oschina.net/oscnet/19026fd2b279bafe65c39a1be830d996afd.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>发现AFURLSessionManager遵守NSSecureCoding, NSCopying两个协议,以及遵守</p> <p>NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDataDelegate,NSURLSessionDownloadDelegate四个代理。在AFURLSessionManager中实现协议里的方法,用来处理网络请求中不同的情况:例如:暂停,取消,数据保存,更新数据进度条一样。</p> <p>2&gt;下面是查看AFURLSessionManager类,所包含的属性:</p> <p><img alt="" width="751" height="622" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/ef50c7ca6ddc73d9c91fafe33a0ea0f1f0d.png" data-original="https://oscimg.oschina.net/oscnet/ef50c7ca6ddc73d9c91fafe33a0ea0f1f0d.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>3&gt;下面是AFURLSessionManager的方法</p> <p>(1)</p> <p><img alt="" width="767" height="276" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/a4d6700dad186bd238fcf067555a28312cb.png" data-original="https://oscimg.oschina.net/oscnet/a4d6700dad186bd238fcf067555a28312cb.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>如果入参configuration为nil,则调用NSURLSessionConfiguration的defaultSessionConfiguration方法,创建一个会话配置,并使用该配置创建一个会话对象,同时还初始化了安全策略、锁、返回数据解析器(JSON 数据解析器)等属性。</p> <p>(2)</p> <p><img alt="" width="750" height="150" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/38e0f6fa0ebe60d451462ffe119d89933ce.png" data-original="https://oscimg.oschina.net/oscnet/38e0f6fa0ebe60d451462ffe119d89933ce.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>此方法是取消会话Session,发现cancelPendingTasks是一个BOOL类型,如果返回NO,意思是允许会话中的任务执行完毕后,再取消会话,但是会话一经取消将无法重启;反之如果返回YES,那么直接取消会话,其相关联的任务和回调都将会释放。</p> <p>(3)</p> <p><img alt="" width="734" height="310" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/846f037da1864f64be630dd9c1fd2d4f4b7.png" data-original="https://oscimg.oschina.net/oscnet/846f037da1864f64be630dd9c1fd2d4f4b7.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>再看一下实现方法:</p> <div class="cnblogs_code"> <pre><code>- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *<span style="color: #000000;">)request completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, <span style="color: #0000ff;">id</span> responseObject, NSError *<span style="color: #000000;">error))completionHandler { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler]; } </span>- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *<span style="color: #000000;">)request uploadProgress:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">uploadProgress)) uploadProgressBlock downloadProgress:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">downloadProgress)) downloadProgressBlock completionHandler:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, <span style="color: #0000ff;">id</span> _Nullable responseObject, NSError *<span style="color: #000000;"> _Nullable error))completionHandler { __block NSURLSessionDataTask </span>*dataTask =<span style="color: #000000;"> nil; url_session_manager_create_task_safely(</span>^<span style="color: #000000;">{ dataTask </span>=<span style="color: #000000;"> [self.session dataTaskWithRequest:request]; }); [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataTask; }<br /></span></code></pre> <pre><code><span style="color: #000000;"> </span></code></pre> </div> <p>创建数据服务,这里在使用会话对象 session 和入参 request 创建任务时,如果 NSFoundationVersionNumber 的值小于 <code>NSFoundationVersionNumber_iOS_8_0</code> 那么 dataTask 的创建会放在 <code>af_url_session_manager_creation_queue</code> 串行队列中同步执行,否则就由当前线程执行。接着,会调用这样的方法:</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)addDelegateForDataTask:(NSURLSessionDataTask *<span style="color: #000000;">)dataTask uploadProgress:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">uploadProgress)) uploadProgressBlock downloadProgress:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">downloadProgress)) downloadProgressBlock completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, <span style="color: #0000ff;">id</span> responseObject, NSError *<span style="color: #000000;">error))completionHandler{ AFURLSessionManagerTaskDelegate </span>*<span style="color: #0000ff;">delegate</span> =<span style="color: #000000;"> [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask]; </span><span style="color: #0000ff;">delegate</span>.manager =<span style="color: #000000;"> self; </span><span style="color: #0000ff;">delegate</span>.completionHandler =<span style="color: #000000;"> completionHandler; dataTask.taskDescription </span>=<span style="color: #000000;"> self.taskDescriptionForSessionTasks; [self setDelegate:</span><span style="color: #0000ff;">delegate</span><span style="color: #000000;"> forTask:dataTask]; </span><span style="color: #0000ff;">delegate</span>.uploadProgressBlock =<span style="color: #000000;"> uploadProgressBlock; </span><span style="color: #0000ff;">delegate</span>.downloadProgressBlock =<span style="color: #000000;"> downloadProgressBlock; }</span></code></pre> </div> <p>在这个方法中会创建一个AFURLSessionManagerTaskDelegate对象,设置其相关联的管理器,任务描述以及回调等苏醒,还会将当前会话注册为监听者,监听 task 任务发出的 AFNSURLSessionTaskDidResumeNotification 和 AFNSURLSessionTaskDidSuspendNotification 通知。当接收到该通知后,分别执行 taskDidResume: 和 taskDidSuspend: 方法,在这两个方法中又发出了 AFNetworkingTaskDidResumeNotification 和 AFNetworkingTaskDidSuspendNotification 通知。</p> <p>(4)</p> <div class="cnblogs_code"> <pre><code>- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *<span style="color: #000000;">)request completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, <span style="color: #0000ff;">id</span> responseObject, NSError *<span style="color: #000000;">error))completionHandler { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler]; }</span></code></pre> </div> <p>创建数据服务,这个方法其实是调用了上一个方法,只是uploadProgress和downloadProgress传nil而已</p> <p>(5)</p> <div class="cnblogs_code"> <pre><code>- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *<span style="color: #000000;">)request fromFile:(NSURL </span>*<span style="color: #000000;">)fileURL progress:(</span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">uploadProgress)) uploadProgressBlock completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, <span style="color: #0000ff;">id</span> responseObject, NSError *<span style="color: #000000;">error))completionHandler { __block NSURLSessionUploadTask </span>*uploadTask =<span style="color: #000000;"> nil; url_session_manager_create_task_safely(</span>^<span style="color: #000000;">{ uploadTask </span>=<span style="color: #000000;"> [self.session uploadTaskWithRequest:request fromFile:fileURL]; });</span> <span style="color: #0000ff;">if</span> (!uploadTask &amp;&amp; self.attemptsToRecreateUploadTasksForBackgroundSessions &amp;&amp;<span style="color: #000000;"> self.session.configuration.identifier) { </span><span style="color: #0000ff;">for</span> (NSUInteger attempts = <span style="color: #800080;">0</span>; !uploadTask &amp;&amp; attempts &lt; AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++<span style="color: #000000;">) { uploadTask </span>=<span style="color: #000000;"> [self.session uploadTaskWithRequest:request fromFile:fileURL]; } } [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> uploadTask; }</span></code></pre> </div> <p>此方法是创建文件上传任务,如果果后台会话对象创建文件上传任务失败时,会根据条件尝试重新创建,当然 AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask 为 5 ,所以只能尝试 5次。如果任务创建成功,则进而为任务创建一个 AFURLSessionManagerTaskDelegate 对象,作为任务的代理。请求报文的请求体数据即为根据参数 fileURL 获取的文件数据。</p> <p>(6)</p> <div class="cnblogs_code"> <pre><code>- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *<span style="color: #000000;">)request fromData:(NSData </span>*<span style="color: #000000;">)bodyData progress:(</span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">uploadProgress)) uploadProgressBlock completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, <span style="color: #0000ff;">id</span> responseObject, NSError *<span style="color: #000000;">error))completionHandler { __block NSURLSessionUploadTask </span>*uploadTask =<span style="color: #000000;"> nil; url_session_manager_create_task_safely(</span>^<span style="color: #000000;">{ uploadTask </span>=<span style="color: #000000;"> [self.session uploadTaskWithRequest:request fromData:bodyData]; }); [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> uploadTask; }</span></code></pre> </div> <p>此方法上传数据与上面上传文件类似,待上传的数据直接由参数 bodyData 给出。</p> <p>(7)</p> <div class="cnblogs_code"> <pre><code>- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *<span style="color: #000000;">)request progress:(</span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">downloadProgress)) downloadProgressBlock destination:(NSURL </span>* (^)(NSURL *targetPath, NSURLResponse *<span style="color: #000000;">response))destination completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, NSURL *filePath, NSError *<span style="color: #000000;">error))completionHandler { __block NSURLSessionDownloadTask </span>*downloadTask =<span style="color: #000000;"> nil; url_session_manager_create_task_safely(</span>^<span style="color: #000000;">{ downloadTask </span>=<span style="color: #000000;"> [self.session downloadTaskWithRequest:request]; }); [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> downloadTask; }</span></code></pre> </div> <p>创建下载任务:</p> <ul><li>request 创建任务时使用的请求报文头信息</li> <li>downloadProgressBlock 下载进度更新时调用的代码块,这个代码会在会话队列中调用,所以如果更新视图,需要自己在任务代码中指定主队列</li> <li>destination 任务下载结束后,该参数可以返回指定的文件保存地址,缓存数据被移动到该地址,targetPath 为下载的数据缓存地址</li> <li>completionHandler 下载任务结束后的回调</li> </ul><p>进一步调用下面方法</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)addDelegateForDownloadTask:(NSURLSessionDownloadTask *<span style="color: #000000;">)downloadTask progress:(</span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">downloadProgress)) downloadProgressBlock destination:(NSURL </span>* (^)(NSURL *targetPath, NSURLResponse *<span style="color: #000000;">response))destination completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, NSURL *filePath, NSError *<span style="color: #000000;">error))completionHandler { AFURLSessionManagerTaskDelegate </span>*<span style="color: #0000ff;">delegate</span> =<span style="color: #000000;"> [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask]; </span><span style="color: #0000ff;">delegate</span>.manager =<span style="color: #000000;"> self; </span><span style="color: #0000ff;">delegate</span>.completionHandler =<span style="color: #000000;"> completionHandler; </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (destination) { </span><span style="color: #0000ff;">delegate</span>.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *<span style="color: #000000;">location) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> destination(location, task.response); }; } downloadTask.taskDescription </span>=<span style="color: #000000;"> self.taskDescriptionForSessionTasks; [self setDelegate:</span><span style="color: #0000ff;">delegate</span><span style="color: #000000;"> forTask:downloadTask]; </span><span style="color: #0000ff;">delegate</span>.downloadProgressBlock =<span style="color: #000000;"> downloadProgressBlock; }</span></code></pre> </div> <p>(8)</p> <div class="cnblogs_code"> <pre><code>- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *<span style="color: #000000;">)resumeData progress:(NSProgress </span>* __autoreleasing *<span style="color: #000000;">)progress destination:(NSURL </span>* (^)(NSURL *targetPath, NSURLResponse *<span style="color: #000000;">response))destination completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLResponse *response, NSURL *filePath, NSError *<span style="color: #000000;">error))completionHandler { __block NSURLSessionDownloadTask </span>*downloadTask =<span style="color: #000000;"> nil; dispatch_sync(url_session_manager_creation_queue(), </span>^<span style="color: #000000;">{ downloadTask </span>=<span style="color: #000000;"> [self.session downloadTaskWithResumeData:resumeData]; }); [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> downloadTask; }</span></code></pre> </div> <p>创建重用数据的下载任务:使用已经下载的部分数据 resumeData 创建一个下载任务,继续进行下载。</p> <p>(9)</p> <div class="cnblogs_code"> <pre><code>- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;</code></pre> </div> <p>获取任务的数据上传进度</p> <p>(10)</p> <div class="cnblogs_code"> <pre><code>- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;</code></pre> </div> <p>获取任务的数据下载进度</p> <p> AFURLSessionManager 中实现的代理方法</p> <p>AFURLSessionManager 遵循 NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 协议,以处理网络请求过程中的数据。</p> <p>有些代理方法中所做的任务,完全由 AFURLSessionManager 的代码块属性决定。如果这些属性并没有设置,那么相应的代理方法就没必要响应。所以 AFURLSessionManager 中重写了 respondsToSelector: 过滤了一些不必响应的代理方法。</p> <span id="OSC_h4_1"></span> <h4>NSURLSessionDownloadDelegate</h4> <p>当下载任务结束后,调用该代理方法。</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)URLSession:(NSURLSession *<span style="color: #000000;">)session downloadTask:(NSURLSessionDownloadTask </span>*<span style="color: #000000;">)downloadTask didFinishDownloadingToURL:(NSURL </span>*<span style="color: #000000;">)location { </span><span style="color: #008000;">//</span><span style="color: #008000;">获取与任务相对应的代理对象</span> AFURLSessionManagerTaskDelegate *<span style="color: #0000ff;">delegate</span> =<span style="color: #000000;"> [self delegateForTask:downloadTask]; </span><span style="color: #008000;">//</span><span style="color: #008000;">如果设置了回调任务,先执行回调任务</span> <span style="color: #0000ff;">if</span><span style="color: #000000;"> (self.downloadTaskDidFinishDownloading) { </span><span style="color: #008000;">//</span><span style="color: #008000;">获取下载数据要保存的地址 </span> NSURL *fileURL =<span style="color: #000000;"> self.downloadTaskDidFinishDownloading(session, downloadTask, location); </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (fileURL) { </span><span style="color: #0000ff;">delegate</span>.downloadFileURL =<span style="color: #000000;"> fileURL; NSError </span>*error =<span style="color: #000000;"> nil; </span><span style="color: #008000;">//</span><span style="color: #008000;">移动下载的数据到指定地址</span> <span style="color: #0000ff;">if</span> (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&amp;<span style="color: #000000;">error]) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification </span><span style="color: #0000ff;">object</span><span style="color: #000000;">:downloadTask userInfo:error.userInfo]; } </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; } } </span><span style="color: #0000ff;">if</span> (<span style="color: #0000ff;">delegate</span><span style="color: #000000;">) { [</span><span style="color: #0000ff;">delegate</span><span style="color: #000000;"> URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; } }</span></code></pre> </div> <p>从上面的代码可知,如果会话管理器的 downloadTaskDidFinishDownloading 的代码块返回了地址,那么便不会去执行任务本身所对应的代理方法了,并且如果移动文件失败便会推送一个 AFURLSessionDownloadTaskDidFailToMoveFileNotification 通知。</p> <p>下面两个协议方法中,都是先执行任务所关联的代理对象的方法,再执行会话对象设置的 downloadTaskDidWriteData 或 downloadTaskDidResume 任务。</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)URLSession:(NSURLSession *<span style="color: #000000;">)session downloadTask:(NSURLSessionDownloadTask </span>*<span style="color: #000000;">)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; </span>- (<span style="color: #0000ff;">void</span>)URLSession:(NSURLSession *<span style="color: #000000;">)session downloadTask:(NSURLSessionDownloadTask </span>*<span style="color: #000000;">)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes;</span></code></pre> </div> <p>从这些代理方法中可知,设置 AFURLSessionManager 会话实例对象的代码块任务属性,那么这些回调任务对于每一个网络请求任务都是有效的,所以针对于单个特殊的任务回调操作,便不能放在会话管理器的属性中,而是要放在与任务相关联的 AFURLSessionManagerTaskDelegate 代理对象中。</p> <p>实际使用 AFURLSessionManager 的方法创建网络请求任务时,传递的回调任务,都是在与任务相关联的代理对象的方法中执行的。</p> <p>以上就是AFURLSessionManager的内容。下面讲解他的子类:AFHTTPSessionManager</p> <p>2&gt;AFHTTPSessionManager</p> <p><img alt="" width="709" height="67" class="b-lazy" data-src="https://oscimg.oschina.net/oscnet/1a0d47ddd57bdffc7477b26ea403b901740.png" data-original="https://oscimg.oschina.net/oscnet/1a0d47ddd57bdffc7477b26ea403b901740.png" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>发现AFHTTPSessionManager是继承AFURLSessionManager并遵守&lt;NSSecureCoding, NSCopying&gt;</p> <p>(1)</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (</span><span style="color: #0000ff;">readonly</span>, nonatomic, strong, nullable) NSURL *<span style="color: #000000;">baseURL; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Requests created with `requestWithMethod:URLString:parameters:` &amp; `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies. @warning `requestSerializer` must not be `nil`. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (nonatomic, strong) AFHTTPRequestSerializer </span>&lt;AFURLRequestSerialization&gt; *<span style="color: #000000;"> requestSerializer; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`. @warning `responseSerializer` must not be `nil`. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (nonatomic, strong) AFHTTPResponseSerializer </span>&lt;AFURLResponseSerialization&gt; *<span style="color: #000000;"> responseSerializer; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. A security policy configured with `AFSSLPinningModePublicKey` or `AFSSLPinningModeCertificate` can only be applied on a session manager initialized with a secure base URL (i.e. https). Applying a security policy with pinning enabled on an insecure session manager throws an `Invalid Security Policy` exception. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (nonatomic, strong) AFSecurityPolicy </span>*securityPolicy;</code></pre> </div> <p>暴露出的 baseUrl 属性是 readonly <br />@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;</p> <p>// 请求序列化 <br />@property (nonatomic, strong) AFHTTPRequestSerializer &lt;AFURLRequestSerialization&gt; * requestSerializer;</p> <p>// 响应序列化 <br />@property (nonatomic, strong) AFHTTPResponseSerializer &lt;AFURLResponseSerialization&gt; * responseSerializer;</p> <p>以及</p> <p>+ (instancetype)manager; <br />- (instancetype)initWithBaseURL:(nullable NSURL *)url; <br />- (instancetype)initWithBaseURL:(nullable NSURL *)url <br />sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration</p> <p>三个初始化方法,第一个是类方法,后两个是需要传参的实例方法。</p> <p>(2)请求方式</p> <p>GET 请求是向服务端发起请求数据,用来获取或查询资源信息 <br />POST 请求是向服务端发送数据的,用来更新资源信息,它可以改变数据的种类等资源 <br />PUT 请求和POST请求很像,都是发送数据的,但是PUT请求不能改变数据的种类等资源,它只能修改内容 <br />DELETE 请求就是用来删除某个资源的 <br />PATCH 请求和PUT请求一样,也是用来进行数据更新的,它是HTTP verb推荐用于更新的 <br />在实际开发过程中,我们还是使用【GET 和 POST】请求是最多的。</p> <p>其实如果看一下AFHTTPSessionManager,发现里面并没有需要讲什么的,关键还是AFURLSessionManager类。</p> <p>下面以GET请求为例:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">//</span><span style="color: #008000;">GET请求,调用下面那个方法</span> - (NSURLSessionDataTask *)GET:(NSString *<span style="color: #000000;">)URLString parameters:(</span><span style="color: #0000ff;">id</span><span style="color: #000000;">)parameters success:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionDataTask *task, <span style="color: #0000ff;">id</span><span style="color: #000000;"> responseObject))success failure:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionDataTask *task, NSError *<span style="color: #000000;">error))failure { </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> [self GET:URLString parameters:parameters progress:nil success:success failure:failure]; } </span><span style="color: #008000;">//</span><span style="color: #008000;">GET请求</span> - (NSURLSessionDataTask *)GET:(NSString *<span style="color: #000000;">)URLString parameters:(</span><span style="color: #0000ff;">id</span><span style="color: #000000;">)parameters progress:(</span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;"> _Nonnull))downloadProgress success:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionDataTask * _Nonnull, <span style="color: #0000ff;">id</span><span style="color: #000000;"> _Nullable))success failure:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionDataTask * _Nullable, NSError *<span style="color: #000000;"> _Nonnull))failure { </span><span style="color: #008000;">//</span><span style="color: #008000;">调用另一个方法构造GET请求</span> NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:<span style="color: #800000;">@"</span><span style="color: #800000;">GET</span><span style="color: #800000;">"</span><span style="color: #000000;"> URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; </span><span style="color: #008000;">/*</span><span style="color: #008000;"> 启动任务 使用AFHTTPSessionManager创建的任务默认都帮你启动了,所以不需要手动调用resume方法了 上一篇中讲解的AFURLSessionManager默认没有启动,所以获取任务后要手动启动 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> [dataTask resume]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataTask; }</span></code></pre> </div> <div class="cnblogs_code"> <pre><code>- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *<span style="color: #000000;">)method URLString:(NSString </span>*<span style="color: #000000;">)URLString parameters:(</span><span style="color: #0000ff;">id</span><span style="color: #000000;">)parameters uploadProgress:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">uploadProgress)) uploadProgress downloadProgress:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSProgress *<span style="color: #000000;">downloadProgress)) downloadProgress success:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionDataTask *, <span style="color: #0000ff;">id</span><span style="color: #000000;">))success failure:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionDataTask *, NSError *<span style="color: #000000;">))failure { NSError </span>*serializationError =<span style="color: #000000;"> nil; NSMutableURLRequest </span>*request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&amp;<span style="color: #000000;">serializationError]; </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (serializationError) { </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (failure) { dispatch_async(self.completionQueue </span>?: dispatch_get_main_queue(), ^<span style="color: #000000;">{ failure(nil, serializationError); }); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> nil; } __block NSURLSessionDataTask </span>*dataTask =<span style="color: #000000;"> nil; </span><span style="color: #008000;">//</span><span style="color: #008000;">利用AFURLSessionManager方法</span> dataTask =<span style="color: #000000;"> [self dataTaskWithRequest:request uploadProgress:uploadProgress downloadProgress:downloadProgress completionHandler:</span>^(NSURLResponse * __unused response, <span style="color: #0000ff;">id</span> responseObject, NSError *<span style="color: #000000;">error) { </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (error) { </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (failure) { failure(dataTask, error); } } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (success) { success(dataTask, responseObject); } } }]; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataTask; }</span></code></pre> </div> <p><strong>2.AFNetworkReachabilityManager</strong></p> <p> AFNetworkReachabilityManager对象用于监听设备当前连接网络的状态。</p> <p>AFNetworkReachabilityManager提供了4种创建方法:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* Returns the shared network reachability manager. </span><span style="color: #008000;">*/</span> +<span style="color: #000000;"> (instancetype)sharedManager; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Creates and returns a network reachability manager with the default socket address. @return An initialized network reachability manager, actively monitoring the default socket address. </span><span style="color: #008000;">*/</span> +<span style="color: #000000;"> (instancetype)manager; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Creates and returns a network reachability manager for the specified domain. @param domain The domain used to evaluate network reachability. @return An initialized network reachability manager, actively monitoring the specified domain. </span><span style="color: #008000;">*/</span> + (instancetype)managerForDomain:(NSString *<span style="color: #000000;">)domain; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Creates and returns a network reachability manager for the socket address. @param address The socket address (`sockaddr_in6`) used to evaluate network reachability. @return An initialized network reachability manager, actively monitoring the specified socket address. </span><span style="color: #008000;">*/</span> + (instancetype)managerForAddress:(<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">void</span> *)address;</code></pre> </div> <div> <div> + ( <span>instancetype)sharedManager; <span>//创建单例对象</span></span> </div> <div> <span><span>+ (<span>instancetype)manager; <span>//创建实例对象</span></span></span></span> </div> <div> <span><span><span><span>+ (<span>instancetype)managerForDomain:(<span>NSString*)domain; <span>//根据地址名创建实例对象</span></span></span></span></span></span></span> </div> <div> <span><span><span><span><span><span><span>+ (<span>instancetype)managerForAddress:(<span>const<span>void*)address; <span>//根据sockaddr创建实例对象</span></span></span></span></span></span></span></span></span></span></span> </div> <br />(1)+ ( <span>instancetype)sharedManager; <span>//创建单例对象</span></span> <br /><br /></div> <div> 代码如下:创建单例对象: </div> <div> <div class="cnblogs_code"> <pre><code>+<span style="color: #000000;"> (instancetype)shareManager{ </span><span style="color: #0000ff;">static</span> AFNetworkReachabilityManager *_shareManager =<span style="color: #000000;"> nil; </span><span style="color: #0000ff;">static</span><span style="color: #000000;"> dispatch_once_t onceToken; dispatch_once(</span>&amp;onceToken,^<span style="color: #000000;">{ _shareManager </span>=<span style="color: #000000;"> [self manager]; ) ; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> _shareManager; } </span></code></pre> </div> <p>sharedManager调用manager方法创建一个AFNetworkReachabilityManager对象,代码如下:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">//</span><span style="color: #008000;">其中sockaddr_in6和sockaddr_in是描述网络套接字的结构体,包含协议族类型、端口、ip地址等信息,然后调用managerForAddress:方法创建,</span> +<span style="color: #000000;"> (instancetype)manager { </span><span style="color: #0000ff;">#if</span> (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) &amp;&amp; __IPHONE_OS_VERSION_MIN_REQUIRED &gt;= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) &amp;&amp; __MAC_OS_X_VERSION_MIN_REQUIRED &gt;= 101100) <span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr_in6 address; bzero(</span>&amp;address, <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address)); address.sin6_len </span>= <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address); address.sin6_family </span>=<span style="color: #000000;"> AF_INET6; </span><span style="color: #0000ff;">#else</span> <span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr_in address; bzero(</span>&amp;address, <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address)); address.sin_len </span>= <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address); address.sin_family </span>=<span style="color: #000000;"> AF_INET; </span><span style="color: #0000ff;">#endif</span> <span style="color: #0000ff;">return</span> [self managerForAddress:&amp;<span style="color: #000000;">address]; }</span></code></pre> </div> <p>其他的两种方法: 一个<span>根据地址名创建实例对象,另一个根据sockaddr创建实例对象,可以直接看源代码,用的并不是特别多。</span></p> <p><span>(2)通过枚举值查看网络状态</span></p> <div class="cnblogs_code"> <pre><code><span style="color: #000000;">typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown </span>= -<span style="color: #800080;">1</span>,<span style="color: #008000;">//</span><span style="color: #008000;">未知状态</span> AFNetworkReachabilityStatusNotReachable = <span style="color: #800080;">0</span>, <span style="color: #008000;">//</span><span style="color: #008000;">未连接</span> AFNetworkReachabilityStatusReachableViaWWAN = <span style="color: #800080;">1</span>, <span style="color: #008000;">//</span><span style="color: #008000;">蜂窝移动网络(2G/3G/4G)</span> AFNetworkReachabilityStatusReachableViaWiFi = <span style="color: #800080;">2</span>, <span style="color: #008000;">//</span><span style="color: #008000;">wifi网络</span> };</code></pre> </div> <p>(3)网络监听</p> <p>AFNetworkReachabilityManager通过startMonitoring方法和stopMonitoring开始并停止监听当前设备连接的网络状态。</p> <p>1&gt;startMonitoring方法</p> <p>该方法主要通过SystemConfiguration框架提供的API将networkReachability让对象加入runloop中,开始工作,并且绑定监听的回调函数处理状态改变。</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)startMonitoring { [self stopMonitoring]; </span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">self.networkReachability) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; } __weak __typeof(self)weakSelf </span>=<span style="color: #000000;"> self; AFNetworkReachabilityStatusBlock callback </span>= ^<span style="color: #000000;">(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf </span>=<span style="color: #000000;"> weakSelf; strongSelf.networkReachabilityStatus </span>=<span style="color: #000000;"> status; </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; SCNetworkReachabilityContext context </span>= {<span style="color: #800080;">0</span>, (__bridge <span style="color: #0000ff;">void</span> *<span style="color: #000000;">)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, </span>&amp;<span style="color: #000000;">context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, </span><span style="color: #800080;">0</span>),^<span style="color: #000000;">{ SCNetworkReachabilityFlags flags; </span><span style="color: #0000ff;">if</span> (SCNetworkReachabilityGetFlags(self.networkReachability, &amp;<span style="color: #000000;">flags)) { AFPostReachabilityStatusChange(flags, callback); } }); }</span></code></pre> </div> <p>根据查看源码发现:该方法首先停止之前的监听,然后调用SCNetworkReachabilitySetCallback方法来设置networkReachability的回调函数AFNetworkReachabilityCallback和上下文context对象。</p> <p>在回调方法中有一个方法比较重要:</p> <div class="cnblogs_code"> <pre><code><span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) { AFNetworkReachabilityStatus status </span>= AFNetworkReachabilityStatusForFlags(flags); <span style="color: #008000;">//</span><span style="color: #008000;">根据flags获取当前网络连接状态status</span> dispatch_async(dispatch_get_main_queue(), ^<span style="color: #000000;">{ </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (block) { block(status); </span><span style="color: #008000;">//</span><span style="color: #008000;">block是context中的info指针,调用info将status传递给外界</span> <span style="color: #000000;"> } </span><span style="color: #008000;">//</span><span style="color: #008000;">status作为通知的值,发通知抛给外界</span> NSNotificationCenter *notificationCenter =<span style="color: #000000;"> [NSNotificationCenter defaultCenter]; NSDictionary </span>*userInfo =<span style="color: #000000;"> @{ AFNetworkingReachabilityNotificationStatusItem: @(status) }; [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification </span><span style="color: #0000ff;">object</span><span style="color: #000000;">:nil userInfo:userInfo]; }); }</span></code></pre> </div> <p>此方法首先根据系统回调的AFNetworkReachabilityStatusForFlags方法以及falgs参数,获取网络连接状态,然后进入block,将status抛出外界,同时抛一个通知将status抛给外界,当网络状态发生改变,会同事用这两种方式传递给外界。</p> <p>AFNetworkReachabilityStatusForFlags方法是核心方法,负责根据flag的状态值,转化为相应的枚举值AFNetworkReachabilityStatus。</p> <p>2&gt;stopMonitoring方法</p> <p>该方法通过监听的方法是让networkReachability对象从runloop中注销。</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)stopMonitoring { </span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">self.networkReachability) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; } SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); }</span></code></pre> </div> <p>(3)网络属性状态</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* The current network reachability status. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (</span><span style="color: #0000ff;">readonly</span><span style="color: #000000;">, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Whether or not the network is currently reachable. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (</span><span style="color: #0000ff;">readonly</span>, nonatomic, assign, getter =<span style="color: #000000;"> isReachable) BOOL reachable; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Whether or not the network is currently reachable via WWAN. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (</span><span style="color: #0000ff;">readonly</span>, nonatomic, assign, getter =<span style="color: #000000;"> isReachableViaWWAN) BOOL reachableViaWWAN; </span><span style="color: #008000;">/*</span><span style="color: #008000;">* Whether or not the network is currently reachable via WiFi. </span><span style="color: #008000;">*/</span><span style="color: #000000;"> @property (</span><span style="color: #0000ff;">readonly</span>, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;</code></pre> </div> <p>通过上面可发现:外界可通过isReachable方法,isReachableViaWWAN方法以及isReachableViaWiFi等获取当前的网络状态。通过keyPathsForValuesAffectingValueForKey方法设置属性值的依赖关系,利用KVO方式监听属性的变化。</p> <p>下面是简单的一个网络监听的小demo</p> <p>1)[[AFNetworkReachabilityManagersharedManager] startMonitoring];</p> <p>2)判断网络连接状态。</p> <div class="cnblogs_code"> <pre><code>[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^<span style="color: #000000;">(AFNetworkReachabilityStatus status) { </span><span style="color: #0000ff;">switch</span><span style="color: #000000;"> (status) { </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> AFNetworkReachabilityStatusNotReachable:{ NSLog(</span><span style="color: #800000;">@"</span><span style="color: #800000;">无网络</span><span style="color: #800000;">"</span><span style="color: #000000;">); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> AFNetworkReachabilityStatusReachableViaWiFi:{ NSLog(</span><span style="color: #800000;">@"</span><span style="color: #800000;">WiFi网络</span><span style="color: #800000;">"</span><span style="color: #000000;">); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> AFNetworkReachabilityStatusReachableViaWWAN:{ NSLog(</span><span style="color: #800000;">@"</span><span style="color: #800000;">3G网络</span><span style="color: #800000;">"</span><span style="color: #000000;">); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">default</span><span style="color: #000000;">: </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; } }]; </span></code></pre> </div> <p><strong>3.AFSecurityPolicy</strong></p> <p>首先看一下AFSecurityPolicy暴露出来的方法。</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">//</span><span style="color: #008000;">https验证模式 默认是无,还有证书匹配和公钥匹配</span> @property (<span style="color: #0000ff;">readonly</span><span style="color: #000000;">, nonatomic, assign) AFSSLPinningMode SSLPinningMode; </span><span style="color: #008000;">//</span><span style="color: #008000;">可以去匹配服务端证书验证的证书 当我们不主动设置pinningMode的时候不会主动该字段是空的,如果主动设置,会调用默认读取certificatesInBundle .cer的证书进行赋值 源码里面有</span> @property (nonatomic, strong, nullable) NSSet &lt;NSData *&gt; *<span style="color: #000000;">pinnedCertificates; </span><span style="color: #008000;">//</span><span style="color: #008000;">allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是需要验证自建证书,需要设置为YES 一般测试的时候YES,https开启就弄为NO</span> <span style="color: #000000;">@property (nonatomic, assign) BOOL allowInvalidCertificates; </span><span style="color: #008000;">//</span><span style="color: #008000;">validatesDomainName 是否需要验证域名,默认为YES; </span><span style="color: #008000;">//</span><span style="color: #008000;">假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。 </span><span style="color: #008000;">//</span><span style="color: #008000;">置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。 </span><span style="color: #008000;">//</span><span style="color: #008000;">如置为NO,建议自己添加对应域名的校验逻辑。</span> @property (nonatomic, assign) BOOL validatesDomainName;</code></pre> </div> <p>AFNetworking认证核心代码</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)URLSession:(NSURLSession *<span style="color: #000000;">)session didReceiveChallenge:(NSURLAuthenticationChallenge </span>*<span style="color: #000000;">)challenge completionHandler:(</span><span style="color: #0000ff;">void</span> (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *<span style="color: #000000;">credential))completionHandler { </span><span style="color: #008000;">//</span><span style="color: #008000;">挑战处理类型为 默认</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理 NSURLSessionAuthChallengeUseCredential:使用指定的证书 NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> NSURLSessionAuthChallengeDisposition disposition </span>=<span style="color: #000000;"> NSURLSessionAuthChallengePerformDefaultHandling; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 服务器挑战的证书</span> __block NSURLCredential *credential =<span style="color: #000000;"> nil; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 这个Block是提供给用户自定义证书挑战方式的,比如是否需要自定义</span> <span style="color: #0000ff;">if</span><span style="color: #000000;"> (self.sessionDidReceiveAuthenticationChallenge) { disposition </span>= self.sessionDidReceiveAuthenticationChallenge(session, challenge, &amp;<span style="color: #000000;">credential); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #008000;">//</span><span style="color: #008000;"> NSURLAuthenticationMethodServerTrust 单向认证关系 也就是说服务器端需要客户端返回一个根据认证挑战的保护空间提供的信任产生的挑战证书</span> <span style="color: #0000ff;">if</span><span style="color: #000000;"> ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战</span> <span style="color: #0000ff;">if</span><span style="color: #000000;"> ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)</span> credential =<span style="color: #000000;"> [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (credential) { disposition </span>=<span style="color: #000000;"> NSURLSessionAuthChallengeUseCredential; } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { disposition </span>=<span style="color: #000000;"> NSURLSessionAuthChallengePerformDefaultHandling; } } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { disposition </span>=<span style="color: #000000;"> NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { disposition </span>=<span style="color: #000000;"> NSURLSessionAuthChallengePerformDefaultHandling; } } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 完成挑战</span> <span style="color: #0000ff;">if</span><span style="color: #000000;"> (completionHandler) { completionHandler(disposition, credential); } }</span></code></pre> </div> <p><strong>4.AFURLRequestSerialization</strong></p> <p>AFURLRequestSerialization涉及两个模块:AFURLRequestSerialization和AFURLResponseSerialization</p> <div> 前者主要是修改请求头部,提供了接口设置HTTP头部字段,后者是处理响应的模块,将请求放回的数据解析成相对应的格式。 </div> <div> <strong>5.UIKit+AFNetworking</strong> </div> <div> 如果需要使用 AFNetworking 的 UIKit 扩展时可直接在 Prefix.pch 文件中引入,或者在工程的相关文件中引入。 </div> <div> (1)AFAutoPurgingImageCache:用于缓存图片的类,通过identifier来添加和搜索UIImage。 </div> <div>   </div> <div> 协议中添加图片 </div> <div> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span>)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;</code></pre> </div> <p>协议中删除图片</p> <div class="cnblogs_code"> <pre><code>- (BOOL)removeImageWithIdentifier:(NSString *)identifier;</code></pre> </div> <p>协议中删除所有图片</p> <div class="cnblogs_code"> <pre><code>- (BOOL)removeAllImages;</code></pre> </div> <p>通过identifier获取图片的方法:</p> </div> <div> <div class="cnblogs_code"> <pre><code>- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;</code></pre> </div> <p>(2)UIButton+AFNetworking</p> <p>UIButton跟图片相关的属性大概有两个,<code>Image</code>和<code>BackgroundImage</code>.所以这个分类就是赋予他们异步加载图片的能力。</p> <p>核心方法有以下:</p> <div class="cnblogs_code"> <pre><code>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)setImageForState:(UIControlState)state withURL:(NSURL </span>*<span style="color: #000000;">)url; </span>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)setImageForState:(UIControlState)state withURL:(NSURL </span>*<span style="color: #000000;">)url placeholderImage:(nullable UIImage </span>*<span style="color: #000000;">)placeholderImage; </span>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)setImageForState:(UIControlState)state withURLRequest:(NSURLRequest </span>*<span style="color: #000000;">)urlRequest placeholderImage:(nullable UIImage </span>*<span style="color: #000000;">)placeholderImage success:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *<span style="color: #000000;">image))success failure:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *<span style="color: #000000;">error))failure; </span>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)setBackgroundImageForState:(UIControlState)state withURL:(NSURL </span>*<span style="color: #000000;">)url; </span>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)setBackgroundImageForState:(UIControlState)state withURL:(NSURL </span>*<span style="color: #000000;">)url placeholderImage:(nullable UIImage </span>*<span style="color: #000000;">)placeholderImage; </span>- (<span style="color: #0000ff;">void</span><span style="color: #000000;">)setBackgroundImageForState:(UIControlState)state withURLRequest:(NSURLRequest </span>*<span style="color: #000000;">)urlRequest placeholderImage:(nullable UIImage </span>*<span style="color: #000000;">)placeholderImage success:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *<span style="color: #000000;">image))success failure:(nullable </span><span style="color: #0000ff;">void</span> (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;</code></pre> </div> <p>(3)UIImageView+AFNetworking</p> <p>UIImageView+AFNetworking 是AFNetworking中一个实现图片异步加载的类, 它是为系统中的UIImageView类添加的类目(Category), 这个类目中的方法为远程异步加载图片功能提供支持.</p> <p>思路:</p> <ol><li>导入AFNetworking工程文件.</li> <li>引入UIKit+AFNetworking/UIImageView+AFNetworking.h头文件.</li> <li>下载图片</li> <li>取消图片下载(可选)</li> </ol><p>实现异步加载:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;"> 创建NSURL对象 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> NSURL </span>*urlStr = [NSURL URLWithString:<span style="color: #800000;">@"</span><span style="color: #800000;">http://img2.cache.netease.com/3g/2015/9/18/20150918195439dc844.jpg</span><span style="color: #800000;">"</span><span style="color: #000000;">]; </span><span style="color: #008000;">/*</span><span style="color: #008000;"> 创建请求对象 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> NSURLRequest </span>*request =<span style="color: #000000;"> [NSURLRequest requestWithURL:urlStr]; </span><span style="color: #008000;">/*</span><span style="color: #008000;"> 创建一个imageView对象 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> UIImageView </span>*imageView = [[UIImageView alloc] initWithFrame:CGRectMake(<span style="color: #800080;">100</span>, <span style="color: #800080;">100</span>, <span style="color: #800080;">100</span>, <span style="color: #800080;">100</span>)];</code></pre> </div> <p>方法一:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* * 方法一: * 直接使用url字符串获取照片 * @param urlStr : NSURL对象 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> [imageView setImageWithURL:urlStr];</span></code></pre> </div> <p>方法二:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* * 方法二: * 直接使用URL(例如:</span><span style="color: #008000; text-decoration: underline;">http://img2.cache.netease.com/3g/2015/9/18/20150918195439dc844.jpg</span><span style="color: #008000;">)异步加载图片, 照片出现之前添加一个占位的背景照片 * @param urlStr : NSURL对象 * @param placeholderImage : UIImage图片对象 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> [imageView setImageWithURL:urlStr placeholderImage:[UIImage imageNamed:</span><span style="color: #800000;">@"</span><span style="color: #800000;">discuss</span><span style="color: #800000;">"</span>]];</code></pre> </div> <p>方法三:</p> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* * 方法三: * 使用URLRequest对象获取照片, 照片在请求后的block块中返回一个UIImage参数用于赋值或其他作. * 参数中得两个block块:分别在请求成功时执行 success:^{}, 请求失败时执行 failure:^{}。 * @param request : NSURLRequest请求对象 * @param placeholderImage : UIImage图片对象 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> [imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:</span><span style="color: #800000;">@"</span><span style="color: #800000;">discuss</span><span style="color: #800000;">"</span>] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *<span style="color: #000000;">image) { </span><span style="color: #008000;">/*</span><span style="color: #008000;">* * 返回值参数 * @param request : NSURLRequest 请求对象 * @param response : NSHTTPURLResponse HTTP响应者对象(包含请求对象和图片信息) * @param image : 加载成功图片UIImage对象(自带的size尺寸用于自适应高度计算) </span><span style="color: #008000;">*/</span><span style="color: #000000;"> [imageView setImage:image]; } failure:</span>^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *<span style="color: #000000;">error) { </span><span style="color: #008000;">/*</span><span style="color: #008000;">*&lt; @param error : NSError对象 加载错误的原因代号(例如 : 403) </span><span style="color: #008000;">*/</span><span style="color: #000000;"> }];</span></code></pre> </div> <div class="cnblogs_code"> <pre><code><span style="color: #008000;">/*</span><span style="color: #008000;">* * 取消图片请求操作 * 可以直接取消本段代码块中在调用这个方法之前的所有异步加载操作(适当的地方慎重使用) </span><span style="color: #008000;">*/</span><span style="color: #000000;"> [imageView cancelImageRequestOperation];</span></code></pre> </div> <p>当然还有其他一些的扩展协议,但使用并不是很多,且看源码不是很难。上面就是这两天看AFNetworking源码的一些自己感悟:因为AFNetworking主要是AFURLSessionManager、AFHTTPSessionManager,自己花7成都在上面,希望对大家对AFNetworking的理解有所加深,欢迎指正。</p> <p> </p> <p>下一篇,我将结合自己的项目对AFNetworking的使用进行封装,对中小型企业APP的网络封装可以直接使用,谢谢大家!!!</p> </div> </div> <div class="alert alert-success" role="alert"><p>来源:<code>oschina</code></p><p>链接:<code>https://my.oschina.net/u/4360351/blog/3284893</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/delegate" hreflang="zh-hans">delegate</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> <div class="field--item"><a href="/tag/sessionmanager" hreflang="zh-hans">SessionManager</a></div> <div class="field--item"><a href="/tag/uikit" hreflang="zh-hans">uikit</a></div> </div> </div> Fri, 24 Apr 2020 09:47:45 +0000 。_饼干妹妹 3591059 at https://www.e-learn.cn iOS获取手机当前的网络状态 https://www.e-learn.cn/topic/3536352 <span>iOS获取手机当前的网络状态</span> <span><span lang="" about="/user/70" typeof="schema:Person" property="schema:name" datatype="">蹲街弑〆低调</span></span> <span>2020-03-29 07:11:21</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <p>获取iOS网络状态,目前有两个办法。</p> <p>1.通过监听手机状态栏的信息。</p> <p>2.通过使用官方提供的类Reachability。</p> <p> </p> <p>一、通过手机监听手机状态栏的信息</p> <p>好处:</p> <p>1.可以通过苹果的审核上架AppStore。</p> <p>2.代码量少,简单易懂。</p> <p>3.可以区分网络类型,精确到2G,3G,4G。</p> <p> </p> <p>缺点:必须保证在使用该方法的过程中,手机状态栏statusBar没有隐藏。</p> <p> </p> <p>代码如下:</p> <pre>- (NSString *)networkingStatesFromStatusBar { UIApplication *app = [UIApplication sharedApplication]; NSArray *children = [[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews]; int type = 0; for (id child in children) { if ([child isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) { type = [[child valueForKeyPath:@"dataNetworkType"] intValue]; } } NSString *networkStateString = @"wifi"; switch (type) { case 0: networkStateString = @"notReachable"; break; case 1: networkStateString = @"2G"; break; case 2: networkStateString = @"3G"; break; case 3: networkStateString = @"4G"; break; case 4: stateString = @"LTE"; break; case 5: networkStateString = @"wifi"; break; default: break; } return networkStateString; }</pre> <p> </p> <p>二、通过通过使用官方提供的类Reachability</p> <p>好处:官方提供的权威方法。无需依赖状态栏。</p> <p>缺点:只有三种网络状态</p> <pre>typedef NS_ENUM(NSInteger, NetworkStatus) { // Apple NetworkStatus Compatible Names. NotReachable = 0, ReachableViaWiFi = 2, ReachableViaWWAN = 1 };</pre> <p>使用:</p> <p>1.下载并导入Reachability,链接:<a href="https://github.com/tonymillion/Reachability" target="_blank" rel="nofollow">https://github.com/tonymillion/Reachability</a></p> <p>2.导入SystemConfiguration.framework框架</p> <p>3.代码实现:</p> <p>Reachability提供了两种实现方法:</p> <p>1.使用Block回调,代码如下</p> <pre> /** 网络状态对象*/ Reachability *reach= [Reachability reachabilityWithHostName:@"www.apple.com"]; //使用block回调实现 reach.reachableBlock = ^(Reachability *reach) { dispatch_async(dispatch_get_main_queue(), ^{ NetworkStatus netStatus = [reach currentReachabilityStatus]; switch (netStatus) { case NotReachable: NSLog(@"未检测到网络"); break; case ReachableViaWiFi: NSLog(@"wifi"); break; case ReachableViaWWAN: NSLog(@"蜂窝网络"); break; default: break; } }); }; reach.unreachableBlock = ^(Reachability *reach) { NSLog(@"UNREACHBLE!"); }; [reach startNotifier];</pre> <p> </p> <p>2.使用通知,代码如下</p> <pre> /** 网络状态对象*/ Reachability *reach = [Reachability reachabilityWithHostName:@"www.apple.com"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil]; [reach startNotifier];</pre> <p>监听方法的实现</p> <pre>-(void)reachabilityChanged:(NSNotification *)notification { Reachability *curReach = [notification object]; NSParameterAssert([curReach isKindOfClass:[Reachability class]]); NetworkStatus netStatus = [curReach currentReachabilityStatus]; switch (netStatus) { case NotReachable: NSLog(@"未检测到网络"); break; case ReachableViaWiFi: NSLog(@"wifi"); break; case ReachableViaWWAN: NSLog(@"蜂窝网络"); break; default: break; } }</pre> <p>最后不要忘记移除通知。</p> <pre>- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil]; }</pre> <p> </p> <p> </p> <p> </p> <p> </p> <p class="p1"><span class="s1"> </span></p> <p> </p> <p> </p> <p> </p> <p style="margin-left: 30px;"> </p> <div class="alert alert-success" role="alert"><p>来源:<code>https://www.cnblogs.com/hd1992/p/5018814.html</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/nslog" hreflang="zh-hans">nslog</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> </div> </div> Sat, 28 Mar 2020 23:11:21 +0000 蹲街弑〆低调 3536352 at https://www.e-learn.cn iOS: How to test Internet connection in the most easy way, without freezing the app (without Reachability)? https://www.e-learn.cn/topic/3519730 <span>iOS: How to test Internet connection in the most easy way, without freezing the app (without Reachability)?</span> <span><span lang="" about="/user/45" typeof="schema:Person" property="schema:name" datatype="">妖精的绣舞</span></span> <span>2020-03-22 06:37:53</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"><h3>问题</h3><br /><p>In my code I used to use three ways for checking Internet, but there is limits to them:</p> <p>1/ Reachability method:</p> <pre><code>- (BOOL)isInternetOk { Reachability *curReach = [Reachability reachabilityWithHostName:@"apple.com"]; NetworkStatus netStatus = [curReach currentReachabilityStatus]; if (netStatus != NotReachable) //if internet connexion ok { return YES; } else { return NO; } } </code></pre> <p>Limit: It works in the most case, but my problem is that if I connect an antenna Wi-Fi without no internet on it, it says that the connection is okay, and it is not true. It is not a good solution, I need to check the status code that it seems not available on Reachability.</p> <p>2/sendSynchronousRequest:</p> <pre><code>- (BOOL)isInternetOk2 { NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init]; NSURL* URL = [NSURL URLWithString:@"https://www.google.com"]; NSError *error = nil; [request setURL:URL]; [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [request setTimeoutInterval:15]; NSData* response2 = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&amp;error]; if (error) { return NO; } else { return YES; } } </code></pre> <p>Limit: It works too, but my problem is that if there is a time out, that can happen at every moment, it freezes the app during too much time. If I put it in a thread, it seems that when I do a request in a dispatch_async, the response is not taking in account.</p> <p>3/sendAsynchronousRequest:</p> <pre><code> NSOperationQueue *myQueue = [[NSOperationQueue alloc] init]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.google.com"]]; request.timeoutInterval = 10; [NSURLConnection sendAsynchronousRequest:request queue:myQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; NSLog(@"response status code: %ld, error status : %@", (long)[httpResponse statusCode], error.description); if ((long)[httpResponse statusCode] &gt;= 200 &amp;&amp; (long)[httpResponse statusCode]&lt; 400) { // do stuff NSLog(@"Connected!"); } else { NSLog(@"Not connected!"); } }]; </code></pre> <p>Limit: I think it is the better way to do, but my problem is that I have to write that every where in my code, which will be a polution. I wonder if there is a less heavy way to do it.</p> <p>What do you think about it? Is there another way easier to check if internet is working without freezing the app?</p> <p>Thanks in advance.</p> <br /><h3>回答1:</h3><br /><p>Nayem is right - you should wrap the third option (async network check) in a class method like this:</p> <pre><code>+ (void)checkInternetConnectivityWithSuccessCompletion:(void (^)(void))completion { NSOperationQueue *myQueue = [[NSOperationQueue alloc] init]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.google.com"]]; request.timeoutInterval = 10; [NSURLConnection sendAsynchronousRequest:request queue:myQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; NSLog(@"response status code: %ld, error status : %@", (long)[httpResponse statusCode], error.description); if ((long)[httpResponse statusCode] &gt;= 200 &amp;&amp; (long)[httpResponse statusCode]&lt; 400) { // do stuff NSLog(@"Connected!"); completion(); } else { NSLog(@"Not connected!"); } }]; } </code></pre> <p>And then call the method like this:</p> <pre><code>[YourClass checkInternetConnectivityWithSuccessCompletion:^{ // your internet is working - add code here }]; </code></pre> <br /><br /><br /><h3>回答2:</h3><br /><p>Another option with</p> <pre><code>#import &lt;SystemConfiguration/SCNetworkReachability.h&gt; </code></pre> <p>.....</p> <pre><code>+(bool)isNetworkAvailable { SCNetworkReachabilityFlags flags; SCNetworkReachabilityRef address; address = SCNetworkReachabilityCreateWithName(NULL, "www.apple.com"); Boolean success = SCNetworkReachabilityGetFlags(address, &amp;flags); CFRelease(address); bool canReach = success &amp;&amp; !(flags &amp; kSCNetworkReachabilityFlagsConnectionRequired) &amp;&amp; (flags &amp; kSCNetworkReachabilityFlagsReachable); return canReach; } </code></pre> <br /><br /><p>来源:<code>https://stackoverflow.com/questions/48341595/ios-how-to-test-internet-connection-in-the-most-easy-way-without-freezing-the</code></p></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/ios" hreflang="zh-hans">ios</a></div> <div class="field--item"><a href="/tag/objective-c" hreflang="zh-hans">objective-c</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> <div class="field--item"><a href="/tag/internet-connection" hreflang="zh-hans">internet-connection</a></div> <div class="field--item"><a href="/tag/sendasynchronousrequest" hreflang="zh-hans">sendasynchronousrequest</a></div> </div> </div> Sat, 21 Mar 2020 22:37:53 +0000 妖精的绣舞 3519730 at https://www.e-learn.cn 可达性算法 https://www.e-learn.cn/topic/3429845 <span>可达性算法</span> <span><span lang="" about="/user/174" typeof="schema:Person" property="schema:name" datatype="">旧街凉风</span></span> <span>2020-02-26 13:55:53</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field--item"> <span id="OSC_h1_1"></span> <h1>一、可达性分析算法</h1> <p>在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。</p> <p><img class="b-lazy" data-src="" data-original="" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /></p> <p>可达性分析算法判断对象是否可以回收</p> <p><br /> 如上图所示,object1~object4对GC Root都是可达的,说明不可被回收,object5和object6对GC Root节点不可达,说明其可以被回收。<br /><strong>在Java中,可作为GC Root的对象包括以下几种:</strong></p> <ul><li>虚拟机栈(栈帧中的本地变量表)中引用的对象</li> <li>方法区中类静态属性引用的对象</li> <li>方法区中常量引用的对象</li> <li>本地方法栈中JNI(即一般说的Native方法)引用的对象</li> </ul><span id="OSC_h1_2"></span> <h1>二、finalize()方法最终判定对象是否存活</h1> <p>即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。<br /><strong>标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。</strong><br /><strong>1. 第一次标记并进行一次筛选。</strong><br /> 筛选的条件是此对象是否有必要执行finalize()方法。<br /> 当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。<br /><strong>2. 第二次标记</strong><br /> 如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。<br /> Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。</p> <span id="OSC_h1_3"></span> <h1>三、Java引用</h1> <p>从可达性算法中可以看出,判断对象是否可达时,与“引用”有关。那么什么情况下可以说一个对象被引用,引用到底代表什么?<br /> 在JDK1.2之后,Java对引用的概念进行了扩充,可以将引用分为以下四类:</p> <ul><li>强引用(Strong Reference)</li> <li>软引用(Soft Reference)</li> <li>弱引用(Weak Reference)</li> <li>虚引用(Phantom Reference)</li> </ul><p>这四种引用从上到下,依次减弱</p> <span id="OSC_h3_4"></span> <h3>3.1 强引用</h3> <p>强引用就是指在程序代码中普遍存在的,类似<code>Object obj = new Object()</code>这类似的引用,只要强引用在,垃圾搜集器永远不会搜集被引用的对象。也就是说,宁愿出现内存溢出,也不会回收这些对象。</p> <span id="OSC_h3_5"></span> <h3>3.2 软引用</h3> <p>软引用是用来描述一些有用但并不是必需的对象,在Java中用<code>java.lang.ref.SoftReference</code>类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。</p> <pre><code>import java.lang.ref.SoftReference; public class Main { public static void main(String[] args) { SoftReference&lt;String&gt; sr = new SoftReference&lt;String&gt;(new String("hello")); System.out.println(sr.get()); } } </code></pre> <span id="OSC_h3_6"></span> <h3>3.3 弱引用</h3> <p>弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。下面是使用示例:</p> <pre><code>import java.lang.ref.WeakReference; public class Main { public static void main(String[] args) { WeakReference&lt;String&gt; sr = new WeakReference&lt;String&gt;(new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); } } </code></pre> <span id="OSC_h3_7"></span> <h3>3.4 虚引用</h3> <p>虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用<code>java.lang.ref.PhantomReference</code>类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。<br /> 要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。</p> <p> </p> <pre><code>import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class Main { public static void main(String[] args) { ReferenceQueue&lt;String&gt; queue = new ReferenceQueue&lt;String&gt;(); PhantomReference&lt;String&gt; pr = new PhantomReference&lt;String&gt;(new String("hello"), queue); System.out.println(pr.get()); } } </code></pre> <span id="OSC_h3_8"></span> <h3>3.5 软引用和弱引用进一步说明</h3> <p>在SoftReference类中,有三个方法,两个构造方法和一个get方法(WeakReference类似):</p> <pre><code>public class SoftReference&lt;T&gt; extends Reference&lt;T&gt; { /** * Timestamp clock, updated by the garbage collector */ static private long clock; /** * Timestamp updated by each invocation of the get method. The VM may use * this field when selecting soft references to be cleared, but it is not * required to do so. */ private long timestamp; /** * Creates a new soft reference that refers to the given object. The new * reference is not registered with any queue. * * @param referent object the new soft reference will refer to */ public SoftReference(T referent) { super(referent); this.timestamp = clock; } /** * Creates a new soft reference that refers to the given object and is * registered with the given queue. * * @param referent object the new soft reference will refer to * @param q the queue with which the reference is to be registered, * or &lt;tt&gt;null&lt;/tt&gt; if registration is not required * */ public SoftReference(T referent, ReferenceQueue&lt;? super T&gt; q) { super(referent, q); this.timestamp = clock; } /** * Returns this reference object's referent. If this reference object has * been cleared, either by the program or by the garbage collector, then * this method returns &lt;code&gt;null&lt;/code&gt;. * * @return The object to which this reference refers, or * &lt;code&gt;null&lt;/code&gt; if this reference object has been cleared */ public T get() { T o = super.get(); if (o != null &amp;&amp; this.timestamp != clock) this.timestamp = clock; return o; } } </code></pre> <p>get方法用来获取与软引用关联的对象的引用,如果该对象被回收了,则返回null。</p> <p>在使用软引用和弱引用的时候,我们可以显示地通过System.gc()来通知JVM进行垃圾回收,但是要注意的是,虽然发出了通知,JVM不一定会立刻执行,也就是说这句是无法确保此时JVM一定会进行垃圾回收的。</p> <span id="OSC_h3_9"></span> <h3>3.6 虚引用进一步说明:</h3> <p>虚引用中有一个构造函数,可以看出,其必须和一个引用队列一起存在。get()方法永远返回null,因为虚引用永远不可达。</p> <pre><code>public class PhantomReference&lt;T&gt; extends Reference&lt;T&gt; { /** * Returns this reference object's referent. Because the referent of a * phantom reference is always inaccessible, this method always returns * &lt;code&gt;null&lt;/code&gt;. * * @return &lt;code&gt;null&lt;/code&gt; */ public T get() { return null; } /** * Creates a new phantom reference that refers to the given object and * is registered with the given queue. * * &lt;p&gt; It is possible to create a phantom reference with a &lt;tt&gt;null&lt;/tt&gt; * queue, but such a reference is completely useless: Its &lt;tt&gt;get&lt;/tt&gt; * method will always return null and, since it does not have a queue, it * will never be enqueued. * * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, * or &lt;tt&gt;null&lt;/tt&gt; if registration is not required */ public PhantomReference(T referent, ReferenceQueue&lt;? super T&gt; q) { super(referent, q); } } </code></pre> <blockquote> <p>参考</p> <ul><li>《深入理解Java虚拟机》</li> <li><a href="https://link.jianshu.com?t=https%3A%2F%2Fwww.cnblogs.com%2Fdolphin0520%2Fp%2F3784171.html" target="_blank" rel="nofollow">https://www.cnblogs.com/dolphin0520/p/3784171.html</a></li> </ul></blockquote> <div class="alert alert-success" role="alert"><p>来源:<code>oschina</code></p><p>链接:<code>https://my.oschina.net/xiaoyoung/blog/3159210</code></p></div></div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field--label">标签</div> <div class="field--items"> <div class="field--item"><a href="/tag/java" hreflang="zh-hans">java</a></div> <div class="field--item"><a href="/tag/fqueue" hreflang="zh-hans">FQueue</a></div> <div class="field--item"><a href="/tag/jdk" hreflang="zh-hans">JDK</a></div> <div class="field--item"><a href="/tag/reachability" hreflang="zh-hans">reachability</a></div> </div> </div> Wed, 26 Feb 2020 05:55:53 +0000 旧街凉风 3429845 at https://www.e-learn.cn