(2/2)候有发现下面出血,请问造成出血的原因出血是什么意思?接下了我该怎么样做?

99健康网_99相伴健康一生
Please click
if you are not redirected within a few seconds.
女人脚臭怎么办 想根治就要这样做
孕期外阴瘙痒应该这样做
冬季这样减肥 小心越减越丑
盘点热姜水能治疗的生活小毛病
3月18日是全国爱肝日,肝病发病率的逐年增加,引起了大家对肝脏健康的重视...
中日友好医院
擅长:1. 耳鼻咽喉-头颈部肿瘤的手术及综合治疗。如:早期喉癌CO2激光微创治疗;喉癌、下咽癌的喉功能保留手术、鼻及鼻窦肿瘤、口腔涎腺肿瘤、舌部肿瘤、鼻咽血管纤维瘤及咽喉部血管瘤的手术治疗。2. 鼻科、鼻眼...
中日友好医院
擅长:擅长开放性眼外伤的修复、眼内异物的取出和伤后组织及视力重建、增殖性糖尿病视网膜病变、眼底出血、视网膜脱离等手术,有数千例的手术经验;也擅长白内障超声乳化吸出及人工晶体植入手术、抗青光眼小梁切除等眼前节...
武警总医院
擅长:擅长颈椎病、腰椎疾病的微创诊治,对腰椎间盘突出、腰椎管狭窄及脊柱侧弯等疾病的治疗有深入的研究。...
武警总医院
擅长:脊柱及关节疾病的诊断治疗,尤其擅长脊柱疾患的微创治疗,不开刀治疗椎间盘突出(椎间孔镜下椎间盘突出治疗)。擅长脊柱退变性疾病,诸如腰椎间盘突出症、腰椎管狭窄症、腰椎滑脱症、颈椎病等;脊柱肿瘤;脊柱结核;脊柱骨折;强直性脊柱炎等疾患的诊治,尤其颈椎病,腰椎间盘突出症,腰椎管狭窄,脊柱侧弯的手术治疗...
武警总医院
擅长:难治性、复发性肾病综合征、乙肝相关性肾炎、慢性肾衰的综合治疗及老年患者的血液透析及长期存活等...
武警总医院
擅长:肾脏内科各种疾病诊治及慢性肾衰尿毒症患者的血液净化治疗、遗传性肾脏病及难治性肾病综合征的诊治...
中日友好医院
擅长:对小儿呼吸系统及消化系统疾病的治疗,经验丰富;对小儿多发性抽动症、注意力缺陷多动综合症等病,颇有研究;尤擅长中医药治疗小儿多发性抽动症(抽动...
中日友好医院
擅长:擅长儿童内分泌、遗传代谢性疾病的诊治,主治各种原因引起的矮小症(生长激素缺乏症,宫内发育迟缓,特纳综合症,家族性矮小,特发性矮小)、性早熟、性发育延迟、甲状腺功能疾病(减低或亢进),先天性肾上腺皮质增...
中日友好医院
擅长:作为妇科肿瘤专家近10年来重点主持妇科宫腔镜、腹腔镜微创治疗及子宫脱垂、盆底结构异常等经阴道手术。出色完成国家制定的宫腔镜、腹腔镜最高等级四级手术,尤其在不孕症、各类输卵管疾患、宫颈癌前病变、子宫肌瘤...
中日友好医院
擅长:擅长妇科良恶性肿瘤的诊断及治疗、危重病人的抢救。完成大量经开腹、阴式、宫腔镜及腹腔镜进行的妇科肿瘤手术;特别在阴道镜检查、阴道镜下活检、宫颈锥切术及宫颈癌的各种手术和辅助治疗方面具有较丰富的临床经验。...
首都医科大学附属北京妇产医院
擅长:男科...
首都医科大学附属北京妇产医院
擅长:男科...
宝宝奶粉能加葡萄糖吗?这是很多家长都会想...
快速提问:排卵期有几天
?一般来说,女性的有10天,但情绪、饮食、生活习惯、环境等多种因素都会影响排卵,因此排卵期有几天是因人而异的。1、一般来说,女性的排卵期有10天。正常女性每个(通常是一个月)会排卵一次,排卵的那天就是“”,排卵日的前5天、排卵日的后4天,再加上排卵日当天,就是女性的排卵期了。所以,女性排卵期有10天。2、并不是每个人的排卵期都是10天。由于现代生活的节奏不断加快,女性受到现实环境的影响越来越大。此外,女性的情绪、饮食、生活习惯等无一不影响着女性的排卵情况。当一个生活节奏比较有规律的女性突然改变了自己的生活作息习惯,一旦身体没有及时做出调整,内分泌情况可能会受到影响,经期就有可能提前或者推迟,那么排卵期的长短就会随着经期的变化而有所起伏。因此,排卵期10天只是大概的时间范围,而实际上,女性排卵时间一般都是小于10天的。3、排卵期的有效受孕时间是7天。既然排卵期不是必然固定的十天,那么在排卵期内同房,有效的受孕时间是多久呢?一般而言,一个正常女性的是2天左右,多则3天,少的则不到12个小时。一个男性的精子在女性体内的存活时间一般是3天到5天,通常是4天。也就是说,排卵期的有效受孕时间一般是女性排卵前5天,因为精子可以在女性体内存活最多5天,以及女性排卵之后的2天,因为卵子排出后,只能在女性体内存活2天,也就说,有效的受孕时间一般是7天左右。
与息息相关,那么期到底是哪几天呢?许多朋友对于排卵期是什么时候还不是很清楚,可是如果大家不知道排卵期为几天,就不太好掌握同房时间了。来了解一下排卵期是什么时候吧!
排卵期是什么时候?
1、如果规律的话,女性的排卵期一般在下次月经来潮前的14天左右。的前5天和后4天,连同排卵日在内共10天称为排卵期。
2、如果经期不稳定的话,你的排卵期是什么时候就要这样推算了:排卵期第一天=最短一次月经周期天数减去18天,排卵期最后一天=最长一次月经周期天数减去11天。计算方法是以本次月经来潮第一天为基点,向后算天数。
比如你月经周期不规律,有时候只有23天,有时候则长达35天。那么你排卵期的第一天就是23-18=5,排卵期的最后一天就是35-11=24。那这种情况下,排卵期可能就有24-5=19天这么长了。但是这也只能说明这段时间有排卵的可能性,无法精确推算出排卵日到底是哪天。
由于每个人的不同,过后几天是的答案也会不一样。但是,大家可以根据这个公式来测算期:1、对于经期比较稳定的女性:一般是下个月经潮来之前的第14天,那么在这天的前面5天,包括这天以及这天的后面4天,就称之为排卵期,也就是说,一般女性的排卵期是十天。2、如果月经周期不稳定,排卵期可以通过这样的方法来推算:排卵期第一天=最短一次月经周期天数减去18天,排卵期最后一天=最长一次月经周期天数减去11天。计算方法是以本次月经来潮第一天为基点,向后算天数。
1、经期推测法(1)对于月经规律的女性女性的排卵日期一般在下次月经来潮前的14天左右。下次月经来潮的第1天算起,倒数14天或减去14天就是排卵日,排卵日及其前5天和后4天加在一起称为排卵期。例如,某女的月经周期为28天,本次月经来潮的第1天在12月2日,那么下次月经来潮是在12月30日(12月2日加28天),再从12月30日减去14天,则12月16日就是排卵日。排卵日及其前5天和后4天,也就是12月11-20日为排卵期。对于月经规律的女性来说,器计算排卵期的结果较为准确。(2)对于月经不规律的女性可以试试运用排卵期计算公式计算:排卵期第一天=最短一次月经周期天数减去18天;排卵期最后一天=最长一次月经周期天数减去11天。例如月经期最短为28天,最长为37天,需将最短的规律期减去18(28-18=10)以及将最长的规律期减去11(37-11=26),所以在月经潮后的第10天至26天都属于排卵期。如果女性月经周期不规律,常出现月经延期或提前,那么计算的排卵期仍然会出现偏差。且因为女性的排卵日会受到外界情况的影响发生变化,所以,对于月经不准的女性来说,最准确的方法还是到医院监测排卵,才能准确掌握排卵期。2、B超监测法B超监测排卵就是通过B超来观察女性是否排卵。当卵泡直径≥18mm,表示卵泡已成熟,随时有排卵的可能。排卵前一天卵泡直径平均21—22mm。排卵标志有:卵泡消失或缩小;子宫直肠窝有液性暗区3-10mm;卵泡边缘模糊,内有稀疏光点,有时可见血肿。如光点密集,形成光团,即为黄体。在所有测排卵的方法中,最为准确的就是B超监测法,它不仅可以测出两侧卵巢中是否有优势卵泡,同时还能测出优势卵泡的大小、子宫内膜的厚度等。此外,这种方法还可以在一定程度上帮助预测排卵和指导受孕性生活。3、自测法排卵试纸是通过检测黄体生成激素(LH)的峰值水平,来预知是否排卵的。女性排卵前24-48小时内,尿液中的黄体生成激素(LH)会出现高峰值,用排卵试纸自测,结果就会显示为阳性。(1)出现两条紫红色线,下端线颜色明显比上端线颜色浅,表示将要排卵,必须持续每天测试。(2)出现两条紫红色线,上、下端线颜色基本相同,或下端线颜色比上端线深,表示将在24-48小时内排卵。(3)只出现一条紫红色线,表示无排卵。具体测试方法,一般在排卵试纸包装上都有说明(须严格按照说明做,才能尽量减少误差)。与早孕试纸不同的是,不可使用晨尿,并尽量采用每天同一时刻的尿样,收集尿液前2小时应减少水分摄入,因稀释了的尿样也会妨碍LH峰值的检测。那么对于月经周期比较规律,则应该在经期前14天(也就是预计的排卵时间),在这个时间的前三天加后三天,连续六天测定;要是月经不规律或者不正常,则一般在月经干净后第三天开始测。直到试纸上两条杠一样深或第二条杠比第一条杠还深,就说明将在24-48小时内排卵,其他测试结果可参考说明书的图示。需要提醒的是这是标准的排卵发生时的生理现象。试纸虽然使用方便,但由于制作过程、自测者本身等原因,准确率大概只有75%。另外,测试结果也并不是简单的“有”或“无”。有些人可能连续强阳几天,这样既有可能发生排卵(一般发生在最后一天的强阳以后),也有可能根本就没有排卵。4、基础体温测量法女性基础体温有周期性变化,排卵后基础体温升高能提示排卵已经发生,排卵一般发生在基础体温上升前由低到高上升的过程中,在基础体温处于升高水平的三天内为“易孕阶段”,但这种方法只能提示排卵已经发生,不能确定排卵将何时发生。正常情况下排卵后的体温上升0.3-0.5℃,称双相型体温。如无排卵,体温不上升,整个周期间呈现低平体温,称单相型体温。值得注意的是:(1)基础体温的测量必须要经6小时充足睡眠后,醒来尚未进行任何活动之前测体温并记录,即在上厕所、洗脸、漱口等等例行工作前进行。如果时间耽搁太久,或者移动身体,体温也会跟着改变。(2)任何特殊情况都可能影响基础体温的变化,所以碰到特殊情况都要记录下来,如前一天夜里的性生活、近日感冒等。需要反复多次测试,并用看表点线相连。(3)将体温计于睡前放在枕边可随手拿到之处,于次日睡醒,尚未起床活动时,放在舌下测量五分钟,并记录在基础体温表上。5、宫颈粘液观察法宫颈黏液在整个月经周期中都会出现,而且它有着微妙的周期性变化。月经刚过,黏液分泌量也逐渐增加,并逐渐变得稀薄而透明,类似蛋清,在排卵前达到高峰,也可以这样说,黏液量最多的一天,排卵的概率最高。此时,用手指伸入阴道深处,沾一些从子宫颈流出的黏液,可以将黏液拉成细丝长达10厘米而不断。排卵后,在孕激素的作用下,黏液的分泌量显着减少,稠厚而浑浊,延展性差,拉丝时容易断裂。若每晚对黏液状态进行观察并记录下来,了解粘液的变化规律,你可以很容易找到自己的排卵日。6、白带观察法根据白带的状态可以辅助你判断排卵期和找准同房时机,这样有利于怀孕,一般来说,白带最多、最稀薄、拉丝能力最强的一天往往就是排卵期。在一个月经周期中,白带并不是一成不变的。大多数时候,白带比较干、比较稠,也比较少。而在两次月经中间的那一天,白带又清、又亮、又多,像鸡蛋清,更像感冒时的清水样鼻涕,这天就是排卵期。这是由于排卵时产生了较高浓度的雌激素,作用于宫颈口的柱状上皮细胞,使它们分泌大量白带。7、乳头触痛观测法女性们有一个体会,就是有时候乳头显得非常敏感。在洗澡、换内衣等乳头受到碰擦、挤压时会感到疼痛。这是因为乳头和乳腺管对雌激素很敏感,在排卵期产生的下,乳头变大、变红、颜色变深,感觉变得很敏感,同时乳腺导管会变粗、变大、变长,把乳头往外顶。8、排卵痛观测法排卵性腹痛属于一种生理性腹痛,和女性原发性痛经一样,不同的是这种痛是由于妇女的卵巢在排卵时,卵泡破裂以及排卵后卵泡液对腹膜的刺激所引起的,因此,腹痛都比较轻。通常,女性体内的两个卵巢是轮流进行排卵的,每月排卵一次,所以腹痛也是周期性地每月发生一次,往往是左、右下腹交替出现疼痛。部分女性在两次月经的中间,恰好相当于排卵前的时间里,下腹部会有疼痛的感觉,所以排卵痛也叫做中间痛。有专家曾调查,有97%的中间痛是在排卵前感觉到的。因此,如有中间痛,则可以认为24小时内将发生排卵。9、观测法在有规律的两次月经中期,即排卵期,由于排卵所致的雌激素水平短暂下降,使部分女性的子宫内膜失去雌激素的支持,而出现子宫内膜脱落,引起有规律的阴道出血,称为排卵期出血。中医学称之为“”。但一般情况下,排卵后随着黄体的形成,黄体分泌雌、孕激素,会很快修复子宫内膜并使子宫内膜朝增生期变化,内膜得以增厚修复而停止出血。
相关知识点
相关产品排行榜
相关词条804
联系编辑:丁笑
联系邮箱:dingxiao#pcbaby.com.cn (#改为@)
联系电话:020-6药品品牌专区
罗浮山国药
白云山药业
颈复康药业
副主任医师
副主任医师
副主任医师
副主任医师
副主任医师
文字发帖咨询医生
00:42:42[]
00:42:26[]
00:42:05[]
00:42:03[]
00:41:53[]
00:41:16[]
00:47:42[]
00:32:51[]
00:13:32[]
00:11:44[]
00:06:16[]
23:54:11[]
常见疾病问题
22:46 21:28 19:08 18:11 18:10 17:51 17:50 17:50 17:49 17:48 17:47 17:46 17:45 16:31 16:28 15:23 15:19 15:16
00:53 00:47 00:42 00:09 23:45 23:41 23:29 23:04 22:57 22:53 22:50 22:45 22:33 22:29 22:28 22:27 22:25 22:24
21:32 21:08 20:51 19:44 17:30 17:20 15:54 15:54 15:54 15:53 15:53 15:52 15:52 15:52 15:52 15:51 15:51 15:51
23:40 23:39 23:17 23:10 22:56 21:42 20:42 20:24 19:49 19:30 18:58 18:55 18:00 17:52 17:44 17:37 17:37 17:30
20:31 20:30 13:45 13:00 12:53 11:30 09:10 04:52 00:34 00:02 23:01 22:32 22:22 22:12 21:03 20:22 19:13 18:29
23:55 22:58 22:20 21:49 21:46 21:23 21:22 21:20 21:17 21:09 20:36 20:20 20:07 19:37 19:34 19:21 19:18 19:16
21:50 21:21 19:57 19:07 17:57 17:40 17:40 17:38 17:33 16:47 16:46 16:44 16:24 15:53 15:38 15:31 14:11 14:00
23:14 23:14 22:46 20:25 19:53 19:05 18:32 17:57 17:35 17:29 17:03 16:47 16:22 16:13 16:12 15:47 15:20 15:19
21:23 21:03 21:01 20:31 17:57 16:47 16:25 16:07 16:05 16:00 14:46 14:09 13:36 12:20 11:28 11:22 11:03 10:29
22:13 19:32 18:05 16:56 16:38 15:36 11:37 08:36 06:23 02:10 23:47 23:44 19:11 18:46 17:16 17:11 16:46 14:33
00:53 23:06 22:48 22:31 22:31 22:19 21:42 21:26 21:25 21:17 21:17 21:16 21:04 20:55 20:53 20:20 20:17 20:11
00:47 23:54 23:52 22:25 22:16 22:07 21:52 21:48 21:33 21:22 21:17 21:06 21:06 20:48 20:47 20:42 20:39 20:29
23:49 22:30 22:25 22:24 21:27 21:24 21:15 20:24 18:55 18:17 18:06 17:53 17:52 17:46 17:43 17:37 17:06 17:05
01:00 00:59 00:55 00:55 00:47 00:40 00:30 00:26 00:24 00:22 00:19 00:11 00:06 00:05 23:51 23:47 23:38 23:32
/ 帮您快速找医院
按地区找医院
按科室找医院
按疾病找医院
按疾病首字母查找:
/ TA们正在分享
综合评分:
参考价格:¥17.00
规格:每片含愈创木酚甘油醚200mg与氢溴酸右美沙芬15mg
综合评分:
参考价格:¥13.00
规格:每片重0.5克
综合评分:
参考价格:¥5.50
规格:120ml
综合评分:
参考价格:¥35.00
规格:20g*10袋
综合评分:
参考价格:¥22.00
规格:9g*10袋
综合评分:
参考价格:¥40.00
规格:150g
综合评分:
参考价格:¥16.00
规格:10g/支
综合评分:
参考价格:¥28.00
规格:4g*6支
综合评分:
参考价格:¥14.00
规格:20mg*6粒
综合评分:
参考价格:¥11.00
规格:20mg*10粒
综合评分:
参考价格:¥16.00
规格:0.4g*30粒
综合评分:
参考价格:¥8.00
规格:60克
综合评分:
参考价格:¥15.00
规格:10g*10袋
综合评分:
参考价格:¥262.00
规格:250mg*25粒
综合评分:
参考价格:¥9.00
规格:50mg*20片*3板
综合评分:
参考价格:¥85.00
规格:228mg*12粒*3板
综合评分:
参考价格:¥10.00
规格:25mg*12片
综合评分:
参考价格:¥26.00
规格:25mg*24粒
综合评分:
参考价格:¥316.00
规格:5ml:17.5mg*10支
综合评分:
参考价格:¥42.00
规格:5ml:50m
综合评分:
参考价格:¥18.00
规格:10ml:100mg
综合评分:
参考价格:¥13.00
规格:15ml
综合评分:
参考价格:¥269.00
规格:25ml:0.1mg,1支
综合评分:
参考价格:¥76.00
规格:5ml:10mg
综合评分:
参考价格:¥198.00
规格:50mg*5片
综合评分:
参考价格:¥21.80
规格:0.5g*12粒*3板
综合评分:
参考价格:¥21.00
综合评分:
参考价格:¥9.80
规格:0.5g*30粒
综合评分:
参考价格:¥35.00
规格:0.32g*24粒
综合评分:
参考价格:¥55.00
规格:21片
综合评分:
参考价格:¥3.00
规格:0.5mg
综合评分:
参考价格:¥126.00
综合评分:
参考价格:¥19.50
规格:0.35g*48粒
综合评分:
参考价格:¥24.00
规格:3g*5粒
综合评分:
参考价格:¥4.90
规格:20mg*7粒
综合评分:
参考价格:¥41.00
规格:0.4g*48粒
综合评分:
参考价格:¥28.00
规格:1g*6袋
综合评分:
参考价格:¥4.50
规格:0.6g*16片*3板
综合评分:
参考价格:¥11.00
规格:1000片
综合评分:
参考价格:¥22.00
规格:10ml*6支
综合评分:
参考价格:¥16.50
规格:1.5g*10袋
综合评分:
参考价格:¥19.00
规格:10g*10袋
综合评分:
参考价格:¥35.10
规格:双室瓶包装,40mg(以甲泼尼龙计),1瓶
综合评分:
参考价格:¥23.00
综合评分:
参考价格:¥7.00
规格:50mg*12袋
综合评分:
参考价格:¥37.50
规格:0.4g*12片
综合评分:
参考价格:¥12.00
规格:0.1g*12片
综合评分:
参考价格:¥45.00
规格:0.314g*24粒
综合评分:
参考价格:¥151.19
规格:0.3g*60粒
综合评分:
参考价格:¥149.90
规格:500mg*60粒
综合评分:
参考价格:¥115.20
规格:250mg/粒×300粒
综合评分:
参考价格:¥70.20
规格:72粒
综合评分:
参考价格:¥188.00
规格:500mg*60粒
综合评分:
参考价格:¥98.00
规格:600mg*100片
综合评分:
参考价格:¥151.19
规格:0.3g*60粒
综合评分:
参考价格:¥149.90
规格:500mg*60粒
综合评分:
参考价格:¥115.20
规格:250mg/粒×300粒
综合评分:
参考价格:¥70.20
规格:72粒
综合评分:
参考价格:¥188.00
规格:500mg*60粒
综合评分:
参考价格:¥98.00
规格:600mg*100片
/&&因为专业,所以精彩!
选择快速问医生的五个理由
万名医生,85%以上的医生来源于三级医院的专家,临床经验丰富
严格按照卫生部的要求审核医生资质,医生认证通过后方可展示
文字提问、在线医生、电话咨询医生,多种方式快速直接与医生沟通
健康咨询、体检一站式服务、健康档案、健康干预等全面家庭医生式服务
提供服务的医生100%均为医生本人,对医生的服务不满意,您可以24小时投诉
快速问医生APP
为您远程诊断,帮您安全用药
快速问医生微信
快速加医生为好友快速帮您求助相同病友
扫一扫关注官方微信
诊疗助手APP
诊疗方案、医学病例让您随身携带
┊ 经营许可证:粤B2- ┊ 互联网药品信息服务资格证: 粤┊粤公网安备:05
服务声明: 网络沟通无法像面诊过程那样,不能全面了解您的健康状况,因此医生的健康指导建议仅供参考,具体诊疗请一定要到医院在医生指导下进行!
珠海健康云科技有限公司 版权所有 版权登记号:
Copyright (C) , 120ASK.COM, All Rights ReservedAFNetworking到底做了什么? - 简书
AFNetworking到底做了什么?
写在开头:
作为一个iOS开发,也许你不知道NSUrlRequest、不知道NSUrlConnection、也不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking。
大多数人习惯了只要是请求网络都用AF,但是你真的知道AF做了什么吗?为什么我们不用原生的NSURLSession而选择AFNetworking?
本文将从源码的角度去分析AF的实际作用。
或许看完这篇文章,你心里会有一个答案。
先从最新的AF3.x讲起吧:
首先,我们就一起分析一下该框架的组成。
将AF下载导入工程后,下面是其包结构,相对于2.x变得非常简单了:
AF代码结构图.png
除去Support Files,可以看到AF分为如下5个功能模块:
网络通信模块(AFURLSessionManager、AFHTTPSessionManger)
网络状态监听模块(Reachability)
网络通信安全策略模块(Security)
网络通信信息序列化/反序列化模块(Serialization)
对于iOS UIKit库的扩展(UIKit)
其核心当然是网络通信模块AFURLSessionManager。大家都知道,AF3.x是基于NSURLSession来封装的。所以这个类围绕着NSURLSession做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit的一个扩展工具包。
这五个模块所对应的类的结构关系图如下所示:
AF架构图.png
其中AFHTTPSessionManager是继承于AFURLSessionManager的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager或者其它类去做。
首先我们简单的写个get请求:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];
[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id
_Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
首先我们我们调用了初始化方法生成了一个manager,我们点进去看看初始化做了什么:
- (instancetype)init {
return [self initWithBaseURL:nil];
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
self = [super initWithSessionConfiguration:configuration];
if (!self) {
//对传过来的BaseUrl进行处理,如果有值且最后不包含/,url加上"/"
//--经一位热心读者更正...以后注释也一定要走心啊...不能误导大家...
if ([[url path] length] & 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
self.baseURL =
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
初始化都调用到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中来了。
其实初始化方法都调用父类的初始化方法。父类也就是AF3.x最最核心的类AFURLSessionManager。几乎所有的类都是围绕着这个类在处理业务逻辑。
除此之外,方法中把baseURL存了起来,还生成了一个请求序列对象和一个响应序列对象。后面再细说这两个类是干什么用的。
直接来到父类AFURLSessionManager的初始化方法:
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.sessionConfiguration =
self.operationQueue = [[NSOperationQueue alloc] init];
//queue并发线程数设置为1
self.operationQueue.maxConcurrentOperationCount = 1;
//注意代理,代理的继承,实际上NSURLSession去判断了,你实现了哪个方法会去调用,包括子代理的方法!
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//各种响应转码
self.responseSerializer = [AFJSONResponseSerializer serializer];
//设置默认安全策略
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
// 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典(重点,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理) ===============
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
设置AFURLSessionManagerTaskDelegate 词典的锁,确保词典在多线程访问时的线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockN
// 置空task关联的代理
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
这个就是最终的初始化方法了,注释应该写的很清楚,唯一需要说的就是三点:
self.operationQueue.maxConcurrentOperationCount = 1;这个operationQueue就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了。至于这里为什么要这么做,我们先留一个坑,等我们讲完AF2.x之后再来分析这一块。
第二就是我们初始化了一些属性,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier,这个是用来让每一个请求task和我们自定义的AF代理来建立映射用的,其实AF对task的代理进行了一个封装,并且转发代理到AF自定义的代理,这是AF比较重要的一部分,接下来我们会具体讲这一块。
第三就是下面这个方法:
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
首先说说这个方法是干什么用的:这个方法用来异步的获取当前session的所有未完成的task。其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有。我们打断点去看,也确实如此,里面的数组都是空的。
但是想想也知道,AF大神不会把一段没用的代码放在这吧。辗转多处,终于从AF的issue中找到了结论:。
原来这是为了防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash。
初始化方法到这就全部完成了。
分割图.png
接着我们来看看网络请求:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
//生成一个task
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
//开始网络请求
[dataTask resume];
return dataT
方法走到类AFHTTPSessionManager中来,调用父类,也就是我们整个AF3.x的核心类AFURLSessionManager的方法,生成了一个系统的NSURLSessionDataTask实例,并且开始网络请求。
我们继续往父类里看,看看这个方法到底做了什么:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
NSError *serializationError =
//把参数,还有各种东西转化为一个request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//如果解析错误,直接返回
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
#pragma clang diagnostic pop
__block NSURLSessionDataTask *dataTask =
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
if (success) {
success(dataTask, responseObject);
return dataT
这个方法做了两件事:
1.用self.requestSerializer和各种参数去获取了一个我们最终请求网络需要的NSMutableURLRequest实例。
2.调用另外一个方法dataTaskWithRequest去拿到我们最终需要的NSURLSessionDataTask实例,并且在完成的回调里,调用我们传过来的成功和失败的回调。
注意下面这个方法,我们常用来 push pop搭配,来忽略一些编译器的警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
这里是用来忽略:?带来的警告,具体的各种编译器警告描述,可以参考这篇:。
说到底这个方法还是没有做实事,我们继续到requestSerializer方法里去看,看看AF到底如何拼接成我们需要的request的:
接着我们跑到AFURLRequestSerialization类中:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
//断言,debug模式下,如果缺少改参数,crash
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod =
//将request的各种属性循环遍历
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
//如果自己观察到的发生变化的属性,在这些方法里
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
//把给自己设置的属性给request设置
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
//将传入的parameters进行编码,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableR
讲一下这个方法,这个方法做了3件事:
1)设置request的请求类型,get,post,put...等
2)往request里添加一些参数设置,其中AFHTTPRequestSerializerObservedKeyPaths()是一个c函数,返回一个数组,我们来看看这个函数:
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths =
static dispatch_once_t onceT
// 此处需要observer的keypath为allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies
// HTTPShouldUsePipelining、networkServiceType、timeoutInterval
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
//就是一个数组里装了很多方法的名字,
return _AFHTTPRequestSerializerObservedKeyP
其实这个函数就是封装了一些属性的名字,这些都是NSUrlRequest的属性。
再来看看self.mutableObservedChangedKeyPaths,这个是当前类的一个属性:
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyP
在-init方法对这个集合进行了初始化,并且对当前类的和NSUrlRequest相关的那些属性添加了KVO监听:
//每次都会重置变化
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
//给这自己些方法添加观察者为自己,就是request的各种属性,set方法
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
KVO触发的方法:
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
//当观察到这些set方法被调用了,而且不为Null就会添加到集合里,否则移除
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
[self.mutableObservedChangedKeyPaths addObject:keyPath];
至此我们知道self.mutableObservedChangedKeyPaths其实就是我们自己设置的request属性值的集合。
接下来调用:
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
用KVC的方式,把属性值都设置到我们请求的request中去。
3)把需要传递的参数进行编码,并且设置到request中去:
//将传入的parameters进行编码,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//从自己的head里去遍历,如果有值则设置给request的head
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
//来把各种类型的参数,array dic set转化成字符串,给request
NSString *query =
if (parameters) {
//自定义的解析方式
if (self.queryStringSerialization) {
NSError *serializationE
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationE
//默认解析方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
//最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。因为这几个method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length & 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
//post put请求
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
//设置请求体
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
return mutableR
这个方法做了3件事:
1.从self.HTTPRequestHeaders中拿到设置的参数,赋值要请求的request里去
2.把请求网络的参数,从array dic set这些容器类型转换为字符串,具体转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式。自定义的方式没什么好说的,想怎么去解析由你自己来决定。我们可以来看看默认的方式:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
//把参数给AFQueryStringPairsFromDictionary,拿到AF的一个类型的数据就一个key,value对象,在URLEncodedStringValue拼接keyValue,一个加到数组里
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
//拆分数组返回参数字符串
return [mutablePairs componentsJoinedByString:@"&"];
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
//往下调用
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 根据需要排列的对象的description来进行升序排列,并且selector使用的是compare:
// 因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数
// 即@[@"foo", @"bar", @"bae"] ----& @[@"bae", @"bar",@"foo"]
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
//判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary =
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array =
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set =
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
return mutableQueryStringC
转码主要是以上三个函数,配合着注释应该也很好理解:主要是在递归调用AFQueryStringPairsFromKeyAndValue。判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。
其中有个AFQueryStringPair对象,其只有两个属性和两个方法:
@property (readwrite, nonatomic, strong)
@property (readwrite, nonatomic, strong)
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
self.field =
self.value =
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
方法很简单,现在我们也很容易理解这整个转码过程了,我们举个例子梳理下,就是以下这3步:
@"name" : @"bang",
@"phone": @{@"mobile": @"xx", @"home": @"xx"},
@"families": @[@"father", @"mother"],
@"nums": [NSSet setWithObjects:@"1", @"2", nil]
field: @"name", value: @"bang",
field: @"phone[mobile]", value: @"xx",
field: @"phone[home]", value: @"xx",
field: @"families[]", value: @"father",
field: @"families[]", value: @"mother",
field: @"nums", value: @"1",
field: @"nums", value: @"2",
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
至此,我们原来的容器类型的参数,就这样变成字符串类型了。
紧接着这个方法还根据该request中请求类型,来判断参数字符串应该如何设置到request中去。如果是GET、HEAD、DELETE,则把参数quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的:
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length & 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
//post put请求
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
//设置请求体
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
至此,我们生成了一个request。
分割图.png
我们再回到AFHTTPSessionManager类中来,回到这个方法:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
NSError *serializationError =
//把参数,还有各种东西转化为一个request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//如果解析错误,直接返回
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
#pragma clang diagnostic pop
__block NSURLSessionDataTask *dataTask =
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
if (success) {
success(dataTask, responseObject);
return dataT
绕了一圈我们又回来了。。
我们继续往下看:当解析错误,我们直接调用传进来的fauler的Block失败返回了,这里有一个self.completionQueue,这个是我们自定义的,这个是一个GCD的Queue如果设置了那么从这个Queue中回调结果,否则从主队列回调。
实际上这个Queue还是挺有用的,之前还用到过。我们公司有自己的一套数据加解密的解析模式,所以我们回调回来的数据并不想是主线程,我们可以设置这个Queue,在分线程进行解析数据,然后自己再调回到主线程去刷新UI。
言归正传,我们接着调用了父类的生成task的方法,并且执行了一个成功和失败的回调,我们接着去父类AFURLSessionManger里看(总算到我们的核心类了..):
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,
NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask =
//第一件事,创建NSURLSessionDataTask,里面适配了Ios8以下taskIdentifiers,函数创建task对象。
//其实现应该是因为iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一…这边做了一个串行处理
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataT
我们注意到这个方法非常简单,就调用了一个url_session_manager_create_task_safely()函数,传了一个Block进去,Block里就是iOS原生生成dataTask的方法。此外,还调用了一个addDelegateForDataTask的方法。
我们到这先到这个函数里去看看:
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber & NSFoundationVersionNumber_With_Fixed_9552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=9552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
//理解下,第一为什么用sync,因为是想要主线程等在这,等执行完,在返回,因为必须执行完dataTask才有数据,传值才有意义。
//第二,为什么要用串行队列,因为这块是为了防止ios8以下内部的dataTaskWithRequest是并发创建的,
//这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate。
dispatch_sync(url_session_manager_creation_queue(), block);
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_
static dispatch_once_t onceT
//保证了即使是在多线程的环境下,也不会创建其他队列
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
return af_url_session_manager_creation_
方法非常简单,关键是理解这么做的目的:为什么我们不直接去调用
dataTask = [self.session dataTaskWithRequest:request];
非要绕这么一圈,我们点进去bug日志里看看,原来这是为了适配iOS8的以下,创建session的时候,偶发的情况会出现session的属性taskIdentifier这个值不唯一,而这个taskIdentifier是我们后面来映射delegate的key,所以它必须是唯一的。
具体原因应该是NSURLSession内部去生成task的时候是用多线程并发去执行的。想通了这一点,我们就很好解决了,我们只需要在iOS8以下同步串行的去生成task就可以防止这一问题发生(如果还是不理解同步串行的原因,可以看看注释)。
题外话:很多同学都会抱怨为什么sync我从来用不到,看,有用到的地方了吧,很多东西不是没用,而只是你想不到怎么用。
我们接着看到:
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
// AFURLSessionManagerTaskDelegate与AFURLSessionManager建立相互关系
delegate.manager =
delegate.completionHandler = completionH
//这个taskDescriptionForSessionTasks用来发送开始和挂起通知的时候会用到,就是用这个值来Post通知,来两者对应
dataTask.taskDescription = self.taskDescriptionForSessionT
// ***** 将AF delegate对象与 dataTask建立关系
[self setDelegate:delegate forTask:dataTask];
// 设置AF delegate的上传进度,下载进度块。
delegate.uploadProgressBlock = uploadProgressB
delegate.downloadProgressBlock = downloadProgressB
1)这个方法,生成了一个AFURLSessionManagerTaskDelegate,这个其实就是AF的自定义代理。我们请求传来的参数,都赋值给这个AF的代理了。
2)delegate.manager =代理把AFURLSessionManager这个类作为属性了,我们可以看到:
@property (nonatomic, weak) AFURLSessionManager *
这个属性是弱引用的,所以不会存在循环引用的问题。
3)我们调用了[self setDelegate:delegate forTask:dataTask];
我们进去看看这个方法做了什么:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
//断言,如果没有这个参数,debug下crash在这
NSParameterAssert(task);
NSParameterAssert(delegate);
//加锁保证字典线程安全
[self.lock lock];
// 将AF delegate放入以taskIdentifier标记的词典中(同一个NSURLSession中的taskIdentifier是唯一的)
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] =
// 为AF delegate 设置task 的progress监听
[delegate setupProgressForTask:task];
//添加task开始和暂停的通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
这个方法主要就是把AF代理和task建立映射,存在了一个我们事先声明好的字典里。
而要加锁的原因是因为本身我们这个字典属性是mutable的,是线程不安全的。而我们对这些方法的调用,确实是会在复杂的多线程环境中,后面会仔细提到线程问题。
还有个[delegate setupProgressForTask:task];我们到方法里去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask =
//拿到上传下载期望的数据大小
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToS
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToR
//将上传与下载进度和 任务绑定在一起,直接cancel suspend resume进度条,可以cancel...任务
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakT
[strongTask cancel];
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakT
[strongTask suspend];
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.uploadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakT
[strongTask resume];
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakT
[strongTask cancel];
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakT
[strongTask suspend];
if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.downloadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakT
[strongTask resume];
//观察task的这些属性
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
options:NSKeyValueObservingOptionNew
context:NULL];
//观察progress这两个属性
[self.downloadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
这个方法也非常简单,主要做了以下几件事:
downloadProgress与uploadProgress的一些属性,并且把两者和task的任务状态绑定在了一起。注意这两者都是NSProgress的实例对象,(这里可能又一群小伙伴楞在这了,这是个什么...)简单来说,这就是iOS7引进的一个用来管理进度的类,可以开始,暂停,取消,完整的对应了task的各种状态,当progress进行各种操作的时候,task也会引发对应操作。
2)给task和progress的各个属及添加KVO监听,至于监听了干什么用,我们接着往下看:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary&NSString *,id& *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
//给进度条赋新值
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
//上面的赋新值会触发这两个,调用block回调,用户拿到进度
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
方法非常简单直观,主要就是如果task触发KVO,则给progress进度赋值,应为赋值了,所以会触发progress的KVO,也会调用到这里,然后去执行我们传进来的downloadProgressBlock和uploadProgressBlock。主要的作用就是为了让进度实时的传递。
主要是观摩一下大神的写代码的结构,这个解耦的编程思想,不愧是大神...
还有一点需要注意:我们之前的setProgress和这个KVO监听,都是在我们AF自定义的delegate内的,是有一个task就会有一个delegate的。所以说我们是每个task都会去监听这些属性,分别在各自的AF代理内。看到这,可能有些小伙伴会有点乱,没关系。等整个讲完之后我们还会详细的去讲捋一捋manager、task、还有AF自定义代理三者之前的对应关系。
到这里我们整个对task的处理就完成了。
分割图.png
接着task就开始请求网络了,还记得我们初始化方法中:
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
我们把AFUrlSessionManager作为了所有的task的delegate。当我们请求网络的时候,这些代理开始调用了:
NSUrlSession的代理.png
AFUrlSessionManager一共实现了如上图所示这么一大堆NSUrlSession相关的代理。(小伙伴们的顺序可能不一样,楼主根据代理隶属重新排序了一下)
而只转发了其中3条到AF自定义的delegate中:
AF自定义delegate.png
这就是我们一开始说的,AFUrlSessionManager对这一大堆代理做了一些公共的处理,而转发到AF自定义代理的3条,则负责把每个task对应的数据回调出去。
又有小伙伴问了,我们设置的这个代理不是NSURLSessionDelegate吗?怎么能响应NSUrlSession这么多代理呢?我们点到类的声明文件中去看看:
@protocol NSURLSessionDelegate &NSObject&
@protocol NSURLSessionTaskDelegate &NSURLSessionDelegate&
@protocol NSURLSessionDataDelegate &NSURLSessionTaskDelegate&
@protocol NSURLSessionDownloadDelegate &NSURLSessionTaskDelegate&
@protocol NSURLSessionStreamDelegate &NSURLSessionTaskDelegate&
我们可以看到这些代理都是继承关系,而在NSURLSession实现中,只要设置了这个代理,它会去判断这些所有的代理,是否respondsToSelector这些代理中的方法,如果响应了就会去调用。
而AF还重写了respondsToSelector方法:
- (BOOL)respondsToSelector:(SEL)selector {
//复写了selector的方法,这几个方法是在本类有实现的,但是如果外面的Block没赋值的话,则返回NO,相当于没有实现!
if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
return self.taskWillPerformHTTPRedirection !=
} else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
return self.dataTaskDidReceiveResponse !=
} else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
return self.dataTaskWillCacheResponse !=
} else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
return self.didFinishEventsForBackgroundURLSession !=
return [[self class] instancesRespondToSelector:selector];
这样如果没实现这些我们自定义的Block也不会去回调这些代理。因为本身某些代理,只执行了这些自定义的Block,如果Block都没有赋值,那我们调用代理也没有任何意义。
讲到这,我们顺便看看AFUrlSessionManager的一些自定义Block:
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeI
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationC
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLS
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPR
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationC
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyS
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyD
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidC
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveR
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadT
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveD
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheR
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishD
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteD
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidR
各自对应的还有一堆这样的set方法:
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
self.sessionDidBecomeInvalid =
方法都是一样的,就不重复粘贴占篇幅了。
主要谈谈这个设计思路
作者用@property把这个些Block属性在.m文件中声明,然后复写了set方法。
然后在.h中去声明这些set方法:
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))
为什么要绕这么一大圈呢?原来这是为了我们这些用户使用起来方便,调用set方法去设置这些Block,能很清晰的看到Block的各个参数与返回值。大神的精髓的编程思想无处不体现...
接下来我们就讲讲这些代理方法做了什么(按照顺序来):
NSURLSessionDelegate
//当前这个session已经失效时,该代理方法被调用。
如果你使用finishTasksAndInvalidate函数使该session失效,
那么session首先会先完成最后一个task,然后再调用URLSession:didBecomeInvalidWithError:代理方法,
如果你调用invalidateAndCancel方法来使session失效,那么该session会立即调用上面的代理方法。
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
方法调用时机注释写的很清楚,就调用了一下我们自定义的Block,还发了一个失效的通知,至于这个通知有什么用。很抱歉,AF没用它做任何事,只是发了...目的是用户自己可以利用这个通知做什么事吧。
其实AF大部分通知都是如此。当然,还有一部分通知AF还是有自己用到的,包括配合对UIKit的一些扩展来使用,后面我们会有单独篇幅展开讲讲这些UIKit的扩展类的实现。
//2、https认证
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
//挑战处理类型为 默认
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeUseCredential:使用指定的证书
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultH
__block NSURLCredential *credential =
// sessionDidReceiveAuthenticationChallenge是自定义方法,用来如何应对服务器端的认证挑战
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
// 此处服务器要求客户端的接收认证挑战方法是NSURLAuthenticationMethodServerTrust
// 也就是说服务器端需要客户端返回一个根据认证挑战的保护空间提供的信任(即challenge.protectionSpace.serverTrust)产生的挑战证书。
// 而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 确定挑战的方式
if (credential) {
//证书挑战
disposition = NSURLSessionAuthChallengeUseC
//默认挑战
唯一区别,下面少了这一步!
disposition = NSURLSessionAuthChallengePerformDefaultH
//取消挑战
disposition = NSURLSessionAuthChallengeCancelAuthenticationC
//默认挑战方式
disposition = NSURLSessionAuthChallengePerformDefaultH
//完成挑战
if (completionHandler) {
completionHandler(disposition, credential);
函数作用:
web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge)。接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书)。最后调用completionHandler回应服务器端的挑战。
函数讨论:
该代理方法会在下面两种情况调用:
当服务器端要求客户端提供证书时或者进行NTLM认证(Windows NT LAN Manager,微软提出的WindowsNT挑战/响应验证机制)时,此方法允许你的app提供正确的挑战证书。
当某个session使用SSL/TLS协议,第一次和服务器端建立连接的时候,服务器会发送给iOS客户端一个证书,此方法允许你的app验证服务期端的证书链(certificate keychain)
注:如果你没有实现该方法,该session会调用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。
这里,我把官方文档对这个方法的描述翻译了一下。
总结一下,这个方法其实就是做https认证的。看看上面的注释,大概能看明白这个方法做认证的步骤,我们还是如果有自定义的做认证的Block,则调用我们自定义的,否则去执行默认的认证步骤,最后调用完成认证:
//完成挑战
if (completionHandler) {
completionHandler(disposition, credential);
//3、 当session中所有已经入队的消息被发送出去后,会调用该代理方法。
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
官方文档翻译:
函数讨论:
在iOS中,当一个后台传输任务完成或者后台传输时需要证书,而此时你的app正在后台挂起,那么你的app在后台会自动重新启动运行,并且这个app的UIApplicationDelegate会发送一个application:handleEventsForBackgroundURLSession:completionHandler:消息。该消息包含了对应后台的session的identifier,而且这个消息会导致你的app启动。你的app随后应该先存储completion handler,然后再使用相同的identifier创建一个background configuration,并根据这个background configuration创建一个新的session。这个新创建的session会自动与后台任务重新关联在一起。
当你的app获取了一个URLSessionDidFinishEventsForBackgroundURLSession:消息,这就意味着之前这个session中已经入队的所有消息都转发出去了,这时候再调用先前存取的completion handler是安全的,或者因为内部更新而导致调用completion handler也是安全的。
NSURLSessionTaskDelegate
//被服务器重定向的时候调用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
NSURLRequest *redirectRequest =
// step1. 看是否有对应的user block 有的话转发出去,通过这4个参数,返回一个NSURLRequest类型参数,request转发、网络重定向.
if (self.taskWillPerformHTTPRedirection) {
//用自己自定义的一个重定向的block实现,返回一个新的request。
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
if (completionHandler) {
// step2. 用request重新请求
completionHandler(redirectRequest);
一开始我以为这个方法是类似NSURLProtocol,可以在请求时自己主动的去重定向request,后来发现不是,这个方法是在服务器去重定向的时候,才会被调用。为此我写了段简单的PHP测了测:
defined('BASEPATH') OR exit('No direct script access allowed');
class Welcome extends CI_Controller {
public function index()
header("location: http://www.huixionghome.cn/");
证实确实如此,当我们服务器重定向的时候,代理就被调用了,我们可以去重新定义这个重定向的request。
关于这个代理还有一些需要注意的地方:
此方法只会在default session或者ephemeral session中调用,而在background session中,session task会自动重定向。
这里指的模式是我们一开始Init的模式:
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.sessionConfiguration =
这个模式总共分为3种:
对于NSURLSession对象的初始化需要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三个类工厂方法:
+defaultSessionConfiguration 返回一个标准的 configuration,这个配置实际上与 NSURLConnection 的网络堆栈(networking stack)是一样的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage。
+ephemeralSessionConfiguration 返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的。
+backgroundSessionConfiguration:(NSString *)identifier 的独特之处在于,它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文。
//https认证
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultH
__block NSURLCredential *credential =
if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseC
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeCancelAuthenticationC
disposition = NSURLSessionAuthChallengePerformDefaultH
if (completionHandler) {
completionHandler(disposition, credential);
鉴于篇幅,就不去贴官方文档的翻译了,大概总结一下:
之前我们也有一个https认证,功能一样,执行的内容也完全一样。
区别在于这个是non-session-level级别的认证,而之前的是session-level级别的。
相对于它,多了一个参数task,然后调用我们自定义的Block会多回传这个task作为参数,这样我们就可以根据每个task去自定义我们需要的https认证方式。
//当一个session task需要发送一个新的request body stream到服务器端的时候,调用该代理方法。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
NSInputStream *inputStream =
//有自定义的taskNeedNewBodyStream,用自定义的,不然用task里原始的stream
if (self.taskNeedNewBodyStream) {
inputStream = self.taskNeedNewBodyStream(session, task);
} else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
inputStream = [task.originalRequest.HTTPBodyStream copy];
if (completionHandler) {
completionHandler(inputStream);
该代理方法会在下面两种情况被调用:
如果task是由uploadTaskWithStreamedRequest:创建的,那么提供初始的request body stream时候会调用该代理方法。
因为认证挑战或者其他可恢复的服务器错误,而导致需要客户端重新发送一个含有body stream的request,这时候会调用该代理。
//周期性地通知代理发送到服务器端数据的进度。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
// 如果totalUnitCount获取失败,就使用HTTP header中的Content-Length作为totalUnitCount
int64_t totalUnitCount = totalBytesExpectedToS
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
if(contentLength) {
totalUnitCount = (int64_t) [contentLength longLongValue];
if (self.taskDidSendBodyData) {
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
就是每次发送数据给服务器,会回调这个方法,通知已经发送了多少,总共要发送多少。
代理方法里也就是仅仅调用了我们自定义的Block而已。
未完总结:
其实写了这么多,还没有讲到真正重要的地方,但是因为已经接近简书最大篇幅,所以只能先在这里结个尾了。
如果能看到这里,说明你是个非常有耐心,非常好学,非常nice的iOS开发。楼主为你点个赞。那么相信你也不吝啬手指动一动,给本文点个喜欢...顺便关注一下楼主...毕竟写了这么多...也很辛苦...咳咳,我不小心说出心声了么?
最后,万一如果本文有人转载,麻烦注明出处~谢谢!
千里之行,始于足下
接着上一篇的内容往下讲,如果没看过上一篇内容可以点这: AFNetworking到底做了什么? 之前我们讲到NSUrlSession代理这一块: 代理8: 这个代理就是task完成了的回调,方法内做了下面这几件事: 在这里我们拿到了之前和这个task对应绑定的AF的dele...
写在开头: 作为一个iOS开发,也许你不知道NSUrlRequest、不知道NSUrlConnection、也不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking。 大多数人习惯了只要是请求网络都用AF,但是...
写在开头: 大概回忆下,之前我们讲了AFNetworking整个网络请求的流程,包括request的拼接,session代理的转发,response的解析。以及对一些bug的适配,如果你还没有看过,可以点这里:AFNetworking到底做了什么?AFNetworking到...
建议去看原文 AFURLSessionManager _AFURLSessionTaskSwizzling 当时看这个私有类的时候一直想不通为什么要弄一个这样的类呢?首先看了AFNetworking给出的解释https://github.com/AFNetworking/A...
写在开头: 大概回忆下,之前我们讲了AFNetworking整个网络请求的流程,包括request的拼接,session代理的转发,response的解析。以及对一些bug的适配,如果你还没有看过,可以点这里:AFNetworking到底做了什么?AFNetworking到...
第一天参加写作群的活动,卡在第一时间就交了作业,特别兴奋。还有更让人兴奋的是感觉自己终于愿意看到自己的阴暗面了。
之前觉得阴暗面,应该是一个面带黑面具,身披黑斗篷的一个怪物,直到有次在梦里,我看到了自己的阴暗面才知道它其实很可爱。
梦里讲的是一个学能量治疗...
在上一篇《历时11个昼夜,我死磕了这1个作品(上)》中,我主要分享了自己从临时接收新任务挑战,到死磕完成一个还算让人满意的视频作品的体验过程。 当然,我也知道在这个阅读速度以“一屏”为单位、追求于己有用的干货时代,大部分人并无心思理会别人的体验和抒发。其实对自己也一样,所以...
1.我发觉我是一个坏小子,你爸爸说的一点也不错。可是我现在不坏了,我有了良心。我的良心就是你。真的。 2.我和你分别以后才明白,原来我对你爱恋的过程全是在分别中完成的。就是说,每一次见面之后,你给我的印象都使我在余下的日子里用我这愚笨的头脑里可能想到的一切称呼来呼唤你。比方...
课堂笔记 ①真正的倾听,不仅仅是身体的靠近,更是心与心的靠近。 ②我们必须把自己“变成五岁的孩子”,用一颗“赤子之心”进入生命,它是情感的、灵性的、直觉的。 ③真正的倾听,不是靠理性的头脑,而是需要一种生命的投入,去解读对方的生命故事。 ④去看他“有”什么,而不是去看他“没...
我幻想中的生活,有一个爱自己的人,陪在自己身边,和TA一起慢慢变老,早起有可口的饭菜,一切安祥和谐;黄昏下和自己喜欢的人一起漫步于小道,享受阳光;睡前有人亲吻你的额头并对你说晚安,好梦。日子就这样日复一日的过下去,我们也一直就这样走下去,该有多好! 1 让我非常感动的一个大...

我要回帖

更多关于 出血是什么意思 的文章

 

随机推荐