记一次技术调研一-iOS-应用实现-gRPC-调用

问题在手机应用的开发中,通常会将复杂的业务逻辑层实现放在服务端,客户端仅负责表现层。但是对于某些手机应用而言,业务逻辑的实现位于服务端反而是不安全的或是不合理的,而是需要将其逻辑直接在手机端实现。 目的 面对不同系统的手机客户端,单独重复实现相同的业务逻辑,并非最佳实践。如何通过第三方语言 Go 语言将业务逻辑封装成库的形式,并以静态打包的方式提供给不同系统的手机客户端使用,是本次调研的目的。 理想目标图: 具体调研内容包括: [x] iOS 应用实现 gRPC 调用[x] Android 应用实现 gRPC 调用[ ] GoMobile SDK 在 iOS & Android 上的集成[ ] GoMobile SDK 在 iOS & Android 上的边界[ ] C/S 架构 or 静态库其中关于 gRPC 在 iOS 与 Android 的实现,本身官方就已经提供了样例。本次调研会用到相关内容,所以将其作为调研的一部分记录下来,方便后来者阅读。调研中所有涉及的项目代码均存放于: liujianping/grpc-apps 仓库中, 需要的朋友可以直接下载测试。 原文发布在我的个人站点: GitDiG.com. 原文链接:iOS 应用实现 gRPC 调用 .1. 环境安装作为一名非专职 iOS 的程序员,经常需要调研陌生的技术或者语言。首先是要克服对于未知的畏惧心理。其实很多东西没那么难,只是需要开始而已。 为了完成目标调研,开始第一部分的调研工作。以文字形式记录下来,方便后来者。 1.1 XCode 安装没什么好说的,直接 AppStore 下载安装。有点慢,一边下载一边准备其它环境。 1.2 Cocoapod 安装类似与其它语言的第三方库管理工具。也没什么好说的,登录官网,按说明安装。 ...

July 4, 2019 · 3 min · jiezi

ffmpeg开发知识点回顾

视频花屏/卡顿原因如果GOP分组中的P帧丢失会造成解码端的图像发生错误为了避免花屏问题的发生,一般如果发现P帧或者I帧丢失,就不显示本GOP内的所有帧,直到下一个I帧来后,重新刷新图像。时间基tbr: 帧率tbn:time base of streamtbc:time base of codec时间戳PTS: Presentation timestampDTS: Decoding timestampI(intra)/B(bidirectional)/P(predicted)帧时间戳顺序实际帧顺序:I B B P存放帧顺序:I P B B解码时间戳:1 4 2 3展示时间戳:1 2 3 4从哪儿获得PTSAVPacket中的PTSAVFrame中的PTSav_frame_get_baset_effort_timstamp()计算当前帧的PTSPTS=PTS * av_q2d(video_stream->time_base)av_q2d(AVRotional a){ return a.num/(double)a.den }计算下一帧的PTSvideo_clock: 预测的下一帧视频的PTSframe_delay: 1/tbraudio_clock: 音频当前播放的时间戳多媒体格式转换ffmpeg -i out.mp4 -vcodec copy -acodec copy out.flv -i:输入文件vcodec copy:视频编码处理方式acodec copy:音频编码处理方式该条命令的作用是将视频文件out.mp4格式转换为out.flv,音频编码方式保持不变,视频编码方式保持不变。 录音命令ffmpeg -f avfoundation -i :0 out.wav:0 代表音频设备该条命令表示使用AVfoundation框架录制一段音频数据,数据来源是麦克风,输出文件是out.wav,录制完成之后,使用ffplay out.wav命令进行播放。 录屏命令ffmpeg -f avfoundation -i 1 r 30 out.yuv -f: 指定使用AVfoundation采集数据-i: 指定从哪儿采集数据,它是一个文件索引号-r:指定帧率该条命令表示使用AVfoundation框架,以30帧每秒的帧率录制屏幕,输出文件是out.yuv。使用ffplay可以进行播放,但是播放的时候需要制定屏幕尺寸和录制的数据格式,否则播放不出来。 ffmpeg滤镜命令ffmpeg -i in.mov -vf crop=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4 ...

July 4, 2019 · 1 min · jiezi

360移动端性能监控实践QDASAPMiOS篇

一、背景360是一家注重用户体验的公司,公司的口号是用户至上。在这么一个注重用户体验的氛围里,app的性能问题无疑是被重点关注的,同样也是造成用户流失的罪魁祸首之一。性能问题主要包含:崩溃、网络请求错误或者超时、UI响应速度慢、主线程卡顿、CPU和内存使用高、耗电量大等等。大多问题的原因在于开发者错误地使用了线程、锁、系统函数、编程规范问题、数据结构等等。解决这个问题的关键在于尽早发现和定位问题。 目前国内各大公司都有自己的一套app性能监控体系,360也不例外。在平时开发和用户反馈的问题中,对性能问题进行了归纳总结出了5个分别是:资源文件如何掌控、 版本质量如何保证、线上问题如何排查、开发阶段如何防止性能衰减、性能监控是否能真实反映用户体验。同时学习了业内相对完善的性能监控平台上的功能原理。从而得出了360在iOS端移动端线上性能监控方案——QDAS-APM。 二、功能和原理QDAS-APM已经实现以下功能监控: 页面渲染时长主线程卡顿网络错误FPS大文件存储CPU内存使用Crash启动时长下面按照功能详细介绍实现细节和原理。另外用户在使用app时会感知性能问题,我们可以将其转化为具体的性能监控指标。 (1)页面渲染时长什么是页面渲染时长,其实是从页面初始化到用户能看到页面效果的时间长度。所要了解的指标有生命周期系统方法执行时长、页面类名、启动类型、执行耗时和插件名称。关键度量的指标是执行耗时,不同的方法和步骤产生的耗时在用户能接受的范围内才被认为是合理。其他指标则是起有关联性作用和定位问题。直接hook UIViewController的方法明显是不可行的,原因是它只作用在UIViewController的方法,而app中大部分都采用继承UIViewController的方式。 这里列出两个可行性方案: 1、采用KVO,我们知道对于任意对象进行KVO操作时,系统都会帮你动态的创建一个复制类,同时实现了setter getter函数的覆盖和函数实现。 2、采用runtime遍历所有类为UIViewController的子类,再进行动态替换。 这两种方式更加推荐第一种,出于对兼容性、性能、以及能够直接获取UIViewController的子类的IMP。那具体如何实现呢?总结归纳为三步骤: 1、需要创建一个UIViewController的类别,对UIViewController的实例进行KVO,目的是让KVO创建需要监控UIViewController的子类。 2、添加需要监控的方法,在KVO创建出来的子类添加需要Swizzle的方法对应的SEL及其IMP。目的是控制调用原来类的方法时机。 3、在UIViewController的实例销毁时,在dealloc方法里将KVO监听移除,不然会导致Crash。 举个例子:我们以监控到qh_viewDidLoad方法举例: static void qh_viewDidLoad(UIViewController *kvo_self, SEL _sel){ Class kvo_cls = object_getClass(kvo_self); Class origin_cls = class_getSuperclass(kvo_cls); // 注意点 IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel)); void(*func)(UIViewController *, SEL) = (void(*)(UIViewController *, SEL))origin_imp; CFAbsoluteTime startTime = CACurrentMediaTime(); func(kvo_self, _sel); CFAbsoluteTime endTime = CACurrentMediaTime(); NSTimeInterval duration = (endTime - startTime)*1000; NSLog(@"Class %@ cost %g in viewDidLoad", [kvo_self class], duration);}会有一种特殊情况,如果KVO生成的类中对应的类原本没有实现监控方法,那么会造成什么后果呢?KVO内部生成的NSKVONotifying_ViewController实际上时继承自ViewController,因此直接取出对应的IMP调用。 ...

July 2, 2019 · 2 min · jiezi

直播知识结构整理

文章内容来自于逻辑教育公开课。 总结:以上主要是对音视频开发中用到的相关知识进行了一个整理,囊括了流媒体知识、直播技术点、直播架构、音频知识点。

July 2, 2019 · 1 min · jiezi

flutter手势动作有效区域占满容器

问题描述:自定义按钮时,GestureDetector明明是占满容器的,但是点击容器内空白区域不能触发点击事件,而点击容器内的文字可以正常触发。解决方案:GestureDetector有个behavior属性,设置behavior: HitTestBehavior.translucent即可,它默认是HitTestBehavior.deferToChild

July 1, 2019 · 1 min · jiezi

FFmpeg组织结构

学习ffmpeg之前,我们应该对ffmpeg的组织结构有一个大体的了解。ffmpeg安装好之后,使用cd /usr/local/ffmpeg命令进入到ffmpeg目录下,会看到ffmpeg的4个主要目录。 binincludelibshare下面是ffmpeg的组织结构图: 一、bin目录bin目录下主要是编译好的三个工具,ffmpeg、ffplay、ffprobe。ffmpeg主要是提供对音视频进行抽取、滤镜、裁剪等等各种操作的。ffplay主要提供音视频的播放。ffprobe主要是查看音视频的各种信息的。 二、include目录构成和说明下方是ffmpeg的include目录下的组织结构和说明。 目录说明libavcodec提供了一系列编码器的实现libavformat实现在流协议,容器格式及其基本IO访问。libavutil包括了hash器,解码器和各种工具函数。libavfilter提供了各种音视频过滤器。libavdevice提供了访问捕获设备和回放设备的接口。libswresample实现了混音和重采样。libswscale实现了色彩转换和缩放功能。三、lib目录lib目录下的文件,基本上include目录下对应文件的.a & dylib文件。lib目录下的内容如下入所示: 四、share目录share目录下又分为ffmpeg和man两个目录。ffmpeg有一个重要的目录examples,里面有一些示例代码,学习者可以拜读借鉴。man目录下有man1和man3,不知道具体是干什么的。

June 29, 2019 · 1 min · jiezi

没有Mac苹果电脑如何去申请iOS证书

现在很多Windows开发平台,可以直接开发安卓及iOS应用。 但一般如果需要上架App Store需要用苹果电脑,如果没有比较麻烦。 Appuploader苹果上架辅助助,可以辅助开发者在Windows申请iOS证书及上传ipa,方便快捷! 工具官网 http://www.applicationloader....

June 28, 2019 · 1 min · jiezi

移动端硬解关键流程梳理

介绍移动端Android/iOS硬解用法的文章有很多,本文将以笔者在实际开发工作中的经验为基础,抽出几个比较关键的部分来跟大家分享,旨在解决实际工作中可能遇到的花屏、(半边)绿屏、播放不完整等问题。本文将以目前广泛应用的H.264编码的视频为例来说明,主要包含:H.264码流数据结构说明、解码器的初始化、seek、前后台切换、无缝分辨率切换、播放结束时的处理以及iOS如何避免下半部分绿屏的问题。 一、H.264码流数据结构说明1. 理解码流数据结构的重要性我们讲支持硬解,提高硬解兼容性,实际上就是对码流数据的结构进行处理以符合平台硬解要求,因此对码流数据结构的理解是必不可少的。2. SPS/PPS与IDR帧SPS(Sequence Parameter Set)序列参数集、PPS(Picture Parameter Set)图像参数集,包含了图像编码的各种参数信息,是作为解码器初始化所必须的参数信息。IDR(Instantaneous Decoding Refresh)帧,也就是即时解码刷新帧,直观意思就是解码器在接收到IDR帧后会刷新参考帧缓存。IDR帧前后的视频帧不会有任何参考关系,解码器可以从任何一个IDR帧开始解码。3. H.264的NAL单元NALU结构图示: H.264标准中,视频流是由NAL(Network Abstraction Layer)单元组成的(简称NALU),每个NALU中可能是IDR图像、SPS、PPS、non-IDR图像等。上图中示意的NALU单元是以startcode方式分割的,关于NALU的分割方式将在后面说明。 另外,NALU内容中添加了防竞争字节,也就是说在一个NALU中,我们不可能再找到匹配的startcode.H.264流的NALU组成图示 从上图可以看到,一个视频帧中可能可能包含多个NALU, 此时可以称该视频帧为多slice视频帧(一个NALU中包含该视频帧的一个slice)。NAL Header的结构说明 其中nal_unit_type是我们关心的字段,该字段标识了当前NALU的类型,我们可以通过将NALU中第一个字节&0x1F的方式来得到NALU类型。NALU类型的具体定义如下图所示:NALU类型定义 其中5代表上面提到的IDR帧数据,7、8分别代表SPS/PPS数据。 AVCC与Annex-BH.264码流分为AVCC与Annex-B两种组织格式。• AVCC格式 也叫AVC1格式,MPEG-4格式,字节对齐,因此也叫Byte-Stream Format。用于mp4/flv/mkv等封装中。 • Annex-B格式 也叫MPEG-2 transport stream format格式(ts格式), ElementaryStream格式。用于TS流中(以及使用TS作为切片的hls格式中)。这两种格式的区别有两点: (1)NALU的分割方式不同; (2)SPS/PPS的数据结构不同。• AVCC格式使用NALU长度(固定字节,字节数由extradata中的信息给定)进行分割,在封装文件或者直播流的头部包含extradata信息(非NALU),extradata中包含NALU长度的字节数以及SPS/PPS信息。• Annex-B格式使用start code进行分割,start code为0x000001或0x00000001,SPS/PPS作为一般NALU单元以start code作为分隔符的方式放在文件或者直播流的头部。AVCC格式的extradata格式定义在“ISO_IEC_14496-15"文档中,Annex-B格式的SPS/PPS定义可以在"ISO_IEC_14496-10"文档中找到。MediaCodec与VideoToolBox使用的数据格式Android的硬解码接口MediaCodec只能接收Annex-B格式的H.264数据,而iOS平台的VideoToolBox则相反,只支持AVCC格式。这就导致:• 在Android平台硬解播放flv/mp4/mkv等封装的视频时,需要将AVCC格式的extradata以及NALU数据转为Annex-B格式;• 在iOS平台播放ts或ts切片的hls视频时,需要将Annex-B格式的SPS/PPS NALU转为AVCC格式的extradata,以及将其他以size方式分割的NALU转为start code方式。 二、解码器的初始化及数据输入初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式之外,还需要配置一些额外的信息。 对于H.264视频,需要填充的就是我们前面提到的SPS/PPS信息。1. Android平台MediaCodec的初始化我们需要将Annex-B格式的两个SPS/PPS NALU单元通过setByteBuffer方法,以"csd-0"为名称(或SPS设为"csd-0", PPS设为"csd-1")设置到MediaFormat对象中,并调用configure接口配置到MediaCodec中去。MediaCodec设置SPS/PPS信息的示例代码MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);// extradata中是Annex-B格式的SPS、PPS NALU数据mediaFormat.setByteBuffer("csd-0", extradata);// ...mediaCodec.configure(mediaFormat, surface, 0, 0);// ...如上节所述,对于mp4/flv/mkv等封装,我们得到的是AVCC格式的extradata,需要先将该extradata转换为Annex-B格式的两个NALU, 然后用startcode进行分割。 Android平台在配置解码方式时,最好使用MediaCodec直接渲染到Surface的方式,一是可以避免不同硬件平台繁杂的YUV格式兼容,二是在解码渲染高分辨率的视频时可以有非常明显的效率提升。2. iOS平台VideoToolBox接口的初始化VideoToolBox针对AVCC格式和Annex-B格式的SPS/PPS信息设置,分别提供了两个方法:• CMVideoFormatDescriptionCreate: 可以设置AVCC格式的extradata信• CMVideoFormatDescriptionCreateFromH264ParameterSets: 用来设置Annex-B格式的SPS/PPS NALU信息(需要去掉startcode)需要注意,iOS平台不支持隔行H.264视频的解码,需要在创建videoToolBox前从SPS中判断当前视频是否隔行编码。3. 数据格式的转换如前所述,Android平台只接受Annex-B格式以startcode分割的H.264 NALU;iOS平台则相反,只接受AVCC格式以size分割的NALU. 在原视频流格式不匹配时需要进行相应的转换。iOS还有以下的一些限制需要留意:(1)如果源视频流本身已经是AVCC格式,但NALU size的大小是3个字节,而非4字节时,需要转为4字节格式。具体的话,需要先更改extradata中标识NALU size的字段,然后每个视频帧中的NALU size都要改成4个字节。(2)如果一个视频帧内有多个NALU(多slice),那必须将这些NALU打包到一个CMSampleBuffer中,一次性送给解码器。 ...

June 28, 2019 · 1 min · jiezi

为何专注于流媒体领域PPIO-技术揭秘续篇

在各种音视频应用充斥着市场的时候,毫无疑问,流媒体领域将会非常适合区块链技术进行场景落地。在上一篇文章中,我们主要讨论了 PPIO 的 PCDN 架构,接下来将介绍 PPIO 的中 P2SP 的下载逻辑和 IaaS 层的流量计数。 下载逻辑 上图为 PPIO 的 CDN 和 P2SP 的传输架构图。这里将会重点讲解 PPIO 中 P2SP 的下载逻辑,它主要分三个部分,Buffer 管理,下载状态机,和下载算法。接下来会对这三部分一一解释。 Buffer 管理 Buffer 管理,即理解为本地设备管理着资源情况,从而决定需要下载的 Piece 的优先级。Buffer 管理也是和实际应用场景是相关的,对于流媒体来说,存在一个视频播放位置,播放位置附近的内容就是紧急内容,该内容则会被优先下载。 #1 普通文件下载的 Buffer 管理逻辑 文件下载的 Buffer 管理相对简单,因为没有具体的播放位置,即内容在紧急程度上没有区别。下载算法将采用稀缺优先的逻辑,优先下载网络中稀缺的 Piece。Piece 越稀缺,其下载的优先级也就越高。如下图: 在示意图中,我们假设所有 Peer 都是一样的,而因为 CDN 节点的存在,实际情况则会复杂得多。CDN 可以被认为比较优质的,但是它不适合请求分散的碎片数据,更适合下载一段连续的数据。之所以将稀缺性作为普通文件的 Buffer 管理的指标,是为了增加 P2P 网络的资源健康度,并且让数据尽快在 P2P 网络中传播。 #2 流媒体的 Buffer 管理逻辑 和普通文件下载不同的是,流媒体下载存在具体播放位置,为了保证视频的观看体验,越接近播放位置的数据将会优先进行下载。 设计 PPIO 的时候,我们根据视频播放位置,在流媒体下载的过程中,将其分为多个区间,越靠近播放位置的区间,下载优先级越高。 已过区间:视频播放位置之前的数据,无需进行下载。 紧急区间:需要立即播放的数据,在此区间的数据将采用极端下载策略,旨在以最快的速度获取数据。Piece 越靠近播放位置,下载优先级越高。这种策略更加依赖于 CDN 和超级节点,尽管有重复下载的风险,该区间将同时进行 P2P 下载,即在第一时间从多个 Peer 下载数据。 ...

June 28, 2019 · 2 min · jiezi

WebRTC源码目录结构

以下是对WebRTC入门学习课程的源码目录结构的整理,方便后续学习时进行查询。 源码对应的目录结构目录对应的功能apiWebRTC接口层,浏览器都是通过该接口调用WebRTCcall数据流的管理层,call代表同一个端点的所有数据的流入流出vedio与视频相关的逻辑audio与音频相关的逻辑common_audio音频算法相关common_vedio视频算法相关media与多媒体相关的逻辑处理,如编解码的逻辑处理logging日志相关module最重要的目录,子模块pcPeer Connection,连接相关的逻辑层p2p端对端相关代码,stun,turnrtc_base基础代码,如线程、锁相关的统一接口接口代码rtc_tool音视频分析相关的工具代码tool_webrtcWebRTC测试相关的工具代码,如网络模拟器system_wrappers与具体操作系统相关的代码。如CPU特性,原子操作等stats存放各种数据统计相关的类sdk存放android和iOS层代码。如视频的采集,渲染重要模块module对应的目录结构目录对应的功能audio_coding音频编解码相关代码audio_device音频采集与音频播放相关的代码audio_mixer混音相关的代码audio_processing音频前后处理相关的代码bitrate_controller码率控制相关的代码congestion_controller流控制相关的代码desktop_controller桌面采集相关的代码pacing码率探测及平滑处理相关的代码remote_bitrate_estimator远端码率估算相关的代码rtp_rtcprtp/rtcp协议相关的代码vedio_capture视频采集相关的代码vedio_coding视频编解码相关的代码video_processing视频前后处理相关的代码

June 28, 2019 · 1 min · jiezi

iOS-App渠道统计跟踪方法

说起 iOS 的渠道统计,不少人会想到苹果官方的 App 分析功能(iTunes Connect),但实际操作中我们会发现,这个服务的统计维度还不够全面,许多广告主和运营人员更关心的是各个推广渠道实际带来的安装量、注册量等数据,毕竟这对渠道引流的分析价值更大。iOS的“渠道”通常是指那些在其它 App 或者网页内部,提供到达 App Store 的链接的页面。因此,在 iOS 中追踪发行渠道,主要是追踪进入 App Store 相关页面的渠道信息。 从技术角度来看,也就是在用户首次下载时不仅要获取下载来源,还要实现参数传递,简单来说,就是用户第一次下载后,我能得知后续的注册、活跃、付费等操作行为。或者在此基础上,实现场景还原,帮助用户在首次打开 App 后直接跳转进指定页面,而不是首页。 方案一:苹果官方自带的统计工具 iTunes Connect 登录 iTunes Connect ,在“App 分析”中,能很方便的查看 App 的展示次数、购买量等基础数据,但无法获取更加详细的安装量、注册量等运营数据。 当然,往往 App 推广的渠道会有很多同时进行,怎么对多个渠道的来源做分析呢?同样在“App分析”的“来源”中点击“营销活动”,右上角有个“生成营销活动链接”,进入后就能自定义给每个渠道生成对应的唯一标识。 这种方法虽然可以追踪到多个渠道的来源,但存在以下几个问题: 只有当营销活动启动后超过一天时间(最长72个小时)后才能显示相关数据;至少有 5 个 App 购买量归因于此营销活动时,营销活动才会在“App 分析”中显示;统计的维度不完整,仅限“展示次数”、“App 购买量”、“销售额”、“App 使用次数”四个;iOS 8.0 及以上版本的用户可以选择是否将自己的应用使用情况的数据发送给Apple。方案二:使用 SFSafariViewController 传递参数SFSafariViewController 是 iOS 9.0 出现的,可以通过 Safari 对应的 cookier 传递参数,跨App与Safari共享数据。但是 openurl 失败率还是很高,并且有系统版本、浏览器等限制,比如微信等第三方 App 的内置浏览器就不能很好实现。 方案三:通过 IDFA 进行追踪,比如 Google Analytics常用的比如谷歌官方的 Google Analytics,它的获取原理就是通过获取设备的 IDFA ,来作为唯一标示符号,然后根据你的渠道来源提供数据,通过比对的方式进行渠道定位。弊端在于,用户重置系统,或者关闭广告跟踪的话,这种方法就会失效。 ...

June 27, 2019 · 1 min · jiezi

WebRTC介绍

一、 WebRTC是什么WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准。简单说就是一个音视频处理+及时通讯的开源库。 二、WebRTC 有哪些优点Google开源的框架(背景强大)跨平台(适合当下软件开发的趋势)用于浏览器实时传输音视频引擎(迎合当下的发展趋势)三、WebRTC应用场景音视频会议在线教育照相机音乐播放器共享远程桌面录制即时通讯工具P2P网络加速文件传输工具游戏实时人脸识别由上方列出的条目可以看出,WebRTC的应用场景十分广泛,尤其是在网路越来越发达的当下,音视频会议、在线教育、即时通讯工具、游戏、人脸识别一定是当下和未来的发展方向,跟上时代的步伐才不至于死在沙滩上。 四、 WebRTC的愿景网络传输音视频引擎内网链接音频引擎P2P传输视频引擎TURN中转 五、 WebRTC运行机制 轨与流Track(一路音频/视频就是一路轨)MediaStream (媒体流包含很多轨)WebRTC的重要类MediaStreamRTCPeerConnection(该类很重要,提供了应用层的调用接口)RTCDataChannel (非音视频数据通过它传输)六、 WebRTC目前支持的浏览器Chrome(谷歌)Safari(苹果)FirefoxEdge (微软)七、学习WebRTC的难点WebRTC庞大、烦杂门槛高,全是英文文档,对学习者是一个挑战客户端与服务器分离,增加学习难度网络屏蔽/系统的学习资料少(这个是因为一些大家都懂的原因,对学习者是一个障碍,需要翻墙)网上虽然有demo,但是网上demo错误多,难以调试通八、特此说明以上是对慕课网上WebRTC入门课程的学习资料的整理归纳。

June 27, 2019 · 1 min · jiezi

Flutter开发环境搭建配置

Futter开发环境搭建配置Flutter SDK的获取 首先,从GitHub官网下载Flutter SDK。或者,先下载git,然后安装git,打开Git Bash,在窗口中输入 git clone -b master https://github.com/flutter/flutter.git由于,谷歌的原因,我们需要镜像地址更改一下(根据官网提示:The Flutter team cannot guarantee long-term availability of this service. ): export PUB_HOSTED_URL=https://pub.flutter-io.cnexport FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn或者,使用上海交通大学的 Linux User Group : export PUB_HOSTED_URL= https://dart-pub.mirrors.sjtug.sjtu.edu.cn/export FLUTTER_STORAGE_BASE_URL= https://mirrors.sjtug.sjtu.edu.cn/然后接着输入: export PATH="$PWD/flutter/bin:$PATH"注意:$PWD为你安装Flutter的路径安装完后,在环境变量中,静Flutter的安装地址配置到Path变量中。 最后,运行flutter doctor

June 23, 2019 · 1 min · jiezi

Deeplink深度链接如何提高App转化率留存率

移动互联网时代,信息的分享传播无疑是 App 引流增长的关键,与其花费大量精力和成本找渠道、硬推广,不如从细节下手,用最快最简便的方法实现 Deeplink(深度链接)技术,打破信息孤岛、缩短分享路径、优化用户体验,最终提高流量转化率和留存率。 什么是 DeepLink(深度链接)技术 如果把 App 看成网站,那么 DeepLink 就是网站中的深入页面,比如商品购物页面、活动促销页面。简单理解,就是当用户点击手机中的某个链接时,可以帮他跳转到 App 内部中的目标页面,直接实现场景还原,而不是 App 首页,这是一种无障碍场景还原技术。 DeepLink 通常运用于App社交分享、App广告引流、App裂变活动、Web to App、分享效果统计、沉默用户唤醒等场景,对广告引流、活动推广、新闻类、电商类、游戏类、视频直播类App的引流推广和转化都有着奇效。 升级版 Deferred deeplink(延迟深度链接)技术 相比 Deeplink,Deferred deeplink 增加了一个判断,能在用户点击链接时判断设备是否安装了目标App,如果没有安装,则跳转应用市场或者浏览器中引导下载,用户安装后再次实现 Deeplink 的场景还原功能。 是否使用这两项技术的差别: 可以看到,使用了深度链接后,用户操作成本明显降低了一至两步,做运营的小伙伴都知道,在用户转化的漏斗中,每多一个步骤,漏斗的路径就会多一层,用户流失率也就随之增加。 怎样为App快速实现这两项技术 我们以 openinstall 的一键拉起功能为例,这项功能集成了深度链接中的scheme,universal link等核心技术,能完美实现 Deeplink、Deferred deeplink 的所有技术场景效果,主要特点如下: 一键拉起功能同时兼容 Android、iOS 系统,不存在操作系统技术障碍;适配了大量主流社交平台和浏览器,如微信、QQ、新浪微博、钉钉、支付宝等;用户已安装该 App 的情况下,点击页面链接可直接拉起本地 App,并自动进入目标页面;用户未安装该 App 的情况下,点击页面链接会引导到应用商店或默认浏览器下载,安装后首次打开将自动进入目标页面;该功能目前以及免费开放给所有开发者。openinstall:https://www.openinstall.io/pu... 不仅如此,在深度链接的基础上,还能完善出更多定制化场景需求,比如: 能够根据需求满足开发者业务逻辑,如:既可实现下载优先,也可以实现拉起优先;在社交分享页面上,openinstall 还能同时实现携带参数安装,帮助开发者实现渠道来源统计,具体到用户是被哪篇文章吸引来的、哪个广告页面的转化率最高、哪件产品付费效果最好;甚至可以在业务上实现【社交平台快速下载 App、免填邀请码、App 安装后自动加群加好友、下载 App 后自动登录账号】等功能,进一步完善用户体验和关系链。哪些产品或场景非常适合深度链接 社交平台我们以微信为例,产品以电商类京东 App、资讯类今日头条 App 为例,可以参考他们的结合效果: 显而易见,电商类、资讯类、旅游类、服务类 App 结合社交分享传播具有非常大的价值,此外,游戏类 App 甚至可以利用一键拉起功能点击分享链接直接进入 App 内的游戏房间。 ...

June 21, 2019 · 1 min · jiezi

FFmpeg常用命令合集

ffmpeg查询命令命令注释-version显示版本号-demuxers显示可用的demuxers-muxers显示可用的muxers-devices显示可用的设备-codecs显示所有编解码器-decoders显示可用的解码器-encoders显示可用的编码器-bsfs显示比特流filter-formats显示可用的格式-protocols显示可用的协议filters显示可用的过滤器-pix_fmts显示可用的像素格式-sample_fmts显示可用的采样格式-layouts显示channel名称-colors显示识别的颜色名称常用命令视频裁剪滤镜(播放器大小裁剪): ffmpeg -i killer.mp4 -vf crop=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4视频裁剪(按时间裁剪): ffmpeg -i killer.mp4 -ss 00:02:00 -t 10 ./vedio-result/1.ts视频拼接: ffmpeg -f concat -i input.txt concat_out.mp4(input.txt内部是文件列表,格式是: file 'fileName')视频转图片命令: ffmpeg -i 1.ts -r 1 -f image2 result-image/image-%3d.jpeg图片转视频:ffmpeg -i image-%3d .jpeg -out.mp4ffmpeg录屏命令ffmpeg -f avfoundation -i 1 -r 30 out.yuv -f: 指定使用avfoundation采集数据-i: 指定从哪儿采集数据,它是一个文件索引号-r:指定帧率播放录屏的命令: ffplay -s 2880X1800 -pix_fmt uyvy422 out.yuv -s: 指定分辨率-pix_fmt :录制时的格式查看支持的设备列表:ffmpeg -f avfoundation -list_devices true -i ""录音命令:ffmpeg -f avfoundation -i :0 out.wav多媒体格式转换ffmpeg -i out.mp4 -vcodec copy -acodec copy out.flv ...

June 20, 2019 · 1 min · jiezi

对Android和iOS项目中的模块结构和类结构设计的探讨

现有的代码规范缺少探讨的部分1.1 关于项目的代码结构通常来说,一个项目由多个模块组成;一个模块由多个类组成;一个类由多个方法组成;一个方法由多条语句组成;按照代码粒度从大到小,可以划分为三个级别,并且分别对应IDE(XCode、Android Studio)的三个不同的视图: 项目结构,包括模块和类的组织——红框视图;类结构,包括属性和方法的组织——绿框视图;方法结构,包括变量和语句的组织——蓝框视图;Xcode的三种视图(如下图) Android Studio的三种试图(如下图) 1.2 关于现有代码规范对于Android和iOS的现有代码规范,有大量的公司和大神进行了细致的探讨。其中,最有影响力的,笔者认为是苹果爸爸和阿里妈妈的两个版本——《Introduction to Coding Guidelines for Cocoa》、《阿里巴巴Android开发手册》。强烈推荐大家进行学习。1.3 本文探讨的代码规范但笔者发现,大部分现有的代码规范,讨论的主题都是命名规范、书写格式要求、特定技术点的处理要求等内容,关注点都是项目中具体代码细节,也就是蓝框部分的内容。但对于更大粒度的红框和绿框部分,也就是项目中模块和类的组织以及对类中方法的组织,讨论的并不多。而在实际工作中,一位新员工加入公司,当然必须先学习公司的代码规范,明晰代码编写时需要注意的事项。然而,新员工加入实际项目后,首先看的其实并不是代码细节,而是项目代码的组织架构。一个具有良好组织结构的项目代码,可以让新员工在不看代码细节的前提下,直接了解项目的整体架构。本文介绍的内容,就是通过对红框和绿框部分的设计,来探讨如何搭建一个“具有良好组织结构的项目”。 模块结构设计的探讨一个好的项目结构,应该对模块和类的组织进行设计,实现不看实现细节,能够大概了解 项目功能 和 类功能;以下,分别使用Android和iOS两个例子进行说明。背景介绍本文举例的项目源码在这两个GitHub项目上:polyv-ios-cloudClass-sdk-demo、 polyv-android-cloudClass-sdk-demo Demo项目是一个直播项目,包括两个页面,第一个是登录页,第二个是观看页。运行项目,会先进入登录页,登录成功后,会进入观看页。2.1 iOS的例子好的项目结构(如下图) 不好的项目结构(如下图) 好的项目结构,能够不看具体代码,而是能够 随着项目结构的逐层展开 直接获取到如下信息:在项目中,有 Login(登录)和 Watch(观看)两个模块;在 Login 模块 的主页面是 PLVLoginViewController(登录页);在 Watch 模块 的页面包括 PLVLiveViewController(直播观看页) 和 PLVVodViewController(点播观看页);在 Watch 模块有两个子模块,分别是 Media(视频区)和Charroom(聊天室);在 Media(视频区)模块中,看到有三种 MediaViewController;看命名差异,可以发现是有两个维度划分的,分别是 Normal / PPT 、Live / Vod ;在 Base 文件夹中,看到 PLVBaseMediaViewController ,猜测是三种 MediaViewController 的父类;在 Base 文件夹中,看到 PPT 和 Live 两个文件夹,结合其中的 PLVBaseMediaViewController+PPT 和 PLVBaseMediaViewController+Live 两个catagory,可以猜测是在 父类 PLVBaseMediaViewController 的基础上增加 PPT 和 Live 两个维度的功能扩展;一些建议把 功能模块所使用的resource 放在 功能模块目录 下,是一个好的处理; ...

June 19, 2019 · 1 min · jiezi

淼瀛正式推出MoAir-物体识别SDK-小白用手机即可完成机器学习

从20世纪60年代,由MIT的计算机教授组织了第一个面向本科生的Summer Project,经历了20世纪50年代初到90年代,尝试用创建三维模型方法去做物体识别;又走过了20世纪90年代,只从图像本身考虑的appearance based techniques,即图片的抽象描述;直到2000年之后,物体识别领域终于有了飞跃式的发展。 中小企业开发者痛点 市场积极讯号成鲜明对比 随着人工智能、大数据和深度学习等等技术的不断发展,物体识别技术在各行业中的应用也越来越频繁,越来越重要。 譬如,工件的自动检测与识别、产品质量的自动检测、食品的自动分类、智能车的自主导航与辅助驾驶、关键地域的保安监视等等。涵盖机器人视觉理解、新零售、智能家居等等多个领域,进一步为企业提高了生产效率,节省了时间、资金成本。 然而,随着应用领域的广泛,对于中小企业开发者而言,物体识别技术的痛点也应运而生。 首先,成本投入过高。如,若构建计算机视觉系统,必须由大量人工参与,增加了投入成本之余,模型精度的效果往往也是差强人意。 其次,必须专业人士参与。由于建模流程繁琐,非专业人员所不能及,且需要的时间过长,拖延了业务落地,无形中又加大了成本。 与此同时,市场也传来了积极的讯号。 公开数据显示,KBV Research发布“全球图像识别市场(2016-2022)”报告表示,截至2022年,全球物体识别(商品识别、车辆识别等)将达到94.5亿美元,年复合平均增长率在20.3%左右。 而在国内,重磅数据也在“中国图像识别市场(2016-2022)”报告中认为,截至2022年,国内图像识别市场规模预计达11.6亿美元左右。年复合平均增长率在18.1%左右。占全球市场平均为11.6%左右。 一面是亟待解决的痛点,一面是庞大的市场需求。可以说,冰与火的交集将中小企业开发者裹挟在内,望叹莫及。 而正当各中小企业开发者一筹莫展时,行业新秀淼瀛的浮出水面,在完美契合了市场发展之余,更是成为了中小企业开发者的“及时雨”。 全球首个“实用化”MoAir物体识别技术问世 作为一家专注于人工智能以及物体识别,成立仅不足三年的科技公司,淼瀛凭借强大的研发团队,成功研发出“实用化”的MoAir物体识别SDK。 据了解,MoAir物体识别是专为中小企业开发者打造的可轻松实现对物体精准识别的SDK开发包,使开发者能够更加灵活、便捷、准确性地实现对物体的识别及反馈,即时便可为人们呈现出一个虚拟与现实高度融合的效果。 功能上,MoAir物体识别SDK的四大特点可助力开发者高效化地完成机器学习和物体识别。 一、全自动化建模流程,全程无需人工干预跨平台,开发者可以在iOS/Andriod,Linux,Javascript Browser等等平台进行。依托强大的研发团队,数以百次的试验,淼瀛成功解决了各种兼容性等问题,排除了开发者的后顾之忧。 在建模过程中,结合AI独家算法,MoAir物体识别SDK采用了全自动匹配最优算法,使整个过程彻底实现了最优化算法匹配。并且,产品本身自动决定神经网络各层的权重,实现了全自动学习、无需人工干预,直接为中小企业开发者降低了建模成本,即使是非专业人士也可搭建专业的AI模型,轻松实现机器学习和物体识别。 二、纯GUI操作,学习快速且稳定 全程可视化操作,从素材采集、数据标注、训练建模,到识别验证,可视化的操作界面让工作更加便捷易用。同时,MoAir通过神经网络层数的最优化,达到了精度和速度的最佳平衡点,在对图像全体进行识别时,有效降低了背景的误识别率。且在训练过程中,速度快速稳定,200个分类的训练在30分钟之内即可完成。 三、CMS后台,更便捷、灵活 MoAir CMS(Content Management System),专门为开发者提供便利的后台管理,可以随时管理、查看或者建立自己的素材、标签、模型、展示效果等内容。 四、丰富的输出效果展示,超出人们的想象力 在MoAir中,识别端的输出效果有无限可能性,文本、图片、网页、音视频、动画……等等,完全超出了人们的想象力,想要的效果都可以通过简单操作与模型进行绑定,最终呈现给用户。 由此可以看到,MoAir物体识别算法以自动决定神经网络各层的权重,全自动学习,无需人工干预,不但极大的降低了业务落地门槛、导入成本,用户还可以进行跨平台的物体识别应用开发,速度快且稳定,以及MoAir所有的学习建模操作均可在App或Web上通过可视化界面完成。其在彻底打破了行业痛点之余,更是进一步为企业节省了成本等等优势的存在,也正是该产品还未发布便受到业界争相关注的原因所在。 当然,更令人为之赞叹的是MoAir物体识别的多场景覆盖,譬如在智慧餐饮、新零售、智能家居、无人支付、机器人视觉理解及物流追踪等场景之下,实用化早已得到了验证,令业务落地更为便捷。不得不说,这一具备高效、全能、低门槛、低导入成本等特征的MoAir物体识别技术的诞生,也召示着淼瀛致力于“AI and AR for Everyone”的新时代已然开启。而伴随着MoAir物体识别SDK的正式问世,淼瀛也成功走进业界视野之中。 行业新秀淼瀛登场 将物体识别进行到底 如何将人工智能技术、物体识别技术更好地惠及企业和中小企业开发者,解决他们的燃眉之急,不仅是整个行业的目标所在,同样也是淼瀛的宗旨。 据了解,淼瀛(上海)信息技术公司成立于2016年底,致力于把自主研发的MoAir物体识别技术,应用于新零售、机器人和AI教育等领域。 自公司成立以来,淼瀛就专门建立了由数十位业界人工智能顶尖人士、图像识别、物体识别学者等组成的研发团队,秉承着不懈创新的精神,团队过硬的研发能力,在东京和上海设立了研发中心,通过对行业技术短板的不断创新攻克,弥补了行业研发力量的不足。 其中,值得一提的是,作为公司创始人兼CEO的何书勉,不但拥有日本京都大学信息学博士学位,还曾先后师从上林弥彦教授和田中克己教授,担任日本最大的电商平台乐天集团执行官、首席科学家等,尤其是2015年在日本创立首个地产大数据平台富士太郎再到回国创业以来,可以说,丰富的经验加之过硬的学术实力和创新精神,使得有着企业家与科学家双重身份的何教授,以对技术商业化落地的出色能力,在国际上更是享有较高的声誉和影响力。 正是如此,淼瀛也吸引着国际上市公司的争相合作。据悉,目前公司合作客户涉及海内外,包括日本、国内等等众多上市公司。 可以看到的是,随着人工智能,物体识别技术的进一步发展,伴随着淼瀛MoAir物体识别SDK的正式问世,其在推动国内乃至全球的行业发展,或将直接影响着各行业业务加快落地之时,对于中小企业无疑是最大的“放心剂”,召示着我国物体识别技术在国际崭露头角之时,也召示着我国又一颗行业科技新星冉冉升起。 正如一位业内人士所说,“人工智能时代,谁抢占第一梯队谁就是赢家。显然,市场蓬勃之时,淼瀛赶上了好机会。确切地说,是给中小企业开发者带来了先机。”

June 18, 2019 · 1 min · jiezi

UICollectionView-固定行距列表左排-来一个自定制-Layout

一般我们是使用 UICollectionViewFlowLayout , 熟悉的格子视图。也可以自定制 UICollectionViewLayout ,对于每一个列表元素,想放哪就放哪。 譬如: 固定行距列表左排 这种情况,系统的就不好直接拿来使了,需要自己定制一个 UICollectionViewLayout. 一般 new 一个 UICollectionViewLeftAlignedLayout, 继承自 UICollectionViewFlowLayout 通常要重写 UICollectionViewFlowLayout 的这两个方法, layoutAttributesForElements(in:), 这个方法需要提供,给定的矩形里面所有的格子的布局属性。给定的矩形区域,就是 UICollectioonView 的内容视图区域 contentSize. layoutAttributesForItem(at:): 这个方法需要提供,格子视图需要的具体的布局信息。我们要重写这个方法,返回要求的 indexPath 位置上格子的布局属性。 有时候也要重写这个属性: collectionViewContentSize, 一般我们是把内容区域的尺寸,作为计算属性处理的。他提供格子视图的内容区域的宽度与高度。格子视图的内容区域,不是格子视图的可见区域。 因为格子视图 UICollectionView,继承自 UIScrollView。 格子视图使用该属性,配置他作为可滑动视图 UIScrollView 的内容视图尺寸。 主要代码见如下: 其中辅助函数没有列出来,具体见文尾的 github repo. class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {// 这个函数没有做什么事情,主要是调用做事情的函数 layoutAttributesForItem,获取信息,提供出去 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var attributesCopy: [UICollectionViewLayoutAttributes] = [] if let attributes = super.layoutAttributesForElements(in: rect) { attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) }) } for attributes in attributesCopy { if attributes.representedElementKind == nil { let indexpath = attributes.indexPath // 做事情的地方 if let attr = layoutAttributesForItem(at: indexpath) { attributes.frame = attr.frame } } } return attributesCopy } // 这个函数里面,具体处理了固定行距列表左排的布局 override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { if let currentItemAttributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as? UICollectionViewLayoutAttributes, let collection = collectionView { let sectionInset = evaluatedSectionInsetForItem(at: indexPath.section) let isFirstItemInSection = indexPath.item == 0 let layoutWidth = collection.frame.width - sectionInset.left - sectionInset.right // 让每一行的第一个元素排头,分两种情况处理。这是第一种,这个 section 的第一个元素,自然是排头。 guard !isFirstItemInSection else{ currentItemAttributes.leftAlignFrame(with: sectionInset) return currentItemAttributes } let previousIndexPath = IndexPath(item: indexPath.item - 1, section: indexPath.section) let previousFrame = layoutAttributesForItem(at: previousIndexPath)?.frame ?? CGRect.zero let previousFrameRightPoint = previousFrame.origin.x + previousFrame.width let currentFrame = currentItemAttributes.frame let strecthedCurrentFrame = CGRect(x: sectionInset.left, y: currentFrame.origin.y, width: layoutWidth, height: currentFrame.size.height) let isFirstItemInRow = !previousFrame.intersects(strecthedCurrentFrame) // 让每一行的第一个元素排头,分两种情况处理。这是第二种,这个 section 的其他的排头,算出来,就是:上一个格子在上一行,不在当前行, guard !isFirstItemInRow else{ currentItemAttributes.leftAlignFrame(with: sectionInset) return currentItemAttributes } // 剩下的,简单了。统一处理掉。 剩下的格子都不是排头,与上一个固定间距完了。 var frame = currentItemAttributes.frame frame.origin.x = previousFrameRightPoint + evaluatedMinimumInteritemSpacing(at: indexPath.section) currentItemAttributes.frame = frame return currentItemAttributes } return nil }// ...}说一下 func layoutAttributesForItem(at indexPath: IndexPath) 的设计思路。因为如果使用 UICollectionViewFlowLayout ,什么都不干,与上图的区别就一点。每一行的元素个数一致,具体也一致,就是那些格子是居中的。毕竟 minimumInteritemSpacing 是最小行内间距的意思,不是固定的行内间距。 ...

June 18, 2019 · 5 min · jiezi

Flutter

如何在Mac上配置Flutter与Android环境变量?在.bash_profile文件里配置

June 18, 2019 · 1 min · jiezi

编辑器之神vim-常用命令

在这个蔚蓝的星球上,流传着两大神器的传说:据说Emacs是神的编辑器,vim是编辑器之神。 拷贝/粘贴/删除拷贝:yy/yw(yy:是拷贝整行,yw:拷贝一个词)粘贴:p删除:dd/dw(dd:删除一行,dw:删除一个词)vim光标移动左下右上:h/j/k/l跳到文件头:gg跳到文件尾:G行内光标移动移动到行首:^移动到行尾 : $按单词移动: 向前w/2w ,向后b/2b(2w:向前移动2个单词,2b:向后移动2个单词,以此类推)vim查找与替换查找关键字: /关键字查找与替换: :%s/关键字/替换字/gc%:代表全文;s:代表search;g:在一行中找到第一个继续往下找,代表整行;c:需要确认。vim 多窗口分窗口:split/vsplit(split:横向分屏;vsplit:竖向分屏)窗口间跳转:ww/w[hjkl]

June 18, 2019 · 1 min · jiezi

VueFlask实现一个图片分享网站及多平台部署

PicShare 网站简介源码地址 这是一个图片分享平台,借鉴Instagram的基础功能和页面布局并进行一点减法的移动端网页,也是我在移动端乃至Web项目的处女作,文章或者项目有问题的地方欢迎大家多多指正(o^^o) 先来点图登录&注册首页&内容发布评论&转发消息个人中心 项目技术栈前端:Vue.js + Vue Router + Vuex + ElementUI后端: Python Flask数据: MariaDB,对象云存储,图床功能模块登录&注册网站的内容需要登录使用,其中注册的第一项为设置头像,不少小伙伴及面试官没有看见而导致提交时候失败,这个地方是我的疏忽,后期有时间了进行优化其显示与验证功能 首页&关注用户登录成功后进入首页,首页可以获取到所有用户最近发表的图文动态,关注页面可以获取到所有关注用户的最近动态,用户可以通过点击点赞按钮对动态进行点赞操作,点击转发按钮进入内容转发页面,点击评论按钮进入评论页面 发表&转发提供给用户一个可以自由发表与转发的面板 消息用户可以收到与自己发表或者转发内容相关的消息,包括点赞,转发,评论,同时用户还可以收到与账户关系相关的消息,如关注,私信(有待实现) 个人中心页面的布局及内容完全模仿Instagram的移动端网页,用户可以通过个人中心展示自己或者他人的信息,他人信息的入口为显示用户头像及用户名的地方(首页&关注的内容去,评论内容区,消息详情区域) 粉丝&关注通过一个列表展示用户之间的关系,同时提供给用户关注与取消关注的按钮 项目部署前端默认当前目录为前端目录(frontend/) 安装所有的npm依赖 npm installbuild npm run build此时前端目录上一级得到的dist文件夹就是我们服务器部署需要的文件夹 后端默认当前的目录为后端目录(backend/) 确保你的服务器已安装Python 3 (推荐Python 3.6及以上)及虚拟环境 venv创建虚拟环境 python3 -m venv ./venv激活虚拟环境 source ./venv/bin/activate安装后端需要的依赖 pip install -r requirements.txt编辑自己的private_config.py SECRET_KEY 可以是字符串,通过这个字符串进行密码加密存储时的加盐HOST 数据库的地址,默认为本地USERNAME 数据库的连接用户名,我使用的是rootPASSWORD 数据库的连接密码PORT 数据库的监听端口,默认为3306DATABASE 数据库的名称,需要先建立数据库,不用建立表结构如果需要使用对象云存储服务,则需要对Bucket进行相应的配置服务器使用Nginx进行反向代理,配置文件参考backend目录下的default文件使用Heroku进行持续部署,配置文件参考backend目录下的Procfile文件部署结果个人主机:Picshare_running on_hostHeroku:Picshare runing on HerokuAzure: Picshare running on Azure总结这是我的第一个web应用,通过77次代码的提交,不断爬坑不断学习,从中学习到了如何使用H5,CSS3,JS,Python以及服务器部署 Github源码

June 17, 2019 · 1 min · jiezi

M3U8格式分析

概述 M3U8是Unicode版本的M3U,用UTF-8编码。"M3UP"和"M3U8"文件都是苹果公司使用的HTTP Live Streaming(HLS)协议格式的基础。这种格式可以在IPhone和Macbook等设备播放。M3U8本质上是一个播放列表,其中可能是一个媒体播放列表(Media Playist),或者是一个主列表(Master Playlist), 其内部文字使用utf-8编码。 主播放列表 如上图所示,其内部提供的是同一份媒体资源的多份流列表资源(Variant Stream),该备用流资源指定了多种不同本报的资源内容,比如不同语言音频文件,不同角度拍摄的视频文件以及不同码流的资源文件等,可以根据用户的喜好选择合适的资源内容。 媒体播放列表#EXTM3U#EXT-X-VERSION:1#EXT-X-MEDIA-SEQUENCE:0#EXT-X-ALLOW-CACHE:NO#EXT-X-TARGETDURATION:10#EXTINF:3,1-4.ts#EXTINF:8,1-6.ts#EXTINF:8,1-8.ts#EXTINF:3,1-26.ts#EXT-X-ENDLIST 如上图所示,这个是一个简单的M3U8文件的内容: #EXTM3U必需,表示一个扩展的m3u文件#EXT-X-VERSIONHLS协议的版本号,暗示流媒体的兼容性#EXT-X-MEDIA-SEQUENCE:34标明首个分段视频的sequence number,只能有一个字段,若没有,则首个视频分段的sequence number位0#EXT-X-ALLOW-CACHE:NO是否运行客户端对下载的视频分段缓存用于以后播放#EXT-X-TARGETDURATION:10每个视频分段的时长,单位秒#EXTINF:3当前视频分段的播放时长,单位位秒1-4.ts当前视频分段的url相对/绝对路径#EXT-X-ENDLISTm3u8文件列表结束

June 17, 2019 · 1 min · jiezi

macOS-Mojave-10145-通过-Homebrew-216-安装-PowerShell-621-踩坑记录

背景其实就是想装个powershell,然后发现需要安装homebrew 好吧,安,结果,又发现超时,超时,超时之后,你懂的,百度,百度,百度……结果,过时的帖子,过期的方法……唉,人笨,没办法,最后死活安上了,做个记录,要不过2天又忘了 开整正常安装嗯,开shell,粘上,回车,等着就行 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"不正常的 没办法,苦等也是超时,自己动手吧 改脚本可以看到,命令中是执行了一个 install 的脚本,那么下载下来改改吧 #!/usr/bin/ruby# This script installs to /usr/local only. To install elsewhere (which is# unsupported) you can untar https://github.com/Homebrew/brew/tarball/master# anywhere you like.HOMEBREW_PREFIX = "/usr/local".freezeHOMEBREW_REPOSITORY = "/usr/local/Homebrew".freezeHOMEBREW_CORE_TAP = "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core".freezeHOMEBREW_CACHE = "#{ENV["HOME"]}/Library/Caches/Homebrew".freeze# **********下面这行,改成科大的源地址 ********************# BREW_REPO = "https://github.com/Homebrew/brew".freezeBREW_REPO = "https://mirrors.ustc.edu.cn/brew.git".freeze# **********看上面 *************************************# TODO: bump version when new macOS is releasedMACOS_LATEST_SUPPORTED = "10.14".freeze# TODO: bump version when new macOS is releasedMACOS_OLDEST_SUPPORTED = "10.12".freeze# no analytics during installationENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1"ENV["HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT"] = "1"# 下面还有很多,不粘了,看着累再安装 ...

June 16, 2019 · 1 min · jiezi

Windows重签名ipa文件的实现和过程

au-signer-win工具可以实现在Windows电脑直接重签名ipa,无需苹果电脑! 对现用的ipa文件进行重签,实现达到可以安装自己苹果手机的目的。 扩展功能可以设置签名时间控制,可以去除第三方签名锁,安装量、启动次数统计,很方便管理! 详细文档 http://www.applicationloader....

June 14, 2019 · 1 min · jiezi

iOS-中gif图的显示

一、前言iOS开发中,大部分时候我们显示一张静态图就可以了,但是有的时候为了UI表现更生动,我就有可能需要展示gif图来达到效果了。 网上找了一下,显示gif图的框架找到了两个。 SDWebImageYYImage 二、显示本地gif图SDWebImage和YYImage的显示本地图片代码。 //load loacle gif image- (void)loadLocaleGifImage{ //sdwebimage [self labelFactoryWithFrame:CGRectMake(0, 80, kScreenWidth, 20) title:@"SDWebImage"]; NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"gif"]; NSData *gifData = [NSData dataWithContentsOfFile:path]; UIImageView *sdImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, kScreenWidth, kScreenHeight/3)]; sdImageView.image = [UIImage sd_animatedGIFWithData:gifData]; [self.view addSubview:sdImageView]; //yyImage show gif image [self labelFactoryWithFrame:CGRectMake(0, kScreenHeight/2 - 20, kScreenWidth, 20) title:@"yyImage"]; YYImage *yyimage = [YYImage imageNamed:@"test.gif"]; YYAnimatedImageView *yyImageView = [[YYAnimatedImageView alloc] initWithImage:yyimage]; yyImageView.frame = CGRectMake(0, kScreenHeight/2, kScreenWidth, kScreenHeight/3); [self.view addSubview:yyImageView];}三、加载网络的gif图SDWebImage和YYImage的加载网络图片代码。 ...

June 14, 2019 · 1 min · jiezi

对NSArray和NSMutableArray的深拷贝浅拷贝的探究

一、原起下面两个问题,面试的时候应该经常会被问到。 对NSArray和NSMutableArray进行copy和mutableCopy分别会得到什么样的数组?当NSString作为一个对象的属性时,我们应该使用strong还是copy来修饰呢?今年三月份面试的时候,被这两个问题搞得很迷茫,今天特地研究了一下。相信您看完我的这篇文章和我有一样疑惑的您,心里会有一个清晰的答案。 二、NSMutableArray的copy和mutableCopy操作进行探究//1、对NSArray分别使用`copy` & `mutableCopy`进行内存地址的对比 NSArray *orgArr = @[@"ningjianwen", @"kongjiangmei"]; NSArray *copyArr = [orgArr copy]; NSMutableArray *mcopyArr = [orgArr mutableCopy]; [mcopyArr addObject:@"jiangxianjin"]; NSLog(@"NSArray 地址对比结果打印:"); NSLog(@"orgArr 地址: %p", orgArr); NSLog(@"copyArr 地址: %p", copyArr); NSLog(@"mcopyArr 地址: %p", mcopyArr);打印结果如下: 2019-06-13 20:05:48.915949+0800 ArrayCopyAndMutableCopy[54942:3399095] NSArray 地址对比结果打印:2019-06-13 20:05:48.916073+0800 ArrayCopyAndMutableCopy[54942:3399095] orgArr 地址: 0x600003716bc02019-06-13 20:05:48.916189+0800 ArrayCopyAndMutableCopy[54942:3399095] copyArr 地址: 0x600003716bc02019-06-13 20:05:48.916266+0800 ArrayCopyAndMutableCopy[54942:3399095] mcopyArr 地址: 0x600003951b90结果分析:从打印结果可以看出orgArr与copyArr内存地址是一致的,说明copy对NSArray进行的是浅拷贝。mcopyArr与orgArr内存地址是不一致的,说明mutableCopy对NSArray进行的是深拷贝,且拷贝之后数组变成了一个可变数组。 三、NSArray的copy和mutableCopy操作进行探究//2、对NSMutableArray分别使用`copy` & `mutableCopy`进行内存地址的对比 NSMutableArray *orgMArr = [NSMutableArray arrayWithObjects:@"星辰", @"江河",nil]; NSArray *copyMArr = [orgMArr copy]; NSMutableArray *mcopyMArr = [orgMArr mutableCopy]; [mcopyMArr addObject:@"日月"]; NSLog(@"NSMutableArray 地址对比结果打印:"); NSLog(@"orgMArr 地址: %p", orgMArr); NSLog(@"copyMArr 地址: %p", copyMArr); NSLog(@"mcopyMArr 地址: %p", mcopyMArr);打印结果如下: ...

June 14, 2019 · 2 min · jiezi

macOS-下ffmpeg源码编译安装

1. 下载ffmpeg源码打开mac 的控制台,切换到您想要保存源码的目录,执行git clone https://git.ffmpeg.org/ffmpeg.git命令下载源码。 2. 进入到ffmpeg目录源码下载完后执行cd /ffmpeg命令,切换到ffmpeg目录下,会看到如下内容。 3.执行如下命令进行编译安装3.1对ffmpeg进行配置在ffmpeg目录下执行如下命令进行ffmpeg的编译前配置。 ./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265 --enable-filter=delogo --enable-debug --disable-optimizations --enable-libspeex --enable-videotoolbox --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --cc=clang --host-cflags= --host-ldflags= --disable-x86asm配置的时候会报错,这个时候不要慌,一般都是缺少库,查看错误信息,确实什么库,使用brew install XXX进行安装即可。 如果配置的时候报错,请看这篇文章 3.2编译配置成功以后,就该对ffmpeg进行编译了。在ffmpeg目录下,执行make命令进行编译。温馨提示,这一步需要等待的时间有点长,耐心等待编译完成再继续下一步。 3.3 进行ffmpeg的安装还是在ffmpeg目录下,执行sudo make install命令进行ffmpeg的安装。安装完成后,切换到/usr/local/ffmpeg/bin目录,如果bin目录下有ffmpeg、ffmplay、ffprobe三个目录。恭喜您,你的ffmpeg安装成功了。 敲黑板!!!注意啦!如果目录下缺失ffmplay,那是因为缺少SDL2库,导致编译不出ffmplay,这个时候需要我们先安装sdl2。在ffmpeg目录下执行brew install sdl2。安装完成之后,再重新依次执行3.1、3.2、3.3的命令。 如果在/usr/local/ffmpeg/bin目录下,看到了ffmpeg、ffmplay、ffprobe三个目录,那么这次真的安装成功了,可以开启您的ffmpeg学习之路了。

June 13, 2019 · 1 min · jiezi

一款基于Flutter开发的语雀APP

基于语雀API打造APP,由Flutter开发:可以查阅个人仓库可以查阅团队仓库完善地解析markdown语法极速渲染欢迎使用! 下载地址:http://levy.ren 关于我们:https://femessage.github.io/b...

June 12, 2019 · 1 min · jiezi

ionic-QR-Scanner常见问题解决

扫码页面黑屏1) src -> index.js。修改代码如下 <ion-app style="background: none transparent;"></ion-app>2)src -> theme -> variables.scss // qrScannerion-app.cameraView, ion-app.cameraView ion-content, ion-app.cameraView .nav-decor { background: transparent none !important; .tabbar.show-tabbar{ opacity: 0; }}[app-viewport],[overlay-portal],[nav-viewport],[tab-portal],.nav-decor { display: none !important; background: none transparent !important;}html,body.transparent-body,.transparent-body,.transparent-body ion-app,.transparent-body .app-root,.transparent-body ion-nav,.transparent-body .ion-page,.transparent-body .nav-decor,.transparent-body ion-content,.transparent-body .viewscan,.transparent-body .fixed-content,.transparent-body .scroll-content { background-color: transparent !important; background: transparent none!important;} 多次扫码后摄像头发热的问题 this.qrScanner.hide(); // hide camerathis.qrScanner.destroy(); // destory camera QR Scanner安卓不能扫码条形码问题此问题乃是编码格式的问题,扩充编码格式即可解决。解决办法: 全局搜索formatList。找到QRScanner.java文件,定位到458行。 formatList.add(BarcodeFormat.UPC_A);formatList.add(BarcodeFormat.UPC_E);formatList.add(BarcodeFormat.EAN_13);formatList.add(BarcodeFormat.EAN_8);formatList.add(BarcodeFormat.CODE_39);formatList.add(BarcodeFormat.CODE_93);formatList.add(BarcodeFormat.CODE_128);formatList.add(BarcodeFormat.ITF);formatList.add(BarcodeFormat.DATA_MATRIX);修改源代码后。要重新构建安卓平台 ionic cordova platform remove androidionic cordova platform add android ...

June 11, 2019 · 1 min · jiezi

Flutter-局部路由实现

Flutter是借鉴React的开发思想实现的,在子组件的插槽上,React有this.props.children,Vue有<slot></slot>。 当然Flutter也有类似的Widget,那就是Navigator,不过是以router的形式实现(像<router-view></router-view>???)。 Navigator的使用无非3个属性 initialRoute: 初始路由onGenerateRoute: 匹配路由onUnknownRoute: 404在实现层面 首先:Navigator的高度为infinity。如果直接父级非最上级也是infinity会产生异常,例如,Scaffold -> Column -> Navigator。所以:Navigator需要附件限制高度,例如:Scaffold -> Column -> Container(height: 300) -> Navigator 然后:在onGenerateRoute属性中,使用第一个BuildContext参数,能够在MaterialApp未设置route的情况下使用Navigator.pushNamed(nContext, '/efg');跳到对应的子路由中。 最后:Navigator执行寻找路由顺序是 initialRoute -> onGenerateRoute -> onUnknownRoute,这个和React的Route是类似的。 最后附上源码 import 'package:flutter/material.dart';class NavigatorPage extends StatefulWidget { @override _NavigatorPageState createState() => _NavigatorPageState();}class _NavigatorPageState extends State<NavigatorPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Navigator'), ), body: Column( children: <Widget>[ Text('Navigator的高度为infinity'), Text('如果直接父级非最上级也是infinity会产生异常'), Container( height: 333, color: Colors.amber.withAlpha(111), child: Navigator( // Navigator initialRoute: '/abc', onGenerateRoute: (val) { RoutePageBuilder builder; switch (val.name) { case '/abc': builder = (BuildContext nContext, Animation<double> animation, Animation<double> secondaryAnimation) => Column( // 并没有在 MaterialApp 中设定 /efg 路由 // 因为Navigator的特性 使用nContext 可以跳转 /efg children: <Widget>[ Text('呵呵呵'), RaisedButton( child: Text('去 /efg'), onPressed: () { Navigator.pushNamed(nContext, '/efg'); }, ) ], ); break; case '/efg': builder = (BuildContext nContext, Animation<double> animation, Animation<double> secondaryAnimation) => Row( children: <Widget>[ RaisedButton( child: Text('去 /hhh'), onPressed: () { Navigator.pushNamed(nContext, '/hhh'); }, ) ], ); break; default: builder = (BuildContext nContext, Animation<double> animation, Animation<double> secondaryAnimation) => Center( child: RaisedButton( child: Text('去 /abc'), onPressed: () { Navigator.pushNamed(nContext, '/abc'); }, ) ); } return PageRouteBuilder( pageBuilder: builder, // transitionDuration: const Duration(milliseconds: 0), ); }, onUnknownRoute: (val) { print(val); }, observers: <NavigatorObserver>[] ), ), Text('Navigator执行寻找路由顺序'), Text('initialRoute'), Text('onGenerateRoute'), Text('onUnknownRoute'), ], ), ); }}项目地址: https://github.com/zhongmeizhi/fultter-example-app撸完代码记得给颗星哦。 ...

June 10, 2019 · 1 min · jiezi

Flutter各种API使用

将Flutter各种Widget各种API都实现一次。用于给初学者提供Flutter可视化Widget,方便初学者学习和使用,同时给自己的Flutter提供备忘、代码copy功能。 点击Widget清单的按钮会跳转到各自对应的Widget实现页面。 项目代码地址 https://github.com/zhongmeizhi/flutter-UI 参考链接: 开发文档功能齐全的flutter项目

June 6, 2019 · 1 min · jiezi

APICloud开发者进阶之路-纯手工编写日程表功能

本文出自APICloud官方论坛,感谢论坛版主 赵永亮 的分享。 最近看论坛内关于极光推送的问题有很多,本想写一个关于极光的详细教程的,无奈已经有很多大牛分享过了,所以只得纯手工写了一个日程表,可以以周为单位左右切换,适用于医疗、教育等方向的app,先看下效果图。 再看一下代码截图 自动识别当前日期,并计算出本周的起始日期,通过左右按钮切换上一周和下一周,并同步更换日程,日程可点击激活高亮,至于激活显示什么样的内容你们自己根据实际功能要求处理吧。 作者:APICloud平台 来源:CSDN 原文:https://blog.csdn.net/weixin_... 版权声明:本文为博主原创文章,转载请附上博文链接!

June 4, 2019 · 1 min · jiezi

软件测试进阶指南-MTSC2019-测试开发大会日程-V10-版公布有奖投票你最关注的议题

投票选出你最关注的 MTSC2019 测试开发大会议题,抽奖领取大会门票和 TesterHome 社区其他福利!参与方式见文末!2019,最前沿热门的测试技术和质量管理 QA 最佳实践,尽在 MTSC2019 测试开发大会! MTSC2019 测试开发大会日程 V1.0 重磅发布MTSC2019 第五届中国移动互联网测试开发大会邀请到60+ 来自 Google、BAT、TMD 等一线互联网企业的测试大咖分享精彩议题,涵盖移动自动化测试、服务端测试、质量保障 QA、高新领域测试(AI+、大数据测试、IOT 测试),游戏测试,工程效能提升等 6 大专题方向,预计有 2000+ 测试同行会在现场交流 。 目前 MTSC2019 大会日程 V1.0 版正式公布(Tips:日程后续可能还会有微调,请以官网最新信息为准)。 MTSC2019 8 折门票倒计时截止到 5 月 31 日,有意向者请抓紧抢票! 大会官网:http://2019.test-china.org/报名地址:https://www.bagevent.com/even...MTSC2019 议题亮点解析MTSC2019 大会在移动测试专题,有阿里巴巴手机淘宝的“双十一”客户端质量保障负责人分享全链路验收实践,有蚂蚁金服移动测试 2.0+ 以及代码染色精准化测试探索,以及腾讯微信小程序质量体系构建。 在游戏测试专题,MTSC2019 联合腾讯 WeTest 邀请到腾讯互娱的质量天团(天美、光子、图灵三大工作室测试总监及核心团队)首次公开揭秘腾讯海量用户大型游戏(王者荣耀、绝地求生等)背后的游戏测试和质量保障黑科技。 在今年备受关注的工程效能提升方向,有来自百度搜索质量平台部的资深测试专家组团深度分享百度持续集成交付平台从 0 到 1 构建在每个阶段的踏坑经验,以及从自动化向智能化迭代(数据驱动+AI 测试)的实践心得,有蚂蚁金服质效破局探索,也有美团持续交付工具链分享以及国内知名持续交付专家乔梁老师对持续交付 2.0 的讲解。 在服务端测试和质量保障 QA 领域,既有 VIPKID 千万级系统压测案例和苏宁亿级用户平台性能调优实践,也有优酷视频和转转电商背后的质量保障体系建设案例分享,以及 DevOps 和 TestOps 背景下的微医多维一体化监控平台实践总结,360 和安居客测试团队应对变化的转型升级经验参考,还有酷家乐对混沌工程的探索实践。 而关于火热的 AI+ 测试技术,更有来自美团、小米(小爱产品)、京东、ANKER、Intel、百度、腾讯的多个 AI+ 测试落地案例。 ...

June 3, 2019 · 1 min · jiezi

美区Apple-ID注册20190602

准备科学上网。(地区最好是 United States )语言切换成 English 。Region 改为 United States时区最好也修改一下,不费事。注册iPhone 上 Safari 中打开 appleid.apple.com 网址滑倒底部,查看左下角是否是 United States。若是继续。点击 Create your Apple ID填写信息。 注意:邮箱最好是使用国外邮箱,如:Gmail手机号码。可以使用 国外虚拟手机号 获取美国手机号,并能获取验证码。(PS: 我注册过程中,没有让我填写手机验证码。)安全问题。拿个小本本记下来,比较重要,等下还要用到。Continue,然后会给你的邮箱发一个验证码,填写完成即可。账号就注册成功了。激活iPhone 上 Safari 中打开 appleid.apple.com 网址选择 Payment & Shipping -> Add Payment Method。填写信息。 支付方式。选择 NONE。(如果你有Paypal或者其他信用卡的话,可以选择其他支付方式)信息填写。可使用该网址生成 US Address Generator选择 Save使用打开 Settings -> iTunes & Apple Store。退出当前登陆。使用刚刚注册的账号登陆。关闭当前正在使用的 Apple Store。(PS: 连按两次 Home 键,移除 Apple Store 应用)打开 Apple Store,搜索一个免费的应用下载(如:Google)。会弹出对话框,让你登陆,选择 Use an existing apple ID第一次使用,会作一个验证,提示 this apple id has not yet been used in the itunes store。此时选择 Review。接着基本上就是一路 Next 到结束即可。选择一个免费应用,开始下载之旅即可。 ...

June 2, 2019 · 1 min · jiezi

iOS开发中定义枚举的正确姿势NSENUM-VS-enum

iOS开发中枚举也是经常会用到的数据类型之一。最近在整理别人写的老项目的时候,发现枚举的定义使用了多种方式。 方式1typedef enum { MJPropertyKeyTypeDictionary = 0, // 字典的key MJPropertyKeyTypeArray // 数组的key} MJPropertyKeyType;方式2typedef enum: NSInteger { None, Melee, Fire, Ice, Posion }AttackType;方式3typedef NS_ENUM(NSUInteger, MeiziCategory) { MeiziCategoryAll = 0, MeiziCategoryDaXiong, MeiziCategoryQiaoTun, MeiziCategoryHeisi, MeiziCategoryMeiTui, MeiziCategoryQingXin, MeiziCategoryZaHui};方式4这种比较特殊支持位操作。 typedef NS_OPTIONS(NSUInteger, ActionType) { ActionTypeUp = 1 << 0, // 1 ActionTypeDown = 1 << 1, // 2 ActionTypeRight = 1 << 2, // 4 ActionTypeLeft = 1 << 3, // 8};针对于前三种方式,我们应该使用那一种更新好呢? 这是来自Stack Overflow的解释。 ...

May 31, 2019 · 1 min · jiezi

RN-从上手到放弃

RN 从上手到“放弃”前言: react-native,相对于最近????的飞起的flutter,不算是一个新技术,2015年Facebook 开源,到现在已经4 5 个年头,一直在维护当中,但是至今未发布 v1 版本,目前已经更新到0.59。 该技术目标: 跨平台实现原生应用。 GitHub start 数目: 77602(2019-5-29)。 正文1、项目预览现在已完成的功能展示: 入手demo项目,本打算模仿微信的功能做一遍。现在已经完成微信的一级界面。截图如下:首页: 通信录: 发现: 我: 朋友圈(上拉加载和下拉刷新): (未完成,就是调用了接口) 聊天界面: 摄像头拍照(安卓虚拟机): github地址: https://github.com/adouwt/rea... nodejs后台:https://github.com/adouwt/nod... 项目主要使用插件(库): react-native-camera (调用摄像头)react-native-vector-icons (图标库)react-navigation (路由导航)参考资料: https://reactnative.cn/https://oblador.github.io/rea...https://github.com/react-nati...https://shenbao.github.io/ish...2、项目运行~前提: 环境搭建及相关软件、安卓或者ios 的模拟器安装, 参考官网即可,https://reactnative.cn/docs/g... git clone https://github.com/adouwt/rea...cd react-native-wxnpm inpm run and (安卓)npm run ios (苹果)(上面的运行命令,我在package.json 做了封装,一些处理编译错误的命令,我也已经封装进去) 执行命令后,会自动弹出nodejs 执行终端界面,这个是程序运行的一个监控 模拟器显示: 3、分步实现3.1 初始化并运行项目react-native init AwesomeProjectreact-native run-ios3.2 项目结构说明3.3 新建文件夹 app,接下来所有的源码文件代码将在这里 ...

May 30, 2019 · 4 min · jiezi

swift中的声明关键字详解

原起学习swift,swift中的关键字当然要了解清楚了,最近在网上看到了关于声明关键字的文章,整理记录一下。 关键字是类似于标识符的保留字符序列,除非用重音符号(`)将其括起来,否则不能用作标识符。关键字是对编译器具有特殊意义的预定义保留标识符。常见的关键字有以下4种: 与声明有关的关键字:class、deinit、enum、extension、func、import、init、let、protocol、static、struct、subscript、typealias和var。与语句有关的关键字:break、case、continue、default、do、else、fallthrough、if、in、for、return、switch、where和while。表达式和类型关键字:as、dynamicType、is、new、super、self、Self、Type、__COLUMN__、__FILE__、__FUNCTION__和__LINE__。在特定上下文中使用的关键字:associativity、didSet、get、infix、inout、left、mutating、none、nonmutating、operator、override、postfix、precedence、prefix、rightset、unowned、unowned(safe)、unowned(unsafe)、weak和willSet。声明关键字一览图swift常见的声明关键字整理如下(不想看长文的,直接看下图即可) 声明关键字详解1、class在swift中,我们使用class关键字去声明一个类或者类方法。 class Person: NSObject { /// add `class` key word before function, this function become a class function class func work(){ print("everyone need work!") }}这样我们就声明了一个Person类。 2、letswift里有let关键字声明一个常量,及我们不可以对他进行修改。(注意:我们用let修饰的常量是一个类, 我们可以对其所在的属性进行修改) class iOSer: Person{ let name: String = "ningjianwen" var age: Int = 30 var height: Float = 170}let ITWork: iOSer = iOSer()ITWork.age = 25print("老子希望永远25岁")在iOSer类中let声明的name不可修改,var声明的age&height可以修改。同时let关键字声明的ITWork实例不可变,但是内部的var关键字声明的刷新是可以修改的。 3、varswift中var修饰的变量是一个可变的变量,可以对她的值进行修改。注意:我们不会用var去引用一个类, 也没有必要。 func iOSerClassFunction(){ let ITWork: iOSer = iOSer() ITWork.age = 25 print("老子希望永远\(ITWork.age)岁") let iOS1 = ITWork iOS1.age = 18 print("iOS1 age =\(iOS1.age)") print("ITWork age = \(ITWork.age)") } /** 打印结果 老子希望永远25岁 iOS1 age =18 ITWork age = 18 */从结果可以看出对iOS1的修改同样影响了ITWork,说明两个对象指向同一块内存空间。 ...

May 27, 2019 · 3 min · jiezi

ObjectiveC的内存管理2从MRC到ARC

罗里吧嗦颠三倒四,单纯的个人笔记。 MRC引用计数上一篇已经有大概讲过。在Objective-C里,每个继承自NSObject的对象都会记录自身的引用计数,一番加加减减之后,变成0就会释放掉。MRC是Mannul Reference Counting的缩写,意思也很简单,这番加加减减都靠手动管理的意思。 使用时的基本原则是:管好自己。每个对象,引用别的对象时加了几次计数最终到了不用的时候就要减几次。不能多也不能少。这样就聚焦了很多。 导致引用计数增加的操作,显式的retain不多说,剩下的就是四个关键字:alloc、new(以及new开头的方法)、copy、mutableCopy,使用这四个关键字得到的对象,就算你自己加的引用计数,回头要自己减掉。 引用计数减少的操作就是release了。 AutoReleaseAutoReleasePool是个自动释放池,加入其中的对象会延迟到Pool“结束”时释放。在MRC中,你可以显式创建一个NSAutoReleasePool,显式地将一个对象加入进去,并显式释放AutoReleasePool: NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];// Code benefitting from a local autorelease pool.NSObject *obj = [[[NSObject alloc] init] autorelease];[pool release];//[pool drain];在ARC中,需要通过特殊的语法: @autoreleasepool { // Code benefitting from a local autorelease pool.}默认地,每个Runloop迭代开始时会创建一个AutoReleasePool,Runloop迭代结束时释放。也就是说,当我们没有显式创建AutoReleasePool时,autorelease的对象会在Runloop迭代结束时释放。当我们显式地创建AutoReleasePool时,其释放时机是我们决定的(显式调用release/drain或block结束)。 主动使用AutoReleasePool的目的通常是为了控制内存峰值。比如,我有一个大循环,每次循环都会创建比较大的autorelease的临时对象。如果不显式释放,这些临时对象会在整个循环结束后才一起释放,期间可能造成内存占用过高。这种情况下就可以在每次循环内声明autoreleasepool,保证临时对象不会堆积。 ARCARC为我们自动地做了很多,屏蔽了很多细节,理论上来说,我们只需要关注对象间的所有权关系即可。上层机制虽然简单,涉及到的细节还是有很多的,可喜可贺的是,ARC是有标准文档的。简直... ARC提供的变量修饰符有以下几个: __strong__weak__unsafe_unretaied__autoreleasing提供的属性修饰符有: assign 对应的所有权类型是 __unsafe_unretained。copy 对应的所有权类型是 __strong。retain 对应的所有权类型是 __strong。strong 对应的所有权类型是 __strong。unsafe_unretained对应的所有权类型是__unsafe_unretained。weak 对应的所有权类型是 __weak。(基本类型默认是assign,对象类型默认是strong) __strong强引用,不多说了。注意声明变量和属性时若未加说明,默认是强引用。 __weak弱引用。当对象被释放时,weak修饰的变量会被置为nil。仔细想想,想要实现这个特性,所有的weak变量都需要放到一个全局的map里,实现成本还是比较高的。 __unsafe_unretained不做任何额外操作。 __autoreleasing__autoreleasing标记的变量等价于调用autorelease方法 想到一个小问题:对于函数返回值,ARC是怎么知道要不要加引用计数呢?看这几行代码: - (void)testMethod{ NSObject *obj = [NSObject new]; NSArray *array = [NSArray array]; // do something}在ARC中,obj和array用完之后都会被自动释放,但是细想之下其实有不少细节。要知道,[NSObject new]返回的对象引用计数是有+1的,而[NSArray array]并不是。这俩玩意儿引用计数差了1,ARC是怎么知道谁要多释放一次的?在MRC中,我们知道new出来的obj需要手动释放,而array就不需要,是通过方法的关键词进行判断。但是方法中的关键词不应该是某种约定吗?ARC难道也会去看一个方法是否是以new开头?看了文档之后发现...ARC还真是这么做的... ...

May 26, 2019 · 1 min · jiezi

ObjectiveC中block的循环引用问题

目标:block执行过程中,self不会释放;执行完可以释放。 最初block中直接使用self会强引用。 self.myBlock = ^() { [self doSomething];};或者使用了对象的属性 self.myBlock = ^() { NSString *str = _str; NSString *str2 = self.str;};在这样的情况下,self强引用block,block也持有该对象,导致循环引用。 要注意的是,只有在self强引用block的时候才会有这样的问题。一般使用GCD或NSOperation时使用的内联block是不会出现循环引用的。 加入weak self__weak __typeof(self) weakSelf = self;self.myBlock = ^() { [weakSelf doSomething];};这样,self持有了block,但block对self是弱引用,就不会导致循环引用了。 而在[weakSelf doSomething]过程中,self是不会释放的,完美。 但是,如果是这样呢? __weak __typeof(self) weakSelf = self;self.myBlock = ^() { [weakSelf doSomething]; [weakSelf doSomething2];};在[weakSelf doSomething]和[weakSelf doSomething2]之间,self可能会被释放掉。这可能会导致奇怪的问题。 加入strong self__weak __typeof(self) weakSelf = self;self.myBlock = ^() { __strong __typeof(self) strongSelf = weakSelf; [strongSelf doSomething]; [strongSelf doSomething2];};这样,block既没有持有self,又能保证block在执行过程中self不被释放,真正达到了最初的目标。 ...

May 26, 2019 · 1 min · jiezi

ObjectiveC的内存管理1内存管理概述

概述应用程序开发中,内存管理是个重要的话题。简单而言,语言层面的内存管理基本有三类: 1. 纯粹的手动管理如C和曾经的C++。 char *some_string = malloc(BUFFER_SIZE);// do somethingfree(some_string);这个简单的例子里用完就释放还好,但是有时候这个some_string被传来传去不知道飞哪儿去了,就比较尴尬。纯手动管理的代价是程序员的心智负担比较重。即使后来C++程序员们抽象出RAII这样的实践规范,一定程度上降低了管理的复杂度,但是相对来说成本还是略高。随着语言的发展,已经很少有语言只依赖手动管理内存了。 2. 基于某些机制实现半自动管理这里的某些机制其实通常就是引用计数。毕竟这是最简单的内存管理辅助手段。 引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。引用计数大家都了解,不多说,单纯的自动使用引用计数问题在于无法解决循环引用的问题。很多语言选择让程序员付出一点劳动来解决这个问题。早年,Objc选择的是退一步,完全让程序员来管理引用计数的加减,称为MRC,显然管理成本偏高。后来推出了ARC,提供了更健全的机制,程序员只要标识出对象间的引用关系是强引用还是弱引用就可以了,大大降低了程序员的负担。虽然走这个路子的语言不算多,但除了Objc之外还是有好几个的。C++的智能指针跟Objc的ARC就比较相似。而Rust的所有权模型本质上也是类似的。 3. 自动垃圾回收通过GC自动管理内存大概是现在的主流了。对程序员来讲实在是太舒适了,开发时几乎不用考虑内存管理的问题。Java、JavaScript、Python、go等一大票语言都是走的这条路。GC是基于可达性分析算法的,即,从根节点(全局变量、局部变量等等)出发,遍历引用到的对象,所有没遍历到的对象就可以释放了。当然从原理到实际应用中间差了十万八千里。朴素的GC会经常造成Stop The World。一旦Stop-the-world发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。于是很多GC算法被发明出来用于优化、减少。常见的CMS、G1回收算法都极大地减少了STW的时间,但仍然不能完全避免。R大在Java 大内存应用(10G 以上)会不会出现严重的停顿?中提到Zing JVM采用的C4算法是可以完全避免STW的,不过看起来为了避免STW,C4算法会吃掉更多的内存,程序吞吐量会受到影响。 小结总结一下吧,纯手动管理基本上已被淘汰,ARC(暂且把方法二这类都称为ARC吧)和GC对比之下,对开发者,ARC需要程序员付出一定的代价进行管理,GC则基本上完全解放双手;性能上,GC通常会造成STW现象,对响应时间比较敏感的程序,比如高频交易系统,是很难接受的,而ARC不会对性能造成明显影响。 几点有趣的事情1. 总体性能总体性能上,只要不是内存跑得特别满,ARC的总体代价是高于GC的。其实想想就知道了,GC只关注两次回收间的变化,而ARC要对每一次引用的改变进行计数,总体性能上比GC差是很正常的,但由于ARC的耗时是均匀分布在运行时间里的,通常我们不用很关注。关于这个问题可以参考这篇论文。 2. cpython的方案另外比较特别的是,Python的默认解释器CPython中应用了引用计数与垃圾回收相结合的手法,没有循环引用的对象会被引用计数回收,剩下的交给GC处理,大大降低了GC的压力。感觉很有意思。 3. ARC的性能在类ARC方案上,C++提供的能力是比较全面的。这可能跟c++常用于一些性能比较苛刻的场景有关。出于性能原因,使用c++智能指针时有如下指导思想: 对象的所有权不重要时 ,用裸指针对象的所有权唯一时,用unique_ptr,能用unique_ptr就不要用shared_ptr。要处理复杂情况时,可以使用shared_ptr,但需要注意不要滥用。当引用关系不影响所有权时,用weak_ptr。Rust也有类似的能力。而python和Objective-C就没有这么多讲究,所有的引用计数其实都是shared_ptr。Objc在iOS上这么多年,而后来的swift也传承了ARC,基本上可以认为,移动端应用从小到大都不差这么一丢丢性能。 以此推论,绝大部分c++应用也完全没必要关注这几个指针间的差异,操起shared_ptr就是干。

May 25, 2019 · 1 min · jiezi

2019-再聊移动端-300ms-延迟及-fastClick-原理解析

前言最近公司新开了一条业务线,有幸和大佬们一起从头开始构建一套适合新业务的框架。俗话说得好呀,适合自己的才是最好的 ????。在新项目的 CodeReview 的时候,被大哥提到有没有添加 fastClick 解决移动端 300ms 延迟的问题。以下就带你追溯移动端延迟的 前世 今生。 介绍前世 - 诞生的因国外有一篇关于 300ms 延迟的文章:What Exactly Is..... The 300ms Click Delay 世间万物皆有因果,网页兴起于桌面端,那时候有谁会想到手机等移动设备的风靡?犹记得上大学那会儿,手机访问学校网站的时候都是通过手指缩放来控制的 ????,心里真的是一万头草泥马奔腾而过,后来为了解决移动端适配的问题,提出了 viewport 的解决方案,基于 无障碍(accessibility)(需要代理)交互设计师为了更好的用户体验,特地提供了 双击缩放 的手势支持。殊不知这正是一切祸乱的根源。 今生 - 消逝的果谷歌有开发者文档: 300ms tap delay, gone away(需要代理) 以下是原文的部分引用 For many years, mobile browsers applied a 300-350ms delay between touchend and click while they waited to see if this was going to be a double-tap or not, since double-tap was a gesture to zoom into text.大致是说,移动浏览器 会在 touchend 和 click 事件之间,等待 300 - 350 ms,判断用户是否会进行双击手势用以缩放文字。 ...

May 24, 2019 · 3 min · jiezi

关于常量的思考与总结

写在前面全局常量作为开发人员一定是一个比较熟悉的概念。全局常量的写法自然也比较多,最近在进行项目的常量重构时看到了各种各样的写法,其中宏定义占大部分,然而有很多使用宏定义是不规范的,而且宏定义只是在预编译阶段进行文本替换,不进行类型检查,从网上看到大量使用宏定义会拖慢编译速度。 所以在定义全局常量时,为了提高开发过程中的规范度和编译速度,宏定义并不是最佳选择。所以我重构的原则是: 能声明成外部常量的,尽量声明成外部常量,万不得已的才使用宏定义。 一、宏计算机科学里的宏(Macro),是一种批量处理的称谓。一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常也是字符串)。这种替换在预编译时进行,称作宏展开。在我刚刚接触开发的时候,我学习到的定义全局常量的方法就是宏。由于宏只是做字符串的替换,它还是有它的优势的。我们可以使用它来一些常量、函数。 例子:1、定义屏幕相关的常量。 /屏幕宽高,frame,bounds,size#define kBKScreenWidth [[UIScreen mainScreen] bounds].size.width#define kBKScreenHeight [[UIScreen mainScreen] bounds].size.height#define kBKScreenBounds [UIScreen mainScreen].bounds#define kBKScale [[UIScreen mainScreen] scale]2、定义调试的log输出函数。 #pragma mark - DEBUG#ifdef DEBUG// 定义是输出Log#define DLog(format, ...) NSLog(@"Line[%d] %s " format, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)#else// 定义是输出Log#define DLog(format, ...)#endif从上面的示例可以看出宏定义的关键字是#define.宏定义常量的公式: #define constantA statementA预编译的时候使用constantA部分的内容替换成statementA。对于函数的定义则稍微复杂一些,有参数和无参数。无参数的函数是直接进行字符串的替换,有参数的还要进行参数的替换。 二、extern使用extern关键字声明全局常量,这个应该算是最标准的做法了。这个是后面在网上的帖子中有看到,当然开源代码中也看到过,确定无疑是定义全局常量的最佳选择。 extern定义全局常量分为声明部分和赋值部分,分别放在 .h & .m文件中。 代码示例: UserInfoModelConstants.hextern NSString *const BKUSER_AGE_KEY ;extern NSString *const BKUSER_TELPHONE_KEY ;extern NSString *const BKUSER_ADDRESS_KEY ;extern NSString *const BKUSER_BRIEF_KEY ;UserInfoModelConstants.mNSString *const BKUSER_AGE_KEY = @"XXXXX.userAge";NSString *const BKUSER_TELPHONE_KEY = @"XXXXX.telphoneNO";NSString *const BKUSER_ADDRESS_KEY = @"XXXXX.address"; NSString *const BKUSER_BRIEF_KEY = @"XXXXX.brief";特别提示: ...

May 23, 2019 · 1 min · jiezi

七分设计感的纯Flutter项目Mung三部曲

React版MungReact-Native版MungFlutter版MungMung-Flutter1. Mung-Flutter:是一个基于Flutter编写,使用豆瓣开源API开发的一个项目。 2. 功能概述启动页:添加了启动页主要是让最开始进入时不至于显示白屏。数据保存 :支持断网加载缓存数据。主题换肤 :现在只支持切换主题颜色,本项目没几张图片。查看电影详情 :支持查看电影详情包括评论。一键搜索: 支持标签和语句查找相关的电影。查看剧照: 支持缩放图片。3.1 动态演示(Android版) 3.2 运行结果图 4. 使用到的框架flutter_swiper :Banner栏图片轮播的效果。rxdart :和Rxjava、RxJs、RxSwift差不多,这里主要用它的BehaviorSubject配合Bloc模式实现状态管理。shared_preferences :简单的数据保存,比较细致的数据存储如列表等还是建议使用数据库。dio :实现网络请求,一个非常不错的三方网络包,功能非常多,如果刚入门或者项目比较急建议使用这个。flutter_spinkit : 加载时显示的加载组件,挺不错,建议看下。photo_view: 图片缩放组件,因为安卓里的photoview正好选了,使用了一个简单的功能,暂时没发现问题。5. 项目全局状态管理现在据我了解的比较成熟的状态管理有。 InheritedWidget(自带的其他三方好像都是基于它开发,只是封装了下,更加方便)scoped_model: 不错。redux和前端的redux是一个意思,但我写过demo用过,个人愚见:差远了。Bloc:(Business Logic Component)paolo soares 和 cong hui 在2018年Google dartconf上提出的,它其实是一个模式InheritedWidget+stream配合使用。本项目使用的就是Bloc。 6. 思考这个开发的第一个flutter,都有这个项目来说该用的主流框架都恰到好处的用了,因为项目太小,适合入门和快速开发。对于flutter个人感觉。 上个月看了一个消息Flutter团队好像在今年不会推出热更新功能,好像是基于安全和可实现性考虑,这里要说下flutter编译模式: 开发阶段使用的是 Kernel Snapshot 模式编译,生产模式使用AOT。flutter上月好像推出了web端和桌面的适配,这个应该对flutter发展有很大帮助。我之前一年多一直使用React-Native开发项目,感觉Flutter的组件比RN多,而且多很多,组件兼容性更好,而且更精致,但是嵌套的模式真心丑,而且巨乱,我开发时把组件拆分成多个函数这样会让界面清新一点。状态管理,暂时还没有一个绝对好的状态管理功能,现在有些项目使用bloc或者bloc+redux,但个人认为不久的将来会有一个好的状态管理功能占据绝对的地址,想RN的redux、mobx一样。组件生命周期函数很少,尤其是开发大型项目时,之前使用RN开发时就觉得RN比原生安卓生命周期少,自己还得去添加全局监听去管理生命周期,flutter就更少了。性能,应该flutter,网上一大堆对比文章一番一大把,个人使用也明显感觉到flutter性能很好,这是现实原理的问题,尤其是列表,比fRN好很多,而且动画等也多,自定义组件还没看,不做评价。社区,毫无疑问RN社区会比Flutter对于现在这个时间段来说,而且RN支持热更新对原生加(RN、Flutter)来说,RN也更站优势,三方组件来说RN已经很多了,开源项目比较多。7. 提示2019-5-12左右豆瓣把开源API关了,现在使用的别的开发者的地址,项目Baser_url是抽出来的后期可以自己改,现在项目使用的是https://douban.uieee.com/v2,可以正常运行。 8.下载地址安卓版ios版(没有企业账号-????)

May 22, 2019 · 1 min · jiezi

Flutter试玩小结

写在前面前段时间Google I/O 2019大会上flutter再次狠狠的刷了一波存在感,随便看看不管是微博,推上还是medium或者一些其它的技术社区上也是铺天盖地的讨论,优缺点搬运过来差不多是: 优点:贯彻始终的widget的设计思想和开发方式对各种背景还是很亲切的跨全平台的愿景很吸引人写界面和相关的逻辑快速简单文档还有编辑器,IDE的支持很完善和贴心社区很活跃教程也很多(包括官方的youtube channel)保持状态的hot reload,效率高到飞起缺点:Desktop, Web平台的支持还处在preview或试验阶段包的数量很少,质量也一般各平台要想实现复杂效果的支持度不行,需要花费大量时间目前采用flutter开发的上架App其实还是很少总结来说就是目前还在初级阶段,简单业务为主的项目可能比较合适,追求体验的App可能会碰到很多坑,实现复杂体验所发的时间基本会抵消掉跨平台复用所带来的成本优势,但是需要合适的场景和项目才有机会体会到这些全部的优缺点。 示例不过还是本着尝鲜还有无聊打发时间的想法,花了点时间学习和把以前做的一个iOS捷径写了一个Cat Catalog的简单App,支持: view cats of different breeds by tagssearch by simple keywordlike your favorite cat(s)read further information on Wikipedia page项目功能和逻辑都很简单,用到的API/widgets很少: JSON -serializingAnimation基础的布局控件使用外部包和字体 代码放在Github: https://github.com/gnehcc/cat... 上了,有兴趣的同学也可以看看,有用的话欢迎star。 使用感受最后就用到的功能谈几点简单感受: Dart学习起来非常简单,只要有任何其他语言的使用经验,花点时间看看language tour基本就可以开始了动画抽象程度高,用起来很简单,异步语法简洁,用的VS Code + Dart插件 + Flutter插件,代码写起来流畅到飞起HOT RELOAD 开发起来特别顺手,大部分时间只需要保存代码之后Simulator就直接更新了,实在不行hot restart也挺快的Widget的嵌套用法让代码有点杂乱,如果Code Extraction做的勤快点的话勉强好点第三方库的数量还有功能丰富度目前确实还比较原始一个Code Base 基本无缝的跑在iOS和Andoid平台,但Flutter的控件其实有两套(Material 和 Cupertino),抛开平台特定功能,也需要至少需要特别处理。个人觉得如果有时间完全可以一遍学一边写写玩玩(主要是也花不了多少时间,写着写着发现动漫也追完了...),如果满意的话上传到App商店也是件不错的事情。 发布在个人博客上的文章可能未及时同步过来,欢迎直接访问 https://gnehc.me 链接捷径个人主页: https://sharecuts.cn/user/eOz... Flutter Dev: https://flutter.dev/docs Flutter boring show A tour of the Dart language: https://dart.dev/guides/langu...

May 21, 2019 · 1 min · jiezi

IOS系统自动获取HTML焦点

IOS系统不能自动获取HTML焦点(focus) //在一般的正常浏览器上,可以用 javascript 来 focus 到一个输入框上: var elem = document.getElementById(‘inputElementId’); elem.focus();但是在 iOS手机 上,这样的代码根本不起作用只有在监听了用户出发的事件的函数中执行 focus 才有用。 var button = document.getElementById(‘btn’);var inputElem = document.getElemntById(‘input-elem’);button.addEventListener(‘click’, function(ev){ inputElem.focus();}); $.subscribe(‘testevent’,function(ev){          $(‘#test’).focus();     });    $(‘#btn’).click(function(ev){         $.publish(‘testevent’)     });   

May 21, 2019 · 1 min · jiezi

如何使用ShareSDK实现Cocos2dx的AndroidiOS分享与授权

Cocos2DX 简介Cocos2d-x是一套成熟的开源跨平台游戏开发框架。其引擎提供了图形渲染、GUI、音频、网络、物理、用户输入等丰富的功能,被广泛应用于游戏开发及交互式应用的构建。引擎的核心采用C++ 编写,支持使用C++、Lua或者JavaScript进行开发。同时Cocos2d-x可以适配IOS、Android、HTML5、Windows和Mac系统。 Cocos2d-x在中国及全球都有一定的市场份额,为了给开发出来的游戏增加知名度,更好的进行营销,社交分享功能是必不可少的。但是所要分享的平台针对全球不同的区域有不同的侧重点,这就需要短时间内接入多个社交平台,但是每个平台的接入时间成本按天计算,多个平台叠加在一起的时间就很不乐观。所以使用Cocos2d-x引擎的开发人员急需一种能够让开发者快速接入授权、分享功能的SDK。 Cocos2d-x集成ShareSDK过程如下:产品主页网址:http://www.mob.com/product/sh...官方插件地址:https://github.com/MobClub/Ne... Android 集成首先下载官方插件,下载完毕之后需要做如下几步工作 把ShareSDK的cocos2dx的proj.android-studio项目里的libs包复制到你的项目里,除了armeabi目录不用复制其他都要。 把ShareSDK的cocos2dx的New-C2DX-For-ShareSDK项目里的Classes目录下的文件都复制到你的项目里的Classes目录下。 在jni/Android.mk添加下面代码来引入相关文件,如: ../../Classes/AppDelegate.cpp \ ../../Classes/HelloWorldScene.cpp \ ../../Classes/C2DXShareSDK/Android/ShareSDKUtils.cpp \ ../../Classes/C2DXShareSDK/C2DXShareSDK.cpp \ ../../Classes/C2DXShareSDK/Android/JSON/CCJSONConverter.cpp \ ../../Classes/C2DXShareSDK/Android/JSON/cJSON/cJSON.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes \ $(LOCAL_PATH)/../../Classes/C2DXShareSDK \ $(LOCAL_PATH)/../../Classes/C2DXShareSDK/Android \ $(LOCAL_PATH)/../../Classes/C2DXShareSDK/Android/JSON \ $(LOCAL_PATH)/../../Classes/C2DXShareSDK/Android/JSON/cJSON在proj.android-studio工程下,app目录下,build.gradle文件内,寻找到dependencies标签,配置如下的代码 compile project(':libcocos2dx')在主activity里(一般在cocos项目的此目录下面:proj.android-studiosrcorgcocos2dxcppAppActivity.java)的onCreate方法里添加ShareSDKUtils.prepare()方法。 protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); ShareSDKUtils.prepare();}各个平台的配置方式:平台的配置在libcocos2dx这个依赖工程内,工程根目录下边有一个MobSDK.gradle文件,写法示例如下边的代码所示,用户需要用自己申请到的Key来替换下文中的key。平台的选择也根据自己的需要来进行增加删除。 apply plugin: 'com.mob.sdk'MobSDK { appKey "moba6b6c6d6"appSecret "b89d2427a3bc7ad1aea1e1e8c1d36bf3"ShareSDK { //平台配置信息 devInfo { SinaWeibo { id 1 sortId 1 appKey "568898243" appSecret "38a4f8204cc784f81f9f0daaf31e02e3" callbackUri "http://www.sharesdk.cn" shareByAppClient true enable true } QZone { id 3 sortId 3 appId "100371282" appKey "aed9b0303e3ed1e27bae87c33761161d" shareByAppClient true bypassApproval false enable true } QQ { id 7 sortId 7 appId "100371282" appKey "aed9b0303e3ed1e27bae87c33761161d" shareByAppClient true bypassApproval false enable true } Wechat { id 4 sortId 4 appId "wx4868b35061f87885" appSecret "64020361b8ec4c99936c0e3999a9f249" userName "gh_b0c6a9ca668a" path "pages/index/index?id=mob" withShareTicket true miniprogramType 2 bypassApproval false enable true } WechatMoments { id 5 sortId 5 appId "wx4868b35061f87885" appSecret "64020361b8ec4c99936c0e3999a9f249" bypassApproval false enable true } WechatFavorite { id 6 sortId 6 appId "wx4868b35061f87885" appSecret "64020361b8ec4c99936c0e3999a9f249" bypassApproval false enable true } Facebook { id 8 sortId 8 appKey "1412473428822331" appSecret "a42f4f3f867dc947b9ed6020c2e93558" callbackUri "https://mob.com" shareByAppClient true enable true } Twitter { id 9 sortId 9 appKey "viOnkeLpHBKs6KXV7MPpeGyzE" appSecret "NJEglQUy2rqZ9Io9FcAU9p17omFqbORknUpRrCDOK46aAbIiey" callbackUri "http://mob.com" shareByAppClient true enable true } }}} ...

May 21, 2019 · 4 min · jiezi

swift开发中那些值得借鉴的写法

写在前面最近在学习swift,从github上下载很多demo进行学习,收获不小,发现了一些不错的写法,记录一下方便以后查询,同时分享给大家,共同成长。 UI相关的一些常量和辅助方法以下代码主要定义了一个swift工程中的UI部分的常量亮和定义,当然,这只是demo,正式工程可以按照这个思路进行扩展。一个XYUI结构体囊括了Screen、Color、Font三个子结构体,分别定义了屏幕、颜色、字体相关的常量和方法,结构清晰,方便后续扩展。 struct XYUI { struct Screen { static let Width : CGFloat = UIScreen.main.bounds.width static let Height : CGFloat = UIScreen.main.bounds.size.height static let NavH : CGFloat = XYUI.Screen.IphoneX == true ? 88 : 64 static let StatusbarH : CGFloat = XYUI.Screen.IphoneX == true ? 44 : 20 static let IphoneX: Bool = Int(XYUI.Screen.Height/XYUI.Screen.Width) == 216 //判断是否是iPhoneX序列 } // 颜色 struct Color { /// 主色调 static let primary = UIColor.hexString(color: "#FFCA07") static let black = UIColor.hexString(color: "#333333") static let white = UIColor.white } struct Font { static func fitFont(size:CGFloat) -> CGFloat { if UIScreen.main.bounds.size.width == 320 { return size * 0.8 } return size } static let f10 = UIFont.systemFont(ofSize: 10) static let f11 = UIFont.systemFont(ofSize: 11) static let f12 = UIFont.systemFont(ofSize: 12) static let f13 = UIFont.systemFont(ofSize: 13) static let f14 = UIFont.systemFont(ofSize: 14) static let f15 = UIFont.systemFont(ofSize: 15) static let f16 = UIFont.systemFont(ofSize: 16) static let f17 = UIFont.systemFont(ofSize: 17) static let f18 = UIFont.systemFont(ofSize: 18) static let f20 = UIFont.systemFont(ofSize: 20) }}关于cellIdentifier使用关于tableview和collectionView的cellIdentifier定义,在objective-c中,我之前是这样定义的: ...

May 21, 2019 · 2 min · jiezi

个推一键认证SDK重磅推出打造秒级登录体验增能开发服务

移动互联网时代,用户注意力的持续时间越来越短,他们追求便捷与高效。从账号密码登录、短信验证,到第三方登录甚至人脸识别登录,APP的注册/登录方式在逐步变化,开发者希望在这重要的交互端口提升用户的体验,并减少用户的流失。与此同时,面对层出不穷的密码破解术和薅羊毛方式,如何提升账户的安全,减少APP拉新、营销活动中的无用注册越来越被重视。 在这一背景之下,个推开发者服务新增“一键认证”产品,帮助APP解决登录流程冗长、虚假注册等问题。APP只需集成小巧灵活的一键认证SDK,就可以实现免密登录功能。基于运营商的验证能力,并结合自身的大数据分析能力,个推“一键认证”产品能够帮助APP实现安全与效益的双赢。 简化流程,保障用户账号安全从用户的角度来说,“一键认证”产品简化了登录的流程,为用户首次登录APP提供了畅通无阻的通道,有效地减少了他们的等待时间,帮助他们迅速地建立起与APP的连接。另外,个推“一键认证”还能够有效保障用户的账号安全,降低由于密码设置过于简单或同一密码多账号使用,造成的密码破解或泄露的风险。同时,该产品还能降低短信验证遭到劫持的风险,为用户的账号提供安全的“保护伞”。 秒级体验,兼顾用户“质”与“量”从APP的角度来说,“一键认证”产品能够有效地提高APP的注册转化率,在最关键的第一步“抓住用户的心”。传统的登录方式,在短信验证的过程中,用户可能会遇到界面来回切换、验证码下发延时或验证流程过长等问题,对一款新的APP失去试用的耐心。而个推的“一键认证”功能,可以帮助APP降低新用户的流失率。除此之外,个推“一键认证”产品基于个推海量的数据,能够从多维度进行分析,帮助APP提前识别问题设备,防范羊毛党及黑产,在追求转化“量”的同时提升了转化的“质”,为运营效果和业务安全双保障。 作为专业的数据智能服务商,个推升级打磨开发者服务的脚步从未停歇。在原有的消息推送、用户画像、应用统计服务之上,个推全新推出一键认证产品,并希望通过专业的服务和产品,为开发者提供更加全面、高效的开发工具,让开发的过程更加简便、智能。

May 18, 2019 · 1 min · jiezi

ObjectiveC-Method-Swizzling

Method Swizzling已经被聊烂了,都知道这是Objective-C的黑魔法,可以交换两个方法的实现。今天我也来聊一下Method Swizzling。 使用方法我们先贴上这一大把代码吧 @interface UIViewController (Swizzling)@end@implementation UIViewController (Swizzling)+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(swizzling_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } });}#pragma mark - Method Swizzling- (void)swizzling_viewWillAppear:(BOOL)animated { [self swizzling_viewWillAppear:animated]; NSLog(@"==%@",NSStringFromClass([self class]));}@end好的,上面就是Method Swizzling的使用方法,将方法- (void)swizzling_viewWillAppear:(BOOL)animated和系统级方法- (void)viewWillAppear:(BOOL)animated交换。常用的场景就是埋点,这个咱就不细说了。 ...

May 16, 2019 · 2 min · jiezi

抖音的分享和授权iOS

准备工作注册appkey (抖音开放平台)集成sharesdk(下载地址)Xcode配置:urlScheme为注册的appkey, 白名单:douyinsharesdk ,douyinopensdk业务代码初始化 import <ShareSDK/ShareSDK.h>[ShareSDK registPlatforms:^(SSDKRegister *platformsRegister) { //抖音[platformsRegister setupDouyinByAppKey:@"app_key" appSecret:@"app_secret"];}];分享可以分享图片,相册图片,单个视频,多个视频 分享图片 // 通用参数设置----图片分享可以使用相册地址、沙盒路径、网络图片地址NSString *imageURL = @"http://img.hb.aicdn.com/28a4962c297205e0868cdb45bb527e2bc5319f08f019-l7N1A3_fw658";NSMutableDictionary *parameters = [NSMutableDictionary dictionary];[parameters SSDKSetupShareParamsByText:nil images:@[imageURL] url:nil title:nil type:SSDKContentTypeImage]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:shareParams onStateChanged:^(SSDKResponseState state, NSDictionary userData, SSDKContentEntity contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@"成功!");}else{ NSLog(@"%@",error);}}];分享视频 // 通用参数设置----视频分享可以使用相册地址、沙盒路径,不支持网络视频,如果使用网络视频请先下载放到沙盒目录下或相册里 NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"mp4"];NSMutableDictionary *parameters = [NSMutableDictionary dictionary];[parameters SSDKSetupShareParamsByText:nil images:nil url:[NSURL URLWithString:videoPath] title:nil type:SSDKContentTypeVideo]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:parameters onStateChanged:^(SSDKResponseState state, NSDictionary *userData, SSDKContentEntity *contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@"成功!"); }else{ NSLog(@"%@",error); }}];分享多个视频 ...

May 15, 2019 · 1 min · jiezi

word如何做流程图word文档怎么把流程图进行组合

流程图(Flow Chart)是一种常见的工作图表。在企业中,流程图主要用于说明某一个过程,该过程可以是生产线上的工艺流程,也可用于表达完成任务所需的步骤。另外,流程图也常用于表示算法的思路,可以有效解决汇编语言和早期的BASIC语言环境中的逻辑问题。运用流程图的时候,需要使用一些标准符号代表某些类型的动作。如用菱形框表示判定,用方框表示流程。具体的表示方法整理如下: 流程图的分类流程图的种类多达10种,归纳整理如下: 但是根据使用的场景不同,大致可划分为7个类别,分别是商业流程图、跨职能流程图、数据流程图、事件管理流程图、IDEF图、工作流程图、SDL图。 商业流程图:又叫做业务流程图,是一种描述系统内部各人员与各单位的业务关系、管理信息以及作业顺序。它是一种物理模型,借助于此,分析人员可以找出业务流程中的不合理流向,方便优化。跨职能流程图:可显示进程中各个步骤之间的相互关系,也能显示执行它们的职能单位。跨职能流程图按照分布的方向不同,可以分为水平跨职能流程图和垂直跨职能流程图。当跨职能流程图用于UML的时候,又被叫作泳道图。数据流程图:一种描述系统数据流程的工具,可以将抽象的数据独立出来,通过特定的图形符号来展示信息的来龙去脉和实际流程。这是一种能描绘信息系统逻辑模型的重要工具。事件管理流程图:这是IT服务管理中重要的流程,当一个事件被输入的时候,服务台的操作人员会依据事件的影响范围和紧急程度,对其进行初步的归类评估。IDEF图:IDEF,即集成计算机辅助制造,一种用于描述企业内部运作的一套建模方法。IDEF图是用于表达这种建模方法的图示。工作流程图:通过适当的符号来记录工作事项,能够反映一个组织系统中各项工作之间的逻辑关系。工作流程图可以帮助管理者了解实际工作活动,并去除工作中多余的工作环节,进而提升工作效率。SDL图:使用说明和规范的语言(SDL)为通信、电信系统以及网络创建图表。流程图的画法了解流程图的类别后,那又该如何绘制我们所需的流程图呢?下面我们以亿图图示软件为例,介绍如何快速创建专业的流程图。 第1步:打开软件,“新建”-“流程图”,然后根据自己的需求,选择绘图模板。比如选择基础流程图,双击鼠标即可打开绘图面板。值得一提的是,亿图图示里除了模板,还有对应的例子,如果是新手绘图,可以借鉴流程图例子帮助自己加深认识。 第2步:从左侧符号库里选择所需的图形符号,并拖动至画布中。并依次添加,直至完毕。 第3步:使用连接线符号,对各个图形符号进行连接。亿图图示软件的连线十分便捷,鼠标点击需要连线的两端,即可生成直角连线。如果连线的位置不满意,也可以通过鼠标拖动线条的方式进行修改。 第4步:最后再添加文字和注释,一份完整的流程图即可大功告成。另外,亿图软件还有丰富的背景图案以及标题栏样式可以选择,这将大大提升流程图的颜值。 特别注意: 亿图图示软件拥有一键更换样式的功能,无需自己搭配,方便省事。绘制完成的作品,可以导出图片、Html、Word、PPT、Excel等多种格式;也可以将作品保存在亿图云,不担心丢失的问题。软件还有打印功能,即见即所得的打印方式,让流程图的存在不再只是虚拟化。支持跨平台操作系统,不论是window、Mac还是Linux,都可以安装使用亿图软件。多类型的图形图表设计软件,不仅可以绘制流程图,还能绘制思维导图、组织架构图、户型图、信息图等260种图示。https://www.edrawsoft.cn/flow...

May 14, 2019 · 1 min · jiezi

iOS知识梳理-category和extension

category和extensioncategory的能力category主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。苹果早年一份官方文档指出,category主要有三种用途: 给现有的类添加方法;将一个类的实现拆分成多个独立的源文件;声明私有的方法。这里我们逐个分析。 1. 给现有的类添加方法常见。比如给UIColor添加一些跟16进制色值互转的方法。如下: @interface UIColor (Hex) + (UIColor*) colorWithHex: (NSUInteger) hex;@end@implementation UIColor (Hex)+ (UIColor*) colorWithHex: (NSUInteger)hex { CGFloat red, green, blue, alpha; red = ((CGFloat)((hex >> 16) & 0xFF)) / ((CGFloat)0xFF); green = ((CGFloat)((hex >> 8) & 0xFF)) / ((CGFloat)0xFF); blue = ((CGFloat)((hex >> 0) & 0xFF)) / ((CGFloat)0xFF); alpha = hex > 0xFFFFFF ? ((CGFloat)((hex >> 24) & 0xFF)) / ((CGFloat)0xFF) : 1; return [UIColor colorWithRed: red green:green blue:blue alpha:alpha];}@end2. 将一个类的实现拆分成多个独立的源文件这种在小项目中可能少见一些,但在大型项目中比较多。比如某日活过亿的iOS软件,其配置系统中大约有400份配置,很多功能根据配置的内容会有不同的表现,不同用户拉取不同内容的配置。这400份配置会在用户首次登陆时全量拉取,在以后登陆时增量拉取。注意这都是一条请求带回来的。如果把这400份配置的解析处理都写到一个文件里,这个文件显然会膨胀到一个很恐怖的地步。最好的方式就是让每个业务添加自己的分类,在分类中处理自己的配置内容。 ...

May 14, 2019 · 1 min · jiezi

如何更好的使用bug管理工具MadPecker场景篇

Hi,大家好,我是MadPecker,一款免费,好用的缺陷(Bug)管理SaaS软件 今天,我给大家介绍一下在项目开发过程中可能遇到的几个场景! 网址:www.madpecker.com 场景一“小啄,首页的UI样式帮我改一下。”“小啄,你那个接口写好了没有啊?”“小啄,刚刚和你说的那个BUG改得怎么样了?” ...... ......“老大,你刚刚叫我改什么来着?”我们都知道,在项目进行的过程中一定会出现形形色色的问题。显然,口头表述并不是一种好的问题解决方式。如果把各种问题全部堆积在聊天工具上,开发人员不停地上翻聊天记录也不是个办法。MadPecker的任务管理工具把所有的任务归类好,展示在任务列表上,不管是BUG、测试、开发、还是需求,任务创建者只需要把这个任务指派给项目中的某个成员,这个处理人就能收到提醒并及时处理任务。 上传图片时,MadPecker支持点击上传、拖拽上传和粘贴上传。 提交BUG页 BUG详情页场景二“小啄,我注册登录的模块已经开发好了,你帮我测一下呗。”“行!”......“咦,小啄,你是不是没写测试用例啊,这个功能你好像漏测了呢!” 测试漏项、测试不规范等问题一直困扰着测试工作者。MadPecker中的测试管理共分为测试用例、测试场景、测试执行三个部分,测试人员可以根据场景录入测试用例,也可以在录入测试用例的时候关联测试场景,并且用例可以复用,进一步减少了测试人员的工作量。在测试执行中,对一个场景测试不通过,测试人员可以直接提交BUG给开发人员。MadPecker完成了测试人员与开发人员之间的互动,把整个测试流程变得紧凑起来。 提交测试用例页 测试场景列表页 测试执行操作页场景三“在XX应用商店发布应用太慢了吧!”没错,对于很多急于上线的用户来说,大部分应用商店的上传机制经常会让应用上传之后还要经过漫长的时间才能将应用推向大众。MadPecker支持应用随时上传随时下载,上传应用之后,系统自动生成二维码,方便用户下载。并且MadPecker支持合并应用功能,不同平台的应用可以合并成同个二维码,所以不同平台的手机都可以扫描同个二维码下载应用了。 下载应用界面 以上为本期全部内容,如果您想要了解更多关于MadPecker的使用或者您想要了解我们的平台其他功能更新,欢迎您到我们的帮助中心查看。同时,如果您对我们的平台有哪些疑惑或建议,欢迎您到我们的用户社区/官方QQ群/微信群畅所欲言。

May 13, 2019 · 1 min · jiezi

swift5展示全球国家列表

CountryCodeList是swift5学习的一个项目。主要练习了UITableView的swift使用,使用HandyJson把从本地读取的json文件转化为数据模型数组。全球国家列表的数据来源是之前从一张全球国家列表的sql表中使用Python洗出来的数据,经过处理之后形成了一个Json文件。 每一个国家对象主要包括了:国家的英文名、国家的缩写、国际区号。 全球国家列表已经进过精心整理成JSON文件,数据对于要做面向海外开发的同学还是有一定帮助的,可以直接使用。demo地址 demo结构概览:效果图:

May 12, 2019 · 1 min · jiezi

手把手教你把项目上传到github上

原起为什么要把自己做的一些东西上传到github上?在软件行业竞争日益激烈的行情下,面试的时候,github上有高Star开源项目,绝对是个加分项。同时知识分享也是一种美德。这种双向受益的事,何乐而不为呢。下面就手把手教你把你自己做的项目上传到github上,让你在面试的时候脱颖而出。 项目上传github的步骤步骤快速一览: cd 到本地项目的根目录下git initgit add .git commit -m "提交说明"git remote add origin '你在github为该项目创建的仓库地址'git pull origin mastergit push -u origin master -f第一步:初始化git仓库cd到你的本地根目录下,执行git init,在你的项目下创建了一个git仓库。 第二步:将项目下的所有文件添加到git仓库在上一步的同级目录下执行git add .。提别提示,此步如果怕git add .出问题,可以先使用git status先查看那些文件被修改了,然后在分别添加就保险了,只是那样会比较麻烦,此处看个人习惯。 第三步:加add到缓存区的文件commit到本地仓库还是在刚才的目录,执行git commit -m "提交注释"。 第四步:去github上创建自己的Repository只有本地仓库肯定是不够的,这一步,我们就去github上创建仓库。在github上创建仓库的步骤如下: 第五步:将本地的仓库关联到github上git remote add origin https://github.com/ningjianwe...记得把上面的仓库地址换成你自己的仓库地址。 第六步:上传代码之前先pull以下github上的代码git pull origin master第七步:把代码push到github上git push -u origin master执行完后,如果没有异常,等待执行完就上传成功了,中间可能会让你输入Username和Password,你只要输入github的账号和密码就行了。 注意事项如果执行最后一步的时候报了类似下方的错, 不要慌,在第七步的命令后跟一个参数-f即可。 git push -u origin master -f这个时候会显示项目正在上传,等着上传完就可以在github上看到你的项目了。 温馨提示:不要忘记在github上添加你的SSHkey,这个请自行百度。

May 12, 2019 · 1 min · jiezi

iOS知识梳理-Objc语法中值得注意的东西

属性声明@property、@synthesize和@dynamic objc推荐我们通过set/get方法访问对象的属性。很显然,为每一个属性手动添加set/get方法的声明和实现是个性价比很低的重复劳动。因此,objc提供了一些关键字帮助我们简化这一过程。这几个关键字实际上就是这么回事儿。 单独做了总结:Objective-C的@property、@synthesize和@dynamic 注释常规的//和/* */就不多说了 值得一提的是#pragma语法, 我们可以用#pragma mark - UITableViewDataSource这样的写法来为代码分块,xcode将会提供导航。 参考:#pragma enumtypedef NS_ENUM(NSUInteger, TTGState) { TTGStateOK = 0, TTGStateError, TTGStateUnknow};参考 Enum in OC 空指针NULL:C的空指针 (void *)0nil:objc的空对象 (id)0Nil:空的类指针 (Class)0NSNull:nil的包装类,为了表示dic里面的某个value为空 [NSNull null]总结:前三个数值上是相等的,在objc中用nil比较普遍 参考nil / Nil / NULL / NSNull BOOL常见的就是bool和BOOL。 bool是c语言的,单独的一个类型(_Bool),在数值上true = 1,false = 0 bool类型只有true和false两种状态。 另外,条件判断语句在判断时,是以0为false,非0为true进行判断的。 由此有以下结论: 2 == true //假(bool)2 == true //真2 //真BOOL是ObjC定义的真假值类型,它在64位机器上等价于bool,在32位机器上是signed char 其定义如下,参考objc4-750 /// Type to represent a boolean value.#if defined(__OBJC_BOOL_IS_BOOL) // Honor __OBJC_BOOL_IS_BOOL when available.# if __OBJC_BOOL_IS_BOOL# define OBJC_BOOL_IS_BOOL 1# else# define OBJC_BOOL_IS_BOOL 0# endif#else // __OBJC_BOOL_IS_BOOL not set.# if TARGET_OS_OSX || TARGET_OS_IOSMAC || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K)# define OBJC_BOOL_IS_BOOL 0# else# define OBJC_BOOL_IS_BOOL 1# endif#endif#if OBJC_BOOL_IS_BOOL typedef bool BOOL;#else# define OBJC_BOOL_IS_CHAR 1 typedef signed char BOOL; // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" // even if -funsigned-char is used.#endifYES和NO ...

May 11, 2019 · 1 min · jiezi

iOS知识梳理-开篇

工作快2年,大部分时候都是在怼业务逻辑,回头看看其实有很多东西掌握得不是很好,计划整体梳理一下iOS开发的相关知识。先列个粗略的提纲,慢慢更新。 OC关于Objective-COC语法中值得注意的东西代码规范:Google Objective-C Style GuideOC的内存管理runtimeiOS平台相关iOS系统能力总览生命周期动画,事件多线程,gcdrunloop网络数据持久化软件架构swiftswift语法Objc与swift的对比

May 11, 2019 · 1 min · jiezi

iOS知识梳理-关于ObjectiveC

作为早期的面向对象语言,且直到现在仍然为人们所熟知,Objective-C显然有其独到之处。这里对Objective-C的特点进行简单阐述。 动态?静态?Objc这个语言具有比较强大的动态特性,常提到的包括动态类型、动态绑定、动态加载。动态类型主要指类指针id,其类型是在运行时才能决定的。动态绑定指一个对象的方法可以在运行时添加、替换。动态加载指运行时可以动态地创建新的类。因此我们称Objc是一个动态类型的动态语言。 虽然Objc有着诸多动态特性,但它并不是个像JavaScript、python那样典型的动态语言,有的人会说Objc是个“动静结合”的语言。它虽然有动态类型,但是更常用的是静态类型系统;而动态加载、动态绑定更是高级特性而非基本语法。 总的而言,Objc写起来更像静态语言的感觉(啰嗦),但它有着丰富的动态特性。 消息传递 vs 函数调用对象间的通信形式应当是怎样的?函数调用的方式是指,调用方直接或间接地拿到被调用方的函数地址进行函数调用。而消息传递是指,调用方只是把方法名和参数等信息传递给被调用方,具体的执行由被调用方处理。 显然,消息机制会更加灵活。早年的半篇神作function/bind的救赎(上)中大佬对消息传递和方法调用的区别做了深刻分析,阐述了消息机制的优点,值得一读。不过作者可能把调用机制看得太重了。 内存管理内存管理大体上有三类,开发者手动维护、基于引用计数并提供一些机制解决循环引用、自动垃圾收集。自己手动派主要就是c/c++了,性能最好,缺点是开发者心智负担重。垃圾回收派是最多的,现在流行的语言大多都是gc的路子,Java/JavaScript/python/golang等等。优点是轻松,缺点是通常情况下gc是阻塞的,触发gc时会有几十ms到数百ms的卡顿。现在也有并发gc的技术,不过用得并不广泛,不太了解。Objc属于基于引用计数并提供一些机制解决循环引用的。我们知道,朴素的引用计数存在的问题是无法识别循环引用。Objc提供了ARC机制,开发者标记对象间的引用是强引用还是弱引用,Objc的runtime来解决剩下的事情。使得内存管理的性能和开发者的负担得到了比较好的平衡。同属这一流派的还有个Rust的所有权机制。 方法定义和方法调用的语法这个点只是个小细节了,跟上面的不在一个层面,不过这可能是大部分人初学Objc时觉得跟其它语言最大的不同。 Objc的方法声明是这个样子的: - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc;它包含了方法标示符(-)、返回类型(void)、方法名称(insertString:atIndex:)、参数类型和参数名称。注意,每个参数前面那一两个次的描述,是算在函数名里面的。其实回头看来,这样的语法是有一定优点的。前两天看一个c++的库,里面有个函数有12个参数,调用的时候就很烦,总担心我填的参数错位了: demoFunc(var1,var2,var3,var4,var5,var6,var7,var8,var9,var10,var11,var12)Objc虽然啰嗦了点,但是看起来其实会比较清晰,比如一个很长的方法: - (id)initWithBitmapDataPlanes:(unsigned char **)planes pixelsWide:(NSInteger)width pixelsHigh:(NSInteger)height bitsPerSample:(NSInteger)bps samplesPerPixel:(NSInteger)spp hasAlpha:(BOOL)alpha isPlanar:(BOOL)isPlanar colorSpaceName:(NSString *)colorSpaceName bitmapFormat:(NSBitmapFormat)bitmapFormat bytesPerRow:(NSInteger)rowBytes bitsPerPixel:(NSInteger)pixelBits;调用时是这个样子: NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&data pixelsWide:gWidth pixelsHigh:gHeight bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:gWidth*3 bitsPerPixel:24];每个参数是啥会比较明确。

May 11, 2019 · 1 min · jiezi

ios-原生骨架库网络过渡动画封装

最新版 2.0.5,release版本目录关于 TABAnimated实现原理优点演变过程效果图安装 使用Cocoapods手动导入使用步骤扩展回调Tips属性相关强调最后关于 TABAnimatedTABAnimated的起源版本是模仿简书网页的骨架屏动态效果。在v1.9探索过模版模式,但是重复的工作量并不利于快速构建,而且两种模式的存在不合理,所以在v2.1删除了这种设定,但是模版模式的出现到删除,并不是没有收获,相反带来了更合理的实现方案,更便捷的构建方式。 实现原理TABAnimated 需要一个控制视图,进行开关动画。该控制视图下的所有subViews都将加入动画队列。 TABAnimated通过控制视图的subViews的位置及相关信息创建TABCompentLayer。普通控制视图,有一个TABLayer表格视图,每一个cell都有一个TABLayerTABLayer负责管理并显示所有的TABCompentLayer。 当使用约束进行布局时,约束不足且没有数据时,致使subViews的位置信息不能体现出来,TABAnimated会进行数据预填充。 优点集成迅速零耦合,易于将其动画逻辑封装到基础库高性能,极少的内存损耗链式语法,方便快捷,可读性高完全自定制,适应99.99%的视图演变过程看不清楚可以放大一下 简单说明一下:第一张图:原有表格组件, 有数据时的展示情况第二张图:是在该表格组件开启动画后,映射出的动画组,相信你可以看出来,效果并不是很美观。第三张图:针对这个不美观的动画组,通过回调,进行预处理,下文进行说明 效果图| 动态效果 | 卡片投影 | 呼吸灯 | | ------ | ------ | ------ | | | | | | 闪光灯 | 分段视图 | 嵌套表格 || ------ | ------ | ------ | | | | | 安装使用 CocoaPodspod 'TABAnimated'手动导入将TABAnimated文件夹拖入工程 使用步骤您只需要四步 在 didFinishLaunchingWithOptions 中初始化 TABAimated还有其他的全局属性,下文用表格呈现。 **老用户注意:原TABViewAnimated已经改名为TABAnimated**如今的TABViewAnimated已经成为UIView的骨架对象 // init `TABAnimated`, and set the properties you need.[[TABAnimated sharedAnimated] initWithOnlySkeleton];// open log[TABAnimated sharedAnimated].openLog = YES;控制视图初始化tabAnimated普通view: ...

May 10, 2019 · 2 min · jiezi

马蜂窝-iOS-App-启动治理回归用户体验

增长、活跃、留存是移动 App 的常见核心指标,直接反映一款 App 甚至一个互联网公司运行的健康程度和发展动能。启动流程的体验决定了用户的第一印象,在一定程度上影响了用户活跃度和留存率。因此,确保启动流程的良好体验至关重要。 「马蜂窝旅游」App 是马蜂窝为用户提供服务的主要阵地,其承载的业务模块不断丰富和完善,产品功能日趋复杂,已经逐渐成长为一个集合旅行信息、出行决策、自由行产品及服务交易的一站式移动平台。 「马蜂窝旅游」iOS App 历经几十个版本的开发迭代,在启动流程上积累了一定的技术债务。为了带给用户更流畅的使用体验,我们团队实施了数月的专项治理,也总结出一些 iOS 启动治理方面的实践经验,借由本文和大家分享。 0X0 如何定义「启动」要分析和解决启动问题,我们首先需要界定启动的内涵和边界,从哪开始、到哪结束,中间经历了哪些阶段和过程。以不同视角去观察时,可以得出不同结论。 技术视角App 启动原本就是程序启动的技术过程。作为开发人员,我们很自然地更愿意从技术阶段去看待和定义启动的流程。 App 启动的方式分为冷启动和热启动两种。简单来说,冷启动发生时后台是没有这个应用的进程的,程序需要从头开始,经过漫长的准备和加载过程,最终运行起来。而热启动则是在后台已有该应用进程的情况下发生的,系统不需要重新创建和初始化。因此,从技术视角讨论启动治理时,主要针对冷启动。 从技术视角出发,分析 iOS 的启动过程,主要分为两个阶段: pre-main: main() 函数是程序执行入口,从进程创建到进入 main 函数称为 premain 阶段, 主要包括了环境准备、资源加载等操作;post-main: main() 函数到-didFinishLaunchWithOptions:方法执行结束。该阶段已获得代码执行控制权,是我们治理的主要部分。<premain>                  <postmain>  +----------------X------------------------------------X--------->start             main                   -didFinishLaunchWithOptions:用户视角iOS App 是面向终端用户的产品,因此衡量启动的最终标准还是要从用户视角出发。 从用户视角定义启动,主要以用户主观视觉为依据,以页面流程为标准。这样看来,常见的 App 启动可以分为三个阶段: T1:闪屏页 闪屏页是启动过程中的静态展示页。在冷启动的过程中,App 还没有运行起来,需要经历环境准备和初始化的过程。这个过渡阶段需要展示一些视图,供阻塞等待中的用户浏览。iOS 系统 (SpringBoard) 根据 App Bundle 目录下的 Info.plist 中"Launch screen interface file base name"字段的值,找到所指定的 xib 文件,加载渲染展示该视图。闪屏页的展示是系统行为,因此无法控制;加载的是 xib 描述文件,无法定制动态展示逻辑,因此是静态展示。对应技术启动阶段的 pre-main 阶段T2(可选):欢迎页(广告) App 运行后根据特定的业务逻辑展示的第一个页面。常见的有广告页和装机引导流程。欢迎页是业务定制的,因此可根据业务需要优化展示策略,该阶段本身也是可选的。T3:目标页 (落地页)  App 启动的目标页。可以是首页或特定的落地页目标页的加载渲染渲染完成标志着 T3 阶段的结束,也标志着启动流程的结束。启动治理的最终目标是提升用户体验,在这样的思想下,本文关于启动流程的讨论主要围绕用户视角进行。 0X1 方法论及关键指标APM 方法论对 iOS 启动的治理,本质上是对应用性能优化 (App Performance Management) 的过程,其基本的方法论可以归纳为: ...

May 10, 2019 · 3 min · jiezi

马甲包审核被拒对应的处理方案

写在前面马甲包审核被拒,目前网上流传最广的就是2.1和4.3,在查看了很多文章之后,从两篇介绍的比较详细的文章中把这两个大礼包对应条款的解决方案,整理出来,为开发马甲包做一个准备,同时分享个大家。 2.1大礼包2.1大礼包主要包含1.1.6、2.3、2.3.1、3.1.1、4.3,5.2.1,5.3.4等条款。 1.1.6 包含虚假信息,功能或误导性元数据一般是因为标题或者icon和截图等有误导的嫌疑,或有些关键词是被苹果列入黑名单的,例如红包包、话费等,但审核条款又没有明确指出。对于上述情况的解决办法是使用保守的文案或素材。2.3.0 含有不经审核也可更改App功能如改变App功能的热更新,这种情况需要把热更新去除,或者对热更新模块代码做深度混淆处理!2.3.1 含有隐藏功能或为记录的功能,包括定向到赌博或彩票网站的开关。常规解决方式:去除隐藏功能模块代码或将需要隐藏功能的代码及定向跳转链接网址做混淆处理,适当增加逻辑复杂度。3.1.1 应用内购以外的支付机制来解锁App中的功能对于第三方支付,尽可能避免使用易扫描的SDK版本,推荐使用H5版本支付。支付跳转链接相应的做屏蔽混淆处理。4.3.0 是另一款应用的复制品,或与另一款应用明显相似。被认为是重复App或马甲包,变更UI和名称,填充无用代码等。A、改名字;B、修改素材及UI色调等,例如修改icon,修改主色调;C、修改功能界面等,可改功能可做小开关;D、填充代码(++最好50%以上++)或注释块;5.2.1 未由拥有并负责提供该应用程序提供的任何服务的法律实体提交。未提供 App 上架所需的行业资质,比如:金融营业许可证、游戏版号等。这个上面讲过些常规方式。5.3.4 含有货币游戏(如:体育下注、赌场游戏等),但未提供相关许可资质。同上,提供资质,审核时最好不要勾选中国区,或使用海外账号。4.3 被拒的原因及处理方案第一种:代码重复(分为三种)1.可能你之前用这套源码上过一个包,现在用这套代码直接改一个logo跟名字再上一个马甲对应的处理方案: 第一步:工程中的文件夹的名字全部进行修改。第二步:每一个工程都有一个类前缀,我们需要取一个长一点的类前缀,并且这个类前缀在你的整个工程一定是一个唯一的字符串,我们假设这个类前缀是PayDayLoan,现在我们需要生成一个控制器,控制器的结尾Controller也需要用一个特定的字符去代替,比如:Director,剩下的View以及object做法类似,就不一一介绍了,做马甲的时候就是把这些名字用另一个唯一的字符去代替,尽量长一点。 第三步:把另一个其他的工程中的类全部导入进来, 主要是混淆代码, 在现有的工程中调用, 可以没有任何效果, 只是单纯调用方法。 敲黑板 单一的加入垃圾代码混淆是没用的!2.如果你的这套源码在一个账号上提交过,但是被拒了,后来因为其他原因你不得不在别的账号上重新提交此源码。对应的处理方案: 这种情况需要在第一个账号做一下处理,xcode新建一个应用,直接用之前提交过的bundleId打包,logo用一个纯白或者纯黑的图片,将这个新建的应用提交到应用市场,构建版本中将之前被拒的包移除,用这个新的应用顶替之前被拒的包,app名字改成“作废-此应用不再提交”后面再随便加一个数次,因为这个名字别人已经用过了,app描述跟app名字一样,剩下的信息全部删除,最后点击保存即可,不需要提交审核。3.你的源码只要提交到itunesconnect里面,就算没提交审核,当你再次使用此源码提交审核的时候.对应的处理方案,参照2的处理方案。第二种:界面功能相似这种情况简单的改源码已经没用了,需要在原有的app上加一些不同的功能。我用借贷类举例说明该如何解决,其他类型的app可以参考。以下举两个应用说明,分别用A应用与B应用代替,你需要如何处理并且如何回复审核人员。 A应用是给没有信用卡的用户使用的一款借款App,B应用是给有信用卡的用户使用的一款借款App。A应用的最高借款额度是1000元, B应用的最高借款额度是25000元。A应用的还款时间是7天与14天, B应用的还款时间是28天。A应用内部有贷款计算器功能, B应用只是一个普通的贷款app,并无其他功能。两款app是我们公司内不同的部门开发的app,分别针对不同的用户人群。 总结 针对界面功能相似的App,我们肯定是要做对应的处理。处理之后我们可以从以下几个方面进行回复:两个App的用户群体差异。两个App的功能差异。两个App同一个公司的两个部门针对不同的用户群体开发(和1.相似)。就是尽可能的找出两个App的不同,称述给苹果的审核人员。第三种:App名字被使用过这一种,可能是最容易被忽略的,取名字之前一定要先搜索appstore有没有同名应用,尽量避免同名应用。 代码混淆工具KLGenerateSpamCode 垃圾代码生成器 CodeMixer,代码混淆,图片改名,批量修改类名 参考文章iOS马甲包审核以及常见审核问题 骨灰级iOS工程师手把手教你如何上架马甲包!

May 10, 2019 · 1 min · jiezi

我是如何用Madpecker去管理bug的

现在国内外的bug工具很多,今天我要说的是我们团队现在用的一款功能强大操作方便的bug管理工具---MadPecker,我们平时提BUG、做测试、敏捷开发什么的都是用的这个。 1.登录他们的官网 https://www.madpecker.com/home2.注册一个账号,这个工具是完全免费的,这一点是很良心的 3.接着就可以进入MadPecker工具平台了 4.首先创建一个项目,并给项目一个名称 5.为项目添加所需工具(敏捷面板真的不错) 6.添加项目成员 7.bug管理界面,可以在这里创建、管理、查看bug 8.同时可以在bug统计界面来查看多角度的bug统计报表 9.测试管理界面,在这里可以直接创建测试流程,通过测试执行进行测试 10.应用发布界面,在这里发布你的应用 11.敏捷面板中的需求看板可以看到客户提的各种需求以及需求所在的流程位置 12.敏捷面板中的迭代看板,在此看板进行每次的迭代,既可以查看与迭代相关的需求,也可以在这里创建迭代中的所需处理的任务 MadPecker这个bug管理工具非常适合用于敏捷开发,尤其契合各类开发团队的办公方式,从功能上来看摒弃了一些办公软件花哨的东西把功能着重于实际,而且现在来看目前此软件属于刚上线,后续应该会有一些新功能的添加。

April 30, 2019 · 1 min · jiezi

XorPay-支付平台-新增个人支付宝当面付接口

XorPay 支付平台 新增 个人支付宝当面付 接口,支付宝官方支付接口个人可申请,欢迎开发者使用。 「 XorPay 支付平台」 是个人可用的 支付宝/微信支付 接口,支持 当面付 / NATIVE / JSAPI / 收银台 / 小程序 / WAP / H5 等支付方式,资金由 支付宝/微信 官方 T+1 结算自动下发个人银行卡( 其中支付宝为即时到账,到你的支付宝账号) 个人支付宝当面付接口XorPay 技术文档GitHub 仓库Demo欢迎留言讨论

April 29, 2019 · 1 min · jiezi

iOS马甲包上架招式

一、什么是马甲包马甲包是利用App store 规则漏洞,通过技术手段,多次上架同一款产品的方法。马甲包和主产品包拥有同样的内容和功能,除了icon和应用名称不能完全一致,其他基本一致。 二、为什么做马甲包,做马甲包有什么好处?1、导量、刷榜、增加关键字覆盖一个App的关键字是有限的,马甲包能增加我们的搜索关键词,增加我们的App被用户搜索和下载的几率。一个本身质量过硬的App,马甲包能够帮助我们迅速提升排名。 2、抗风险一些不确定效果的新功能,我们可以在马甲包上先做测试,效果OK之后,我们再迭代到主App上,这样即使新功能效果不佳也不会影响主App的流量。 三、马甲包的开发招式1、UI部分在原有的UI的基础上,修改新的UI。启动图修改,坚决不能和之前的一样。logo修改,坚决不能和之前的一样。2、代码部分修改工程中文件夹名字(全部需要修改)。修改项目名字。修改类名,前缀统一的进行统一替换,后缀名也可以根据情况进行修改(view/ViewController/model)。添加混淆代码,修改之前的方法名,往类中添加不相关的方法(此处建议使用 #pragma mark -(此处是马甲包的特殊标记)进行标记,方便后续修改)。修改boundID。在之前App的基础上,增加或者删除部分功能,把两个App之间的差异尽量最大化。四、上架招式上架马甲包,最好是准备一个新的账号,不要影响主App,防止账号被封或者处罚影响主App的正常下载。上架的时候项目描述不要和主App的一样。项目宣传也不要和主App的一样。提供给苹果的测试账号也提供新的。上传马甲包的电脑,不要和上传主App使用同一台电脑(据说会检测上传包的ip)。五、总结马甲包本身是不符合苹果的上架规范的,但是为了让更多的用户下载我们的App,提升我们App的排名,我们不得不想尽办法制作马甲包,顶风作案。开发马甲包我们主要从UI展现和代码实现尽量的把它们做的不像相同的App,但是它们的核心内容是相似的,用户流量最终流向同相同的服务器,实现导量和提升排名的功效。 我们在上架马甲包的时候还要尽量保证主App的安全,所以使用单独的账号上架马甲包,为了提高过审率,还要使用不同的电脑进行包的上传。项目描述&产品宣传等等都不能一样,就是尽量做成两个App,但是呢周期又要短。 最后,马甲包只是一个辅助,我们的App本身一定要有内容,这样才能够留住用户,否则就算用户下载了,很快也会卸载。导致“留住了用户的人,没有留住用户的心”,只留下了用户信息,不能为我们带来实质性的价值。 参考文章 iOS马甲包上架总结

April 29, 2019 · 1 min · jiezi

ObjectiveC的propertysynthesize和dynamic

objc推荐我们通过set/get方法访问对象的属性。很显然,为每一个属性手动添加set/get方法的声明和实现是个性价比很低的重复劳动。因此,objc提供了一些关键字帮助我们简化这一过程。实际上就是这么回事儿。 @property首先来看现在的property: 例如: @interface ViewController : UIViewController@property (nonatomic,assign) BOOL testVar;@end简单理解,相当于声明了成员变量_testVar,声明了实现了set方法setTestVar、get方法testVar,等价于: @interface ViewController : UIViewController{ BOOL _testVar;}- (void)setTestVar:(BOOL)newTestVar;- (BOOL)testVar;@end@implementation ViewController- (void)setTestVar:(BOOL)newTestVar{ _testVar = newTestVar;}- (BOOL)testVar{ return _testVar;}@end@synthesize@synthesize可以指定set/get方法的实现。 1. 默认默认地,@synthesize会生成一个同名的成员变量作为set/get的目标。 例如: @interface ViewController : UIViewController@property (nonatomic,assign) BOOL testVar;@end @implementation ViewController@synthesize testVar;@end等价于: @interface ViewController : UIViewController{ BOOL testVar;}- (void)setTestVar:(BOOL)newTestVar;- (BOOL)testVar;@end@implementation ViewController- (void)setTestVar:(BOOL)newTestVar{ testVar = newTestVar;}- (BOOL)testVar{ return testVar;}@end可以看到,set/get方法指向了成员变量testVar,也不会再默认生成_testVar。 注意,很多人误认为@synthesize默认生成的成员变量是_testVar,据我所知这是不对的。 官方文档: Important: If you use @synthesize without specifying an instance variable name, like this: ...

April 29, 2019 · 1 min · jiezi

组件化实践

最近想了解一些组件化的知识,去看了Casa写的iOS应用架构谈 组件化方案这篇文章,Casa在文中针对蘑菇街的组件化方案提出了一些不同的观点,陈述了自己的组件化方案。 大神们讨论具体的实施方案,是对理论的描述,在架构层面来分析利弊,我看过之后感觉还是有点晦涩,具体的方案异同之处我们先不说,今天我们先从应用着手,在自己当前的工程实施组件化。 当然了,我们选择使用的方案是Casa的CTMediator。 准备首先我们得先了解组件化这个概念,其实通俗的讲,就是把我们的项目拆解成一个一个的小组件分别管理。我们平时使用cocoapods继承的三方的库,可以理解成是一个公有的组件。我们项目中,也可以把一些模块拆解出来,使用cocoapods来集成。这样拆解成一个个的组件的好处有很多,比如说业务模块之间解耦,复用模块,节省编译时间等等。 所以我们要先学会创建cocoapods私有库。 这里多说一句,Casa的组件化方案在实施的时候,每独立出来一个组件,就会相应的创建一个Category工程,作为中间的调度,所以说,我们每做一个组件,就要创建两个私有的pod工程。 我们结合Casa这篇在现有工程中实施基于CTMediator的组件化方案,来做一下补充或者说是注解吧,本文中的流程取自于上文。 创建私有Pod工程1. 先去开一个repo,这个repo就是我们私有Pod源仓库2. pod repo add [私有Pod源仓库名字] [私有Pod源的repo地址]3. 创立一个文件夹,例如Project。把我们的主工程文件夹放到Project下:~/Project/MainProject4. 在~/Project下clone快速配置私有源的脚本repo:git clone git@github.com:casatwy/ConfigPrivatePod.git5. 将ConfigPrivatePod的template文件夹下Podfile中source 'https://github.com/ModulizationDemo/PrivatePods.git'改成第一步里面你自己的私有Pod源仓库的repo地址6. 将ConfigPrivatePod的template文件夹下upload.sh中PrivatePods改成第二步里面你自己的私有Pod源仓库的名字首先我们先创建一个名为Project的文件,然后把我们项目的主程序,我们叫做MainProject放到Project路径下,然后在Project路径下clone出我们需要的脚本(Casa提供) 在~/Project下clone快速配置私有源的脚本:git clone git@github.com:casatwy/ConfigPrivatePod.git现在我们的文件目录结构是这样的。 Project├── ConfigPrivatePod(脚本文件)└── MainProject在Project路径下创建我们的组件工程(一个普通的iOS工程),我们把这个工程名字叫PayComponents (模拟抽取项目中的支付模块)。 当前目录结构 Project├── ConfigPrivatePod├── MainProject└── PayComponents有了本地的工程之后,我们现在需要创建一个repo,作为我们的私有pod源仓库。也就是在github,或者gitee(码云)上面创建一个项目,放我们的项目代码,命名PayComponents。 然后呢,我们还需要创建一个东西,就是私有Pod源仓库名字。 pod repo add [私有Pod源仓库名字] [私有Pod源的repo地址]落实到我们这个项目中,我们应该这样写。 pod repo add PayComponents https://gitee.com/LittleBin/PayComponents.git那这到底代表着我们创建了什么? 我们打开finder->前往->前往文件夹,然后输入~/.cocoapods/repos 可以看到目录是这样子的 repos路径下面有一个master,一个payComponents。这两个文件夹我们可以粗略的认为他和pod search还有install有关。 打个比方就拿search来说,我们查询一个库的时候会用下面这个命令 pod search AFNetworking然后会从master路径下找到AFNetworking,然后列出来他有哪些版本什么的。我们有的时候会发现一个库其实已经跟新到2.x.x版本,但是我们search出来只有1.x.x,这也可能是我们的cocoapods没有更新,我们的master路径下没有新的版本。 这个PayComponents文件夹,就代表我们本地有一个私有的pod库。我们search的时候,也会查这些本地的私有库。 下面把远程的这个repo和我们本地创建的项目关联到一起,这个工作Casa给我们提供的脚本就可以完成,顺便还会帮我们生成.podspec的文件,具体这个文件的作用我们后面再说,还会初始化podfile。 我们进到ConfigPrivatePod文件中,执行config.sh脚本,然后在终端根据提示输入就行了。 [localhost:ConfigPrivatePod sunxiaobin$ ./config.sh Enter Project Name: PayComponentsEnter HTTPS Repo URL: https://gitee.com/LittleBin/PayComponents.gitEnter SSH Repo URL: git@gitee.com:LittleBin/PayComponents.gitEnter Home Page URL: https://gitee.com/LittleBin/PayComponents================================================ Project Name : PayComponents HTTPS Repo : https://gitee.com/LittleBin/PayComponents.git SSH Repo : git@gitee.com:LittleBin/PayComponents.git Home Page URL : https://gitee.com/LittleBin/PayComponents================================================confirm? (y/n):ycopy to ../PayComponents/FILE_LICENSEcopy to ../PayComponents/.gitignorecopy to ../PayComponents/PayComponents.podspeccopy to ../PayComponents/readme.mdcopy to ../PayComponents/upload.shcopy to ../PayComponents/Podfileediting...edit finishedcleaning...Initialized empty Git repository in /Users/fmb/Documents/LEARN/Project_test/PayComponents/.git/clean finishedfinishedlocalhost:ConfigPrivatePod sunxiaobin$ Enter Project Name:的时候,后面的名字一定要跟我们创建的PayComponents工程一样,要不然脚本找不到文件,就配置不了。 ...

April 28, 2019 · 2 min · jiezi

2019年iOS常问的基础面试题都会了吧

常问基础面试题:1、return一个类返回的属性,会不会被释放2、单例可不可以被销毁3、NSObject的结构体构造4、runloop有几个run方法,分别适用于什么场景5、runloop的生命周期6、NSObject的load方法是否了解7、Selcetor如何找到其要执行的方法8、什么情况下会造成死锁9、锁的类型10、多线程传值如何做11、多线程的生命周期12、如何让一个线程常驻13、对NSOpretion和GCD的理解14、atomic是绝对线程安全的么15、如何保证线程安全,有哪几种方式16、说说对autoreleasepool的理解以及应用17、定时器的使用方法有哪些,更加精准的定时器应该怎么做18、performselect在哪个线程执行19、oclint是否有用过,testflight自动化测试工具,自动打包工具是否用过20、对http的理解,对socket的理解,对tcp、udp的理解21、加密方式有哪些22、https为啥安全23、对mvvm的理解24、swizzling的理解25、数据结构的理解和常用算法的使用:如:链表反转,快速排序,二叉树遍历,二分查找,以及一些类似的简单算法26、swift如何使用runtime27、autoreleasepool嵌套后发生的一些执行顺序28、fmdb是同步还是异步数据库29、userdefault如何保证快速存取30、category实现原理31、对动画的使用,是否用过coreanimation32、oc与swift的差异化33、对设计模式的深入理解以及阐述推荐文集* iOS面试题大全(附答案)* BAT—最新iOS面试题总结

April 26, 2019 · 1 min · jiezi

深入理解Flutter多线程

该文章属于<简书 — 刘小壮>原创,转载请注明:<简书 — 刘小壮> https://www.jianshu.com/p/54da18ed1a9e Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。 事件队列和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。应用程序启动后,开始执行main函数并运行main isolate。 每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queue和microtask queue事件队列,event和microtask队列有点类似iOS的source0和source1。 event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件。microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。 这两个队列也是有优先级的,当isolate开始执行后,会先处理microtask的事件,当microtask队列中没有事件后,才会处理event队列中的事件,并按照这个顺序反复执行。但需要注意的是,当执行microtask事件时,会阻塞event队列的事件执行,这样就会导致渲染、手势响应等event事件响应延时。为了保证渲染和手势响应,应该尽量将耗时操作放在event队列中。 async、await在异步调用中有三个关键词,async、await、Future,其中async和await需要一起使用。在Dart中可以通过async和await进行异步操作,async表示开启一个异步操作,也可以返回一个Future结果。如果没有返回值,则默认返回一个返回值为null的Future。 async、await本质上就是Dart对异步操作的一个语法糖,可以减少异步调用的嵌套调用,并且由async修饰后返回一个Future,外界可以以链式调用的方式调用。这个语法是JS的ES7标准中推出的,Dart的设计和JS相同。 下面封装了一个网络请求的异步操作,并且将请求后的Response类型的Future返回给外界,外界可以通过await调用这个请求,并获取返回数据。从代码中可以看到,即便直接返回一个字符串,Dart也会对其进行包装并成为一个Future。 Future<Response> dataReqeust() async { String requestURL = 'https://jsonplaceholder.typicode.com/posts'; Client client = Client(); Future<Response> response = client.get(requestURL); return response;}Future<String> loadData() async { Response response = await dataReqeust(); return response.body;}在代码示例中,执行到loadData方法时,会同步进入方法内部进行执行,当执行到await时就会停止async内部的执行,从而继续执行外面的代码。当await有返回后,会继续从await的位置继续执行。所以await的操作,不会影响后面代码的执行。 下面是一个代码示例,通过async开启一个异步操作,通过await等待请求或其他操作的执行,并接收返回值。当数据发生改变时,调用setState方法并更新数据源,Flutter会更新对应的Widget节点视图。 class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); } loadData() async { String dataURL = "https://jsonplaceholder.typicode.com/posts"; http.Response response = await http.get(dataURL); setState(() { widgets = json.decode(response.body); }); }}FutureFuture就是延时操作的一个封装,可以将异步任务封装为Future对象。获取到Future对象后,最简单的方法就是用await修饰,并等待返回结果继续向下执行。正如上面async、await中讲到的,使用await修饰时需要配合async一起使用。 ...

April 26, 2019 · 2 min · jiezi

iOS开发-图片的解压缩到渲染过程

一.图像从文件到屏幕过程 通常计算机在显示是CPU与GPU协同合作完成一次渲染.接下来我们了解一下CPU/GPU等在这样一次渲染过程中,具体的分工是什么? CPU: 计算视图frame,图片解码,需要绘制纹理图片通过数据总线交给GPUGPU: 纹理混合,顶点变换与计算,像素点的填充计算,渲染到帧缓冲区。时钟信号:垂直同步信号V-Sync / 水平同步信号H-Sync。iOS设备双缓冲机制:显示系统通常会引入两个帧缓冲区,双缓冲机制图片显示到屏幕上是CPU与GPU的协作完成 对应应用来说,图片是最占用手机内存的资源,将一张图片从磁盘中加载出来,并最终显示到屏幕上,中间其实经过了一系列复杂的处理过程。 二.图片加载的工作流程假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩;然后将生成的 UIImage 赋值给 UIImageView ;接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;在主线程的下一个 runloop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤: 分配内存缓冲区用于管理文件 IO 和解压缩操作;将文件数据从磁盘读到内存中;将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;最后 Core Animation 中CALayer使用未压缩的位图数据渲染 UIImageView 的图层。CPU计算好图片的Frame,对图片解压之后.就会交给GPU来做图片渲染渲染流程 GPU获取获取图片的坐标将坐标交给顶点着色器(顶点计算)将图片光栅化(获取图片对应屏幕上的像素点)片元着色器计算(计算每个像素点的最终显示的颜色值)从帧缓存区中渲染到屏幕上我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。 三.为什么要解压缩图片既然图片的解压缩需要消耗大量的 CPU 时间,那么我们为什么还要对图片进行解压缩呢?是否可以不经过解压缩,而直接将图片显示到屏幕上呢?答案是否定的。要想弄明白这个问题,我们首先需要知道什么是位图 其实,位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点。我们在应用中经常用到的 JPEG 和 PNG 图片就是位图 大家可以尝试 UIImage *image = [UIImage imageNamed:@"text.png"];CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));打印rawData,这里就是图片的原始数据. 事实上,不管是 JPEG 还是 PNG 图片,都是一种压缩的位图图形格式。只不过 PNG 图片是无损压缩,并且支持 alpha 通道,而 JPEG 图片则是有损压缩,可以指定 0-100% 的压缩比。值得一提的是,在苹果的 SDK 中专门提供了两个函数用来生成 PNG 和 JPEG 图片: ...

April 25, 2019 · 2 min · jiezi

在原生-React-Native-应用中使用-Expo-API

本文翻译自 Expo 的一篇博客:You can now use Expo APIs in any React Native app 注: 本文最初于 2019 年 2 月 28 日发布,随后于 2019 年 3 月 14 日更新,以反映 Workflow 的改进。从今天开始,你可以在任何 React Native 应用程序中使用尽可能少或尽可能多的 Expo SDK。 我们已经花了很多时间构建和维护这些包含原生应用特性的跨平台 API,我们很高兴最终实现了向整个 React Native 生态共享这些 API,并将它们作为一个整体继续优化。 Expo 未来的两个主要的工程流分别是 Managed 和 Bare。 Managed 应用程序是通过 expo-cli、移动设备上的 Expo 客户端和我们的各种服务: Push Notifications、构建服务和 无线(OTA)更新 构建的。 Expo 试图尽可能多地为你管理构建应用程序的复杂性,所以我们称之为 Managed Workflow。 另一方面,Bare 应用程序将所有的控制权(以及随之而来的复杂性)交给了开发人员。 关于 Bare Workflow,可以参考:“Hello World” guide for bare projects in the Expo docs我们称这个初始版本为预览版,因为还它没有我们希望的那样足够简化,但是我们希望尽快把这些功能交到用户手中,因为它们已经是一个很大的改进了。 ...

April 24, 2019 · 2 min · jiezi

iOS开发-简历中需要特别注意的 3 个点

本次要说的内容,是关于简历的,网上其实流传了很多的简历模版,但是大部分都没有说这些模版到底好在哪,所以很多人就算拿过来用了,最后也是貌似神离。 今天我不讲形式,只说内容,简历中请一定要注意的 3 个问题: 项目描述请尽量简短;自我评价尽量不要用形容词;项目经验请使用倒序;下面分别做下详细说明。1、项目描述请尽量简短。我们先看一个例子: 项目描述:交易资金托管服务是指银行为货物、服务交易或存在资金托管需求的双方(或多方)提供信用中介,一方先将资金存入银行并暂时冻结,待另一方提供了双方约定的货物或服务、或满足了双方约定的其他事项,银行按照协议约定协助完成资金的划转;若双方未达成交易或未实现双方约定的其他事项,银行按协议约定退回交易资金。若交易双方需要银行移交权证,银行则根据协议约定协助完成相关权益证明的交换。如果你看到简历里面这段描述,第一印象是什么?给我的第一感觉就是,这个人对自己太不负责了,项目描述都不是自己的总结,一看就是拷贝粘贴而来。 其次就是这个人可能在问题优先级上的关注不够,比如这么长的项目描述,写出来是希望告诉面试官什么信息呢?说明你的项目很庞大?很出色?然后呢?和写这份简历的你有什么关系? 既然是我们自己的简历,我觉得所有地方的描述,肯定都是为了突出自己的能力、贡献、态度等等,任何不能体现自己这些长处的内容都需要精简,甚至删除。 看看这个项目描述改成这样如何: 项目描述:该系统是一个资金托管服务平台。系统共有十大模块,我带领三个小伙伴负责其中五个主要模块的功能和性能测试,项目发布后没有严重问题反馈。怎么样?看完了什么感觉?是不是一目了然?知道了项目大概功能的同时,也明白了候选人在项目中的角色。2、自我评价尽量不要用形容词。还是先看一个自我评价的例子: 本人对待工作认真,善于沟通,勤于学习能不断提升自身的能力和综合素质;性格开朗,可以快速融入团队,有较强的团队精神;对工作充满热情,适应能力强,创新务实;在工作中用心进取,态度认真,不怕吃苦。如果你看到这个评价,是什么感觉?给我的感觉就是空洞,或者说是说了一堆的废话,我甚至都不会逐字的看完。 形容词谁都会用,但问题是,怎么证明他的可信度呢?不能自己说自己牛掰,然后就真的牛掰了吧? 十全十美是人人都希望达到的境界,但这并不现实,所以与其让自己呈现的完美,倒不如让自己呈现的真实,真实就是在实际的做人做事中具体体现的行事方式。 比如把上面的例子我们改成下面的描述: 有一定的学习能力,曾经紧急接手一个新项目,项目使用的是 Swift 语言,但是我之前都是学习的 OC,所以我花了一个星期的时间进行突击,很快就掌握了 Swift 的基本语法的使用,可以看懂业务中的简单代码实现了;懂得作为测试的坚持,之前有个项目在上线前还有一个 P2 的 bug 没有修复,产品打算带着 bug 上线,可是我看了 bug 后发现,确认它的严重程度和优先级都属于 P2 级别的,于是找开发和产品沟通协商,最终决定还是修复完 bug 再上线。怎么样?是不是很吸引人,没有用xxx的形容词,但是看的人自己就会得出一个结论「这个人很靠谱」,看,这不就是我们要达到的效果吗?与其假惺惺的告诉别人你的各种优势,倒不如好好找个例子让别人看到你做事的态度。 3、项目经验请使用倒序简要罗列。这个就不放例子了,反例都太长了,大概说下情况。 如果参与过的项目非常多,完全没必要每一个都进行罗列,就算写了三页纸一点都改变不了自己分不清轻重的事实。 如果是按所在公司进行项目罗列的话,每个公司可以找一个代表性项目就可以了。 什么是代表性项目?不是项目规模大就好,而是自己的参与度和价值体现最大的项目,记住,自己才是这个简历的主人,项目都是为了衬托自己的。 如果不用公司的时间轴,完全按照项目时间轴罗列的话,同样只需要挑选代表性项目就可以了,哪怕全都是某一个公司的也没有关系,因为你在工作经历中已经提到公司的时间轴了。 如果面试官对其他公司的项目感兴趣,他会主动问起来,这时候再答复就可以,同时,面试官筛选简历时,不会因为项目罗列的不够全而淘汰掉简历。 或者这么说,面试官不会因为简历写的太差而淘汰谁,只会因为简历中没有看到自己需要的能力而被淘汰,所以只管突出的展示自己的能力即可,毕竟简历只是为了获得面试的机会而已,只要获得了面试机会,简历的使命就算完成了。 最后说下时间轴顺序,请使用倒序,请使用倒序,请使用倒序,重要的事要说三遍。 倒序就是把离当前时间点最近的项目放在前面。 一个人带着工作经验去面试,那么他的经验是他的底牌,只有尽早尽快的在经验的展现上吸引住面试官,才更有可能获得面试机会,而对于工作经验,大家当然是更关注最近的实践经验了,况且对于个人来说,肯定越是最近的项目,个人能力的体现也应该是最好的,不然怎么体现出自己的进步呢? 以上,稍微有点啰嗦了,关于简历不知道大家是否还有其他的疑惑或者问题,欢迎进入iOS技术交流群:624212887,一起探讨,更有企业内推机会! 另外,如果看了文章还是不知道简历怎么改的,推荐一个不错的简历指导视频,值得一看;观看地址:第二十七讲:简历指导视频

April 22, 2019 · 1 min · jiezi

深度链接对社会化营销有哪些价值和作用?

社交网络时代,微信、微博等社交软件被广泛应用于我们的生活,大部分的非社交软件都添加了社区分享功能,比如说爱奇艺的泡泡社区,互联网社交化的发展趋势,也影响着营销界,社会化营销也因此变成了营销推广中的一个重要的传播阵地,人人都是自媒体的时代,信息的传播通过人与人之间传播开来,由此可见,社会化营销已经成为各大品牌和产品中一个重要的营销战术,同时社会化营销的发展和产生的巨大收益,也得益于技术的发展,不仅为企业提供了全面的信息、全面的数据,帮助企业进行分析和决策,同时深度链接集成技术(MobLink)的发展,也在最大程度上优化营销活动中复杂的转化过程,实现一键跳转功能。虽然社会化营销中深度链接(MobLink)的身影很常见,但是很多人对深度链接还不是很理解,那么我们先来看看什么是深度链接技术(MobLink),app语境里的深度链接与web语境的深度链接有一定的相似性,在web中深度链接是指能够直接跳转到网站内页而非首页的链接,延伸到app语境中,深度链接技术就是解决web与app间跳转问题的技术,可以从web或者app直接进入到另一个app内部链接对应界面而非app首页的链接。深度链接对社会化营销有哪些价值和作用呢?我们就来说一下澎湃新闻在接入MobLink的一个场景应用案例:当用户在朋友圈或者其他途径,看到一条被分享的新闻之后,用户点击新闻内的打开按钮即可直接跳转至app内观看,如果出现用户的手机没有安装app的情况,用户在点击打开之后,页面会引导用户去下载app,用户点击下载按钮之后既可跳转至下载app页面,成功下载安装之后,用户首次启动即可查看原完整新闻,如下图所示:深度链接在帮助运营实现产品拉新、促活方面有着亮眼的表现,我们知道,在产品运营中,用户转化过程越简单最终转化率越高,产品运营中每多一个步骤就会损失一批用户,如果转化过程复杂,再好的营销活动也很难带来好的转化效果,深度链接将复杂的人工跳转程序一键化,实现用户在移动端与app之间、各个app之间的切换变得便捷流畅。深度链接(MobLink)技术一键跳转功能在微信、微博,甚至推特、Facebook等社会化营销中都是一项必不可少的的营销技术,微信、微博等社会化营销的流量都可以通过深度链接功能回到app内,同时包括短信营销、网页端、邮件营销等方式都可以一键唤起app,不仅能够起到拉新的作用,在很大程度上也提高了app内用户的活跃度以及留存。

April 18, 2019 · 1 min · jiezi

iOS常问面试题:三次握手与四次挥手

在面试中,三次握手和四次挥手可以说是问的最频繁的一个知识点了,我相信大家也都看过很多关于三次握手与四次挥手的文章,今天的这篇文章,重点是围绕着面试,我们应该掌握哪些比较重要的点,哪些是比较被面试官给问到的,我觉得如果你能把我下面列举的一些点都记住、理解,我想就差不多了。三次握手当面试官问你为什么需要有三次握手、三次握手的作用、讲讲三次三次握手的时候,我想很多人会这样回答:首先很多人会先讲下握手的过程:第一次握手: 客户端给服务器发送一个 SYN 报文。第二次握手: 服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。第三次握手: 客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。服务器收到 ACK 报文之后,三次握手建立完成。作用是为了确认双方的接收与发送能力是否正常。这里我顺便解释一下为啥只有三次握手才能确认双方的接受与发送能力是否正常,而两次却不可以:第一次握手: 客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。第二次握手: 服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。第三次握手: 客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。因此,需要三次握手才能确认双方的接收与发送能力是否正常。这样回答其实也是可以的,但我觉得,这个过程的我们应该要描述的更详细一点,因为三次握手的过程中,双方是由很多状态的改变的,而这些状态,也是面试官可能会问的点。所以我觉得在回答三次握手的时候,我们应该要描述的详细一点,而且描述的详细一点意味着可以扯久一点。加分的描述我觉得应该是这样:刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后第一次握手: 客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号ISN(c)。此时客户端处于 SYN_Send 状态。第二次握手: 服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。第三次握手: 客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方以建立起了链接。三次握手的作用三次握手的作用也是有好多的,多记住几个,保证不亏。例如:确认双方的接受能力、发送能力是否正常。指定自己的初始化序列号,为后面的可靠传送做准备。如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成到。单单这样还不足以应付三次握手,面试官可能还会问一些其他的问题,例如:1、(ISN)是固定的吗三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。2、什么是半连接队列服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。这里在补充一点关于SYN-ACK 重传次数的问题:服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超 过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s, 2s, 4s, 8s, ….3、三次握手过程中可以携带数据吗很多人可能会认为三次握手都不能携带数据,其实第三次握手的时候,是可以携带数据的。也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。也就是说,第一次握手可以放数据的话,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据页没啥毛病。关于三次握手的,https 的认证过程能知道一下最好,不过我就不说了,留着写 http 面试相关时的文章再说。四次挥手四次挥手也一样,千万不要对方一个 FIN 报文,我方一个 ACK 报文,再我方一个 FIN 报文,我方一个 ACK 报文。然后结束,最好是说的详细一点,例如想下面这样就差不多了,要把每个阶段的状态记好,我上次面试就被问了几个了,呵呵。我答错了,还以为自己答对了,当时还解释的头头是道,呵呵。刚开始双方都处于 establised 状态,假如是客户端先发起关闭请求,则:第一次挥手: 客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于CLOSED_WAIT1状态。第二次握手: 服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于CLOSE_WAIT2状态。第三次挥手: 如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。第四次挥手: 客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。这里特别需要主要的就是TIME_WAIT这个状态了,这个是面试的高频考点,就是要理解,为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭。这其中的原因就是,要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。至于 TIME_WAIT 持续的时间至少是一个报文的来回时间。一般会设置一个计时,如果过了这个计时没有再次收到 FIN 报文,则代表对方成功就是 ACK 报文,此时处于 CLOSED 状态。这里我给出每个状态所包含的含义,有兴趣的可以看看。LISTEN - 侦听来自远方TCP端口的连接请求;SYN-SENT -在发送连接请求后等待匹配的连接请求;SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;ESTABLISHED - 代表一个打开的连接,数据可以传送给用户;FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;FIN-WAIT-2 - 从远程TCP等待连接中断请求;CLOSE-WAIT - 等待从本地用户发来的连接中断请求;CLOSING -等待远程TCP对连接中断的确认;LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认;CLOSED - 没有任何连接状态;最后,在放在三次握手与四次挥手的图另外附上一份收集的各大厂面试题(附答案) ! 要的可加iOS高级技术群:624212887,群文件直接获取 ...

April 16, 2019 · 1 min · jiezi

手把手教你在Flutter项目优雅的使用ORM数据库--下篇

A orm database Flutter plugin.之前发了一篇文章《手把手教你在Flutter项目优雅的使用ORM数据库》,很多人咨询使用也提了一些宝贵的意见,说不希望要写lua,这样不够优雅,也增加了学习成本。细想了一下,确实是,对flutter项目开发来讲,最好就是纯flutter版的orm框架,于是我就写了一个flutter版的 orm插件flutter_orm_plugin ,使用的demo我放到github上了,大家可以下载来玩玩。下面我介绍一下flutter_orm_plugin提供的所有api。添加orm表flutter_orm_plugin中一个orm对应一个表,例如demo中Student表,它所在的db名字叫School,表名叫Student,它包含如下四个字段:studentId 在数据库中是Integer类型,主键,自增的。name 在数据库中是Text类型class 在数据库中是Text类型,是外键,关联的表是另一个表Class表score 在数据库中是Real类型创建这样的表的代码在demo的main.dart中 Map<String , Field> fields = new Map<String , Field>(); fields[“studentId”] = Field(FieldType.Integer, primaryKey: true , autoIncrement: true); fields[“name”] = Field(FieldType.Text); fields[“class”] = Field(FieldType.Text, foreignKey: true, to: “School_Class”); fields[“score”] = Field(FieldType.Real); FlutterOrmPlugin.createTable(“School”,“Student”,fields);数据库中某一列的数据通过Field类定义,我们先看看Field的定义就可以知道我们的orm对象支持哪些属性了class Field { final FieldType type;//类型包括Integer、Real、Blob、Char、Text、Boolean bool unique;//是否惟一 int maxLength; bool primaryKey;//是否主键 bool foreignKey;//是否外键 bool autoIncrement;//是否自增 String to;//关联外键表,以DBName_TableName命名 bool index;//是否有索引}插入数据单条插入 Map m = {“name”:“william”, “class”:“class1”, “score”:96.5}; FlutterOrmPlugin.saveOrm(“Student”, m);批量插入 List orms = new List(); for(int i = 0 ; i < 100 ; i++) { Map m = {“name”:name, “class”:className, “score”:score}; orms.add(m); } FlutterOrmPlugin.batchSaveOrms(“Student”, orms); 查询数据全部查询 Query(“Student”).all().then((List l) { }); 查询第一条 Query(“Student”).first().then((Map m) { }); 根据主键查询 Query(“Student”).primaryKey([1,3,5]).all().then((List l) { }); where条件查询 Query(“Student”).whereByColumFilters([WhereCondiction(“score”, WhereCondictionType.EQ_OR_MORE_THEN, 90)]).all().then((List l) { }); where sql 语句查询 Query(“Student”).whereBySql(“class in (?,?) and score > ?”, [“class1”,“class2”,90]).all().then((List l) { }); where 查询并排序 Query(“Student”).orderBy([“score desc”,]).all().then((List l) { }); 查询指定列 Query(“Student”).needColums([“studentId”,“name”]).all().then((List l) { }); group by 、having 查询 Query(“Student”).needColums([“class”]).groupBy([“class”]).havingByBindings(“avg(score) > ?”, [40]).orderBy([“avg(score)”]).all().then((List l) { }); 更新数据全部更新 Query(“Student”).update({“name”:“test all update”}); 根据主键更新 Query(“Student”).primaryKey([11]).update({“name”:“test update by primary key”}); 根据特定条件更新 Query(“Student”).whereByColumFilters([WhereCondiction(“studentId”, WhereCondictionType.LESS_THEN, 5),WhereCondiction(“score”, WhereCondictionType.EQ_OR_MORE_THEN, 0)]).update({“score”:100}); 根据自定义where sql更新 Query(“Student”).whereBySql(“studentId <= ? and score <= ?”, [5,100]).update({“score”:0}); 删除数据全部删除 Query(“Student”).delete(); 根据主键删除 Query(“Student”).primaryKey([1,3,5]).delete(); 根据条件删除 Query(“Student”).whereByColumFilters([WhereCondiction(“studentId”, WhereCondictionType.IN, [1,3,5])]).delete();根据自定义where sql删除 Query(“Student”).whereBySql(“studentId in (?,?,?)”, [1,3,5]).delete();联表查询inner join JoinCondiction c = new JoinCondiction(“Match”); c.type = JoinType.INNER; c.matchColumns = {“studentId”: “winnerId”}; Query(“Student”).join(c).all().then((List l) { }); left join JoinCondiction c = new JoinCondiction(“Match”); c.type = JoinType.LEFT; c.matchColumns = {“studentId”: “winnerId”}; Query(“Student”).join(c).all().then((List l) { }); 利用外键联表 JoinCondiction c = new JoinCondiction(“Class”); c.type = JoinType.INNER; Query(“Student”).join(c).all().then((List l) { }); where sql 联表 JoinCondiction c = new JoinCondiction(“Match”); c.type = JoinType.INNER; c.matchColumns = {“studentId”: “winnerId”}; Query(“Student”).join(c).whereBySql(“Student.score > ?”,[60]).all().then((List l) { }); 部分column 联表查询 JoinCondiction c = new JoinCondiction(“Match”); c.type = JoinType.INNER; c.matchColumns = {“studentId”: “winnerId”}; Query(“Student”).join(c).needColums([“name”,“score”]).all().then((List l) { }); group by 、having 联表查询 JoinCondiction c = new JoinCondiction(“Class”); c.type = JoinType.INNER; Query(“Student”).join(c).needColums([“class”]).groupBy([“Student.class”]).havingByBindings(“avg(Student.score) > ?”, [40]).all().then((List l) { }); order by 联表查询 JoinCondiction c = new JoinCondiction(“Class”); c.type = JoinType.INNER; Query(“Student”).join(c).orderBy([“Student.score desc”]).all().then((List l) { }); 使用介绍flutter_orm_plugin 已经发布到flutter 插件仓库。只要简单配置即可使用,在yaml文件中加上flutter_orm_plugin依赖以及orm框架所需要的lua源文件,flutter_orm_plugin会对所有lua代码进行封装,最终使用只需要关心dart接口,对lua是无感的。 flutter_orm_plugin: ^1.0.0 . . . assets: - packages/flutter_orm_plugin/lua/DB.lua - packages/flutter_orm_plugin/lua/orm/model.lua - packages/flutter_orm_plugin/lua/orm/cache.lua - packages/flutter_orm_plugin/lua/orm/dbData.lua - packages/flutter_orm_plugin/lua/orm/tools/fields.lua - packages/flutter_orm_plugin/lua/orm/tools/func.lua - packages/flutter_orm_plugin/lua/orm/class/fields.lua - packages/flutter_orm_plugin/lua/orm/class/global.lua - packages/flutter_orm_plugin/lua/orm/class/property.lua - packages/flutter_orm_plugin/lua/orm/class/query.lua - packages/flutter_orm_plugin/lua/orm/class/query_list.lua - packages/flutter_orm_plugin/lua/orm/class/select.lua - packages/flutter_orm_plugin/lua/orm/class/table.lua - packages/flutter_orm_plugin/lua/orm/class/type.lua 在ios项目podfile加上luakit 依赖source ‘https://github.com/williamwen1986/LuakitPod.git'source ‘https://github.com/williamwen1986/curl.git'...pod ‘curl’, ‘> 1.0.0’pod ‘LuakitPod’, ‘> 1.0.17’在android项目app的build.gradle加上luakit依赖repositories { maven { url “https://jitpack.io” }}…implementation ‘com.github.williamwen1986:LuakitJitpack:1.0.9’完成配置即可使用。 ...

April 15, 2019 · 2 min · jiezi

APICloud |UIChatTools 模块demo

UIChatTools 模块是一个聊天输入框模块,开发者可自定义该输入框的功能。通过 open 接口可在当前 window 底部打开一个输入框,该输入框的生命属于当前 window 所有。当输入框获取焦点后,会自动弹动到软键盘之上。开发者可通过监听输入框距离底部弹动的高度,来改变聊天对话界面的高度,从而实现类似 QQ 聊天页面的功能。UIChatTools 模块是 UIChatBox 模块的优升级。模块文档地址:https://docs.apicloud.com/Cli…此demo覆盖模块所有接口,供大家参考。需要注意的地方,open接口-styles-mask参数若设置,则会弹出遮罩层,点击遮罩层会收起键盘。addFace(添加表情包)接口,需先下载了一个表情包,然后解压(使用到了zip模块)到指定目录。使用fs模块的,可验证文件是否存在。Android添加表情包后,需再次点击表情按钮生效。此输入框模块只是提供了一个界面,和界面上按钮的点击回调事件,具体功能的实现,还需要配合其他api实现。比如,录音功能,还需要结合其他录音模块,或api对象的录音接口去实现。选择图片功能,UIChatTools模块已自带。图标、颜色可根据UI设计进行替换调整,模块提供参数可以进行设置。下载widget代码包:https://community.apicloud.co…

April 15, 2019 · 1 min · jiezi

iOS面试题:反射是什么?可以举出几个应用场景么?

系统Foundation框架为我们提供了一些方法反射的API,我们可以通过这些API执行将字符串转为SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。// SEL和字符串转换FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);// Class和字符串转换FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);// Protocol和字符串转换FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);通过这些方法,我们可以在运行时选择创建那个实例,并动态选择调用哪个方法。这些操作甚至可以由服务器传回来的参数来控制,我们可以将服务器传回来的类名和方法名,实例为我们的对象。// 假设从服务器获取JSON串,通过这个JSON串获取需要创建的类为ViewController,并且调用这个类的getDataList方法。Class class = NSClassFromString(@“ViewController”);ViewController *vc = [[class alloc] init];SEL selector = NSSelectorFromString(@“getDataList”);[vc performSelector:selector];反射机制使用技巧假设有一天公司产品要实现一个需求:根据后台推送过来的数据,进行动态页面跳转,跳转到页面后根据返回到数据执行对应的操作。遇到这样奇葩的需求,我们当然可以问产品都有哪些情况执行哪些方法,然后写一大堆if else判断或switch判断。但是这种方法实现起来太low了,而且不够灵活,假设后续版本需求变了,还要往其他已有页面中跳转,这不就傻眼了吗….这种情况反射机制就派上用场了,我们可以用反射机制动态的创建类并执行方法。当然也可以通过runtime来实现这个功能,但是我们当前需求反射机制已经足够满足需求了,如果遇到更加复杂的需求可以考虑用runtime来实现。这时候就需要和后台配合了,我们首先需要和后台商量好返回的数据结构,以及数据格式、类型等,返回后我们按照和后台约定的格式,根据后台返回的信息,直接进行反射和调用即可。假设和后台约定格式如下:@{ // 类名 @“className” : @“UserListViewController”, // 数据参数 @“propertys” : @{ @“name”: @“liuxiaozhuang”, @“age”: @3 }, // 调用方法名 @“method” : @“refreshUserInformation” };定义一个UserListViewController类,这个类用于测试,在实际使用中可能会有多个这样的控制器类。#import <UIKit>// 由于使用的KVC赋值,如果不想把这两个属性暴露出来,把这两个属性写在.m文件也可以@interface UserListViewController : UIViewController@property (nonatomic,strong) NSString name;/!< 用户名 /@property (nonatomic,strong) NSNumber age;/!< 用户年龄 // 使用反射机制反射为SEL后,调用的方法 */- (void)refreshUserInformation;@end下面通过反射机制简单实现了控制器跳转的方法,在实际使用中再根据业务需求进行修改即可。因为这篇文章主要是讲反射机制,所以没有使用runtime代码。简单封装的页面跳转方法,只是做演示,代码都是没问题的,使用时可以根据业务需求进行修改。- (void)remoteNotificationDictionary:(NSDictionary *)dict { // 根据字典字段反射出我们想要的类,并初始化控制器 Class class = NSClassFromString(dict[@“className”]); UIViewController *vc = [[class alloc] init]; // 获取参数列表,使用枚举的方式,对控制器属性进行KVC赋值 NSDictionary *parameter = dict[@“propertys”]; [parameter enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { // 在属性赋值时,做容错处理,防止因为后台数据导致的异常 if ([vc respondsToSelector:NSSelectorFromString(key)]) { [vc setValue:obj forKey:key]; } }]; [self.navigationController pushViewController:vc animated:YES]; // 从字典中获取方法名,并调用对应的方法 SEL selector = NSSelectorFromString(dict[@“method”]); [vc performSelector:selector];}更多:iOS面试题大全(附答案) ...

April 15, 2019 · 1 min · jiezi

2019 DDCTF write up

MISC北京地铁Color Threshold提示:AES ECB密钥为小写字母提示2:密钥不足位用0补全提示3:不要光记得隐写不看图片本身啊…MulTzor原文为英语,请破解12ce98ec4c4d79e38bf9454a71fecafa5a196ce58fb5795771ea87f41c5a71fd82f04e5076eacae6454a6ce887b5595779ef86f058196ce58fb54b5c6bf98fe7521959e186fc594a38e484b56b566ae18eb56b586aada3dc1c4d77ad98f05d5d38fe9ff74f4d79e39efc5d5538ec87fa49576cfecafa5a1955e298e659147be28ef058196aec8efc53197be287f8495771ee8be1555676fecafa5a196ce58fb57d4171fecae5534e7dff99b5485179f9cafd5d5d38ef8ff052197de389fc4c517dff8ff11c4c6be484f21c7c76e48df85d1975ec89fd55577dfec4b5685171fecaec555c74e98ff11c5471e183e15d4b61ad83fb485c74e183f259577be8cae254507be5c6b55d5577e38db54b506ce5cae154586cad8ce7535438e29efd594b38e98ff64e4068f98ff11c7860e499b54e587ce485b55d577cad9ef0505c68ff83fb485c6aad9ee75d576be083e64f5077e399b91c4e79fecaf2554f7de3cae1545c38ee85f1595779e08fb569556cff8bbb1c6d70e499b54b586bad89fa524a71e98fe7595d38ef93b54b5c6bf98fe752194bf89ae759547dadabf950507de9cad6535475ec84f1594b38c99dfc5b516cadaebb1c7c71fe8ffb54566fe898b5485638e58be359197ae88ffb1c1b7ce889fc4f506ee8c8b5485638f982f01c7874e183f058196ee489e1534b61a3e09f68517dadaffb555e75eccaf85d5a70e484f04f196fe898f01c5838eb8bf8555561ad85f31c4977ff9ef45e557dad89fc4c517dffcaf85d5a70e484f04f196fe49efd1c4b77f985e71c4a7bff8bf85e557dff99bb1c7e77e28eb553497dff8be155577fad9ae7535a7de99fe7594a34ad9ae753497dff86ec1c5c76eb85e75f5c7ca1cae2534c74e9cafd5d4f7dad87f4585c38f982f01c4974f88df753586ae9cad052507fe08bb551587be583fb59196de388e7595873ec88f9591738c585e2594f7dffc6b551566bf9cafa5a196ce58fb57b5c6ae08bfb1c5471e183e15d4b61ad8cfa4e5a7dfec6b54f5c7bff8fe11c4a7dff9cfc5f5c6bad8bfb58197be49cfc505079e3caf45b5c76ee83f04f196ce58be11c4c6be88eb5795771ea87f41c5c75fd86fa455c7cad9afa534b38e29af04e586ce484f21c496ae289f0584c6ae899b91c5876e9cafc48196fec99b548517dfe8fb54c5677ffcae54e567be88ee04e5c6bad9efd5d4d38ec86f9534e7de9cae1545c38c884fc5b5479ad87f45f5171e38fe61c4d77ad88f01c4b7dfb8fe74f5c35e884f255577de898f0581979e38eb548517dad89fc4c517dff99b5485638ef8fb54e5c79e9c49f366d70e8cad2594b75ec84b54c556dea88fa5d4b7ca08fe4495068fd8ff11c7c76e48df85d197ae889f4515c38c38bef55195fe898f85d5761aa99b54c4b71e389fc4c5874ad89e745496ce2c7e6454a6ce887bb1c706cad9df44f197aff85fe595738ef93b548517dadbafa50506be5cad259577dff8bf91c6a6cec8cf31b4a38ce83e5545c6aada8e04e5c79f8cafc52195ce889f0515b7dffcaa4050a2aa1cae2554d70ad9efd591979e48eb5535f38cb98f0525a70a099e04c4974e48ff11c5076f98ff950507fe884f6591975ec9ef04e5079e1cafa5e4d79e484f058197eff85f81c5838ca8fe7515876ad99e5451738cccaf853576ce5caf7595f77ff8fb548517dad85e0485b6ae88bfe1c567eadbdfa4e557cadbdf44e1951c4c6b55d4d38eccaf653577ee898f0525a7dad82f0505d38e38ff44e194fec98e65d4e34ad9efd591948e286fc4f5138ce83e5545c6aada8e04e5c79f8cae654586ae88eb5554d6badaffb555e75ecc7f74e5c79e683fb5b196ce889fd525069f88fe61c5876e9cae1595a70e385f9535e61ad9dfc485138f982f01c7f6ae884f6541979e38eb57e4b71f983e6541738c99fe755577fad9efd59195fe898f85d5738e484e35d4a71e284b5535f38dd85f95d577ca1caf6534b7dadbafa50506be5cad6554970e898b57e4c6ae88be01c497dff99fa52577de1cae2594b7dad8fe35d5a6dec9ef0581538fb83f41c6b77e08bfb555834ad9efa1c7f6aec84f659196fe58fe759196ce58fec1c5c6bf98bf750506be58ff11c4d70e8cac57f195aff9ffb53196be48dfb5d556bad83fb485c74e183f259577be8cae648586ce485fb1c4e71f982b57a4b7de389fd1c5f79ee83f9554d71e899b54f4c68fd85e7481738de9ff65f5c6bfe8ce050197be285e5594b79f983fa521979e085fb5b196ce58fb56c5674e899b91c4d70e8cad34e5c76ee82b91c5876e9cae1545c38cf98fc48506be5caf448195ae18fe15f5174e893b56c586ae6caf653576ce484e0595d38f884e1555538c79ffb591929b4dea510196fe58ffb1c7f6aec84f659196bf898e759577ce898f058196ce2cae1545c38ca8fe7515876fec49f367f6ae287b5485171fecaf7595e71e384fc525e34ad9efd59195aff83e1554a70adadfa4a5c6ae387f0524d38ce85f1591979e38eb57f4068e58fe71c6a7be585fa501930caa9b37f6a31ad8be11c7b74e89ef654557df4cac55d4b73ad88e055556cad9fe51c5876ad8fed485c76fe83e359197bff93e5485876ec86ec48507bad89f44c587ae486fc484036ada3fb554d71ec86f9451538f982f01c5d7dee98ec4c4d71e284b54b586bad87f4555774f4cafa5a1954f88ce14b587eeb8fb5147e7dff87f4521979e498b55a566aee8fbc1c5876e9caf41c5f7dfacadd595c6aadc2d2594b75ec84b55d4b75f4c3b5515c6bfe8bf2594a34ad8be61c4d70e8cade4e507dea99f85d4b71e38fb5147e7dff87f4521976ec9cec15197de09af953407de9caf8495a70ad87fa4e5c38fe8ff6494b7dad9ae7535a7de99fe7594a38eb85e71c4c6be484f21c7c76e48df85d1738cc86f452194cf898fc525e34ad8bb57f5875ef98fc585e7dadbffb554f7dff99fc484038e08be1545c75ec9efc5f5079e3caf4525d38e185f2555a71ec84b91c496ae29cfc585c7cad87e05f5138e28cb548517dad85e7555e71e38bf91c4d70e484fe55577fad9efd5d4d38e18ff11c4d77ad9efd59197ce899fc5b5738e28cb548517dad89e745496cec84f450406ce489f450197ae287f7591975ec89fd55577dfecae154586cad9df04e5c38e484e6484b6de08ffb485874ad83fb1c5c6ee884e1495874e193b55e4b7dec81fc525e38f982f01c5779fb8bf91c7c76e48df85d1738c585e2594f7dffc6b548517dada1e7555c7ffe87f44e5076e8cafc524d6ae28ee05f5c7cad8bfb1c7c76e48df85d196ee898e6555676ad9dfc485138eccaf3534c6af982b54e566ce298b55a566aad83e14f194da088fa5d4d6ba1cae7594a6de19efc525e38e484b55d1968ff85f953577fe88eb54c5c6ae485f11c4e70e884b548517dfe8fb5515c6bfe8bf2594a38ee85e0505d38e385e11c5b7dad8ef05f4b61fd9ef0581738da83e154196ce58fb55f5868f99fe7591977ebcae759557dfb8bfb48197be49afd594b38e68fec4f1979e38eb548517dad9fe6591977ebcaf8495a70ad8cf44f4d7dffcac06f1956ec9cec1c5b77e088f04f1538ff8ff2495579ffc6b54e5868e48eb54e5c79e983fb5b1977ebcac0115b77ec9eb5515c6bfe8bf2594a38ff8fe649547de9c49f366d70e8caf350587fad83e606195cc9a9c17a427ab88ca1055c2abcdaa30a0b2bbddbf45f097ebb8ca65d0f2dbfdca40c0f7ebc97[PWN] strikeWireshark简单的流量分析联盟决策大会为了共同的利益,【组织1】和【组织2】成立了联盟,并遵守共同约定的协议。为了让协议的制定和修改更加公平,组织1和组织2共同决定:当三位以上【组织1】成员和三位以上【组织2】成员同意时,才可以制定或修改协议。为了实现这一功能,联盟的印章被锁在密码保险柜中,而保险柜的密码只通过Shamir秘密分享方案分享给【组织1】和【组织2】的每一位成员。现在,【组织1】的【成员1】、【成员2】、【成员4】,【组织2】的【成员3】、【成员4】、【成员5】一致同意制定新的协议。请还原出这套方案的设计思路,按照这套方案的思路恢复出保险柜密码,取出印章吧!p =C45467BBF4C87D781F903249243DF8EE868EBF7B090203D2AB0EDA8EA48719ECE9B914F9F5D0795C23BF627E3ED40FBDE968251984513ACC2B627B4A483A6533组织1成员1 =729FB38DB9E561487DCE6BC4FB18F4C7E1797E6B052AFAAF56B5C189D847EAFC4F29B4EB86F6E678E0EDB1777357A0A33D24D3301FC9956FFBEA5EA6B6A3D50E组织1成员2 =478B973CC7111CD31547FC1BD1B2AAD19522420979200EBA772DECC1E2CFFCAE34771C49B5821E9C0DDED7C24879484234C8BE8A0B607D8F7AF0AAAC7C7F19C6组织1成员4 =BFCFBAD74A23B3CC14AF1736C790A7BC11CD08141FB805BCD9227A6E9109A83924ADEEDBC343464D42663AB5087AE26444A1E42B688A8ADCD7CF2BA7F75CD89D组织2成员3 =9D3D3DBDDA2445D0FE8C6DFBB84C2C30947029E912D7FB183C425C645A85041419B89E25DD8492826BD709A0A494BE36CEF44ADE376317E7A0C70633E3091A61组织2成员4 =79F9F4454E84F32535AA25B8988C77283E4ECF72795014286707982E57E46004B946E42FB4BE9D22697393FC7A6C33A27CE0D8BFC990A494C12934D61D8A2BA8组织2成员5 =2A074DA35B3111F1B593F869093E5D5548CCBB8C0ADA0EBBA936733A21C513ECF36B83B7119A6F5BEC6F472444A3CE2368E5A6EBF96603B3CD10EAE858150510Reversewindows reverse1windows reverse2Confusedconfused, need your flag.obfuscating macros提交格式:DDCTF{最短的正确输入},使得程序输出WELL DONE!黑盒破解II-时间谜题黑盒破解II-时间谜题去年大家已经破解掉了黑盒子系统,现在飞船的发动机引擎已经可以启动了,但常规的飞行速度是不能保证我们安全逃离的,速度与时间很重要!该如何做呢?挑战一下吧。你可以输入一串精心构造的数据来触发超常规的引擎启动获得逃离,但别被他骗了,不过无论怎样,当你看到Flag时就已经成功了。注:请运行在>= windows 7 x64 系统之上,程序正确输出格式:Flag:DDCTF{….}

April 15, 2019 · 1 min · jiezi

使用React Native构建App

原文地址:使用React Native构建App最近因为项目需要,深入研究React和React Native,React已经掌握得差不多了,现在集中精力在ReactNative的项目开发。这里需要记录在学习过程中的技术细节,好记性真的不如烂笔头,多写文档总会有好处的。[坑太多,一个个填]本文重点记录使用React Native构建双平台App的过程,同时进一步掌握构建过程中运用的技术。【持续更新,坚持不懈…】搭建开发环境安装react-native-cli:npm i -g react-native-cliAndroid SDK安装Android SDK并启动进行配置:配置环境变量export ANDROID_HOME=~/Library/Android/sdkexport PATH=${PATH}:${ANDROID_HOME}/toolsexport PATH=${PATH}:${ANDROID_HOME}/platform-toolsAndroid 虚拟机设定Genymotion的Android SDK 位置(Android SDK 的路径可以在 SDK Manager 上找到)。模拟器有多款模拟器可供选择,Android Studio自带,Genymotion和夜神模拟器,推荐选择夜神模拟器。配置方法:在Nox/bin目录运行nox_adb.exe connect 127.0.0.1:62001,如果失败,使用adb devices查询,出现版本不一致的情况,可以把Android/sdk目录下的adb.exe拷贝到Nox/bin下,并改名为nox_adb.exe,反过来操作也是可以的。分别打开Android Studio和夜神模拟器,此时运行命令nox_adb.exe connect 127.0.0.1:62001基本上都会成功。新建React Native项目运行react-native init project-name,进入project-name文件夹安装依赖npm i并运行react-native run-android或react-native run-ios构建App。以Android App为例,在Android Studio打开Android文件夹(注意:此处是Android文件夹,不是project-name项目文件夹)。运行项目这一步很关键,配置java的环境变量,首先是JAVA_HOME和ANDROID_HOME:JAVA_HOME,变量值为D:\Android\sdk;ANDROID_HOME,变量值为D:\Android\sdk;然后在Path项中添加jdk和jre下的bin目录;以上是用户变量配置,下面进行系统变量配置:在Path项中添加下图中变量:同时打开Android Studio、Nox并在AS中打开项目中的Android文件夹。运行nox_adb.exe connect 127.0.0.1:62001连接AS和Nox,然后再运行react-native run-android,此时就会构建Android App,

April 13, 2019 · 1 min · jiezi

让UINavigationController更好用

去年看到过美团点评技术团队的一篇文章iOS系统中导航栏的转场解决方案与最佳实践,文章对系统导航栏的改造很有意思,最近就试着写点代码练练手。项目地址:DoubleNavigationController这个库还没有在实际项目中检验过,还有很多不完善或者不能满足业务需求的地方,欢迎提issue或者PR。些许疑问为什么要开发这个库?UINavigationController在苹果官方文档上的是这样介绍的:A navigation controller is a container view controller that manages one or more child view controllers in a navigation interface.也就是说UINavigationController 是作为UIViewController的管理者,因此它的NavigationBar不应该从属于任何一个ViewController。但是大部分UI设计者都没有明白苹果设计的用意,因此在业务中经常出现一个场景:在逻辑上应该从属于同一个NavigationController的多个ViewController却拥有不同的NavigationBar样式。不同页面的开发者只能看到对自己开发的便利性,对UINavigationController的理解不到位,到处修改NavigationBar样式,在页面专场过程中NavigationBar出现了各种不可控的问题。这个问题在App支持路由后会变得更为突出,原因是各个页面的跳转关系将会非常复杂且不可预知。DoubleNavigationController解决的便是这个问题,让开发者自由地修改NavigationBar样式,并且不用担心在退回到栈下ViewController后NavigationBar的样式也被修改。简单来说就是,我们修改NavigationBar不再会影响栈内现有的页面样式,而只影响之后Push的新页面。为什么不设计成既不影响栈内现有的页面样式,也不影响新页面?在这里先讲一下DoubleNavigationController的两个设计思想 “先到先得”、“谁用谁修改”。“先到先得”先出现的页面样式不应该受到后出现的页面影响。用户在使用过程中先看到了页面A的样式,接着从A页面跳转到B页面,B页面的导航栏样式与A页面不同,这时用户再返回A页面,从正常逻辑上来说,用户希望看到的A页面导航栏应该还是之前见过的样式,不应该受到B的影响而改变。“谁用谁修改”继续上面一个场景,用户从页面A到页面B,再从页面B跳转到页面C,在上一个场景下我们知道,页面B修改了导航栏样式,使其与页面A不同,当我们跳转到页面C时,此时存在如下两种可能:页面C也对刚才B页面修改过的导航栏样式属性进行了修改。页面C没有对刚才B页面修改过的导航栏样式属性进行修改。在第1种情况下我们很容易确定,C页面的导航栏样式就应该是C页面自己修改的样式。那么在第2种情况下,C页面导航栏应该长什么样?再考虑以下3种方案:跟A页面一样?跟UIAppearance配置一样?跟B页面一样?C页面导航栏跟A页面一样?这个方案在逻辑上就是错误的,因为C页面根本不应该关心它的上上一个页面样式。如下图,假设B页面有两条跳转路径A1和A2,此时C页面的样式就有2种可能,相信绝大多数App的设计都不会出现 “1个页面,2种UI” 的情况吧。C页面导航栏跟UIAppearance配置一样?让C页面保持和UIAppearance配置一致,这里也存在两个问题,一个问题是如果用户没有配置UIAppearance怎么办?还有一个更大的问题是,这么做似乎破坏了苹果对于UINavigationController的定义,这就使得导航栏在逻辑上成为了单个页面所独立持有的个体,在这种情况下倒不如隐藏系统NavigationBar,每个页面是实现一个自己的导航栏来个更方便维护。C页面导航栏跟B页面一样?保持和B页面一样,粗略一想,这和“跟A页面一样?”方案似乎是差不多的,但实际上这两种方案有着本质区别。“跟B页面一样”换一个更好的说法应该是“跟最近一次用户对导航栏修改之后的样式一样”,也就是说C页面只需要关注导航栏本身,而不需要关注谁修改了导航栏,这样一来就满足来上述的设计思想 “谁用谁修改”。实现关于这个库的实现,笔者在这里参考了美团点评的这篇文章iOS系统中导航栏的转场解决方案与最佳实践。在转场的过程中隐藏原有的导航栏并添加假的 NavigationBar,当转场结束后删除假的 NavigationBar 并恢复原有的导航栏,这一过程可以通过 Swizzle 的方式完成,而每个 ViewController 只需要关心自身的样式即可。DoubleNavigationController核心的解决方案与这篇文章提到的是一样的,但是在实现方式和细节上可能与文章中提到的并不一样,另外有一些实现细节在美团点评的这篇文章中并没有过多地透露。几个细节细节1:DoubleNavigationController中选择直接NSKeyedArchiver来复制一个FakeNavigationBar而并没有自定义UIView。细节2:有些时候一个页面的NavigationBar可能会在用户交互过程中动态变化,因此我们需要记录每一次用户对NavigationBar外观的修改,并在适当的时候对FakeNavigationBar外观也进行更新。细节3:由于UIAppearance的原理是在UIView被添加到视图树后才会去改变对象的外观,因此在使用FakeNavigationBar之前需要再一次和当前的navigationBar进行一次UIAppearance属性的复制。参考:iOS UIAppearance 探秘 — HyanCat’s例子clone这个仓库,进到Example目录下执行pod install来运行一个demo。用法通过在ViewController中实现dbn_configNavigationController这个方法来定制导航栏样式。- (void)dbn_configNavigationController:(UINavigationController *)navigationController { [navigationController setNavigationBarHidden:NO animated:NO]; navigationController.navigationBar.barTintColor = [UIColor whiteColor]; navigationController.navigationBar.tintColor = [UIColor purpleColor]; navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName: [UIFont systemFontOfSize:20], NSForegroundColorAttributeName: [UIColor redColor]};}- (void)dbn_configNavigationItem:(UINavigationItem *)navigationItem { UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithTitle:@“Next” style:UIBarButtonItemStylePlain target:self action:@selector(eventFromButton:)]; navigationItem.rightBarButtonItem = btnItem; navigationItem.title = @“Hello”;}你还可以使用dbn_performBatchUpdates:这个方法来随时更新导航栏样式。[self dbn_performBatchUpdates:^(UINavigationController * _Nullable navigationController) { if (navigationController) { navigationController.navigationBar.tintColor = [UIColor purpleColor]; }}];参考iOS系统中导航栏的转场解决方案与最佳实践UIKit UIAppearance - APPLEiOS UIAppearance 探秘 — HyanCat’s ...

April 12, 2019 · 1 min · jiezi

iOS面试题:什么是离屏渲染?什么情况下会触发?该如何应对?

更多:iOS面试题大全离屏渲染就是在当前屏幕缓冲区以外,新开辟一个缓冲区进行操作。离屏渲染出发的场景有以下:圆角 (maskToBounds并用才会触发)图层蒙版阴影光栅化为什么要有离屏渲染?大家高中物理应该学过显示器是如何显示图像的:需要显示的图像经过CRT电子枪以极快的速度一行一行的扫描,扫描出来就呈现了一帧画面,随后电子枪又会回到初始位置循环扫描,形成了我们看到的图片或视频。为了让显示器的显示跟视频控制器同步,当电子枪新扫描一行的时候,准备扫描的时发送一个水平同步信号(HSync信号),显示器的刷新频率就是HSync信号产生的频率。然后CPU计算好frame等属性,将计算好的内容交给GPU去渲染,GPU渲染好之后就会放入帧缓冲区。然后视频控制器会按照HSync信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器,就显示出来了。具体的大家自行查找资料或询问相关专业人士,这里只参考网上资料做一个简单的描述。离屏渲染的代价很高,想要进行离屏渲染,首选要创建一个新的缓冲区,屏幕渲染会有一个上下文环境的一个概念,离屏渲染的整个过程需要切换上下文环境,先从当前屏幕切换到离屏,等结束后,又要将上下文环境切换回来。这也是为什么会消耗性能的原因了。由于垂直同步的机制,如果在一个 HSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。[](https://github.com/liberalism…?CPU GPU 在绘制渲染视图时做了大量的工作。离屏渲染发生在 GPU 层面上,会创建新的渲染缓冲区,会触发 OpenGL 的多通道渲染管线,图形上下文的切换会造成额外的开销,增加 GPU 工作量。如果 CPU GPU 累计耗时 16.67 毫秒还没有完成,就会造成卡顿掉帧。圆角属性、蒙层遮罩 都会触发离屏渲染。指定了以上属性,标记了它在新的图形上下文中,在未愈合之前,不可以用于显示的时候就出发了离屏渲染。在OpenGL中,GPU有2种渲染方式On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作离屏渲染消耗性能的原因需要创建新的缓冲区离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕哪些操作会触发离屏渲染?光栅化,layer.shouldRasterize = YES遮罩,layer.mask圆角,同时设置 layer.masksToBounds = YES、layer.cornerRadius大于0考虑通过 CoreGraphics 绘制裁剪圆角,或者叫美工提供圆角图片阴影,layer.shadowXXX,如果设置了 layer.shadowPath 就不会产生离屏渲染

April 12, 2019 · 1 min · jiezi

iOS不使用微信sdk,直接打开小程序

直接贴代码iOS审核不让有支付代码,所以只使用轻度功能的话,可以不使用微信SDK。使用前需要先去微信开放平台绑定。我的封装/** * 开发前需要到微信开放平台把App绑定小程序,然后在小程序的管理员微信上点击同意绑定,就可以转跳了 * 字段解释: * @appid:小程序appid * @username:‘gh’开头的小程序公用id * @path:小程序需要打开页面的路径 * @type:0是正式版,1是开发版,2是体验版 **/-(void)jumpToWechatMiniProgram:(NSString *)appid ghId:(NSString *)username path:(NSString *)path type:(NSString *)miniProgramtype{ NSString *mPath = [path stringByReplacingOccurrencesOfString:@"/" withString:@"%2F"]; NSString *url = [NSString stringWithFormat:@“weixin://app/%@/jumpWxa/?userName=%@&path=%@&miniProgramType=%@&extMsg=",appid,username,mPath,miniProgramtype]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]options:@{} completionHandler:^(BOOL success) { NSLog(@“跳转成功”); }];}调用-(IBAction)jumpWithUrl:(id)sender{ [self jumpToWechatMiniProgram:@“wx8888888888888” ghId:@“gh_88888888888” path:@“pages/index/index?session=自己定的参数” type:@“2”];}获取微信sdk的其他功能iOS中,app互相转跳走的都是openUrl这个接口,通过scheme就可以转跳到目标程序,但是scheme是不审核的,可以随意指定,所以我们可以通过写一个假微信(scheme是weixin),来拦截微信SDK的启动请求,从而获取到对应的启动字符串,然后自己拼接字符串即可。伪造微信在info.plist里添加(注意缩进不要弄错了,最好在模拟器上试,如果安装了微信,是不会跳到我们的假微信里的。):<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>weixin</string> </array> <key>CFBundleURLName</key> <string>1111</string> </dict> </array>看不到源码页面的话,右键info.plist,选择Open As -> Source Code就能看到了,改完了切回Property List模式,不报错就说明格式是对的。获取转跳参数在appDelegate.m里增加:// 这方法显示已经废弃了,但是只是获取参数还是可以的- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{ //显示截取的urlscheme UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@“接收到的urlScheme” message:url.absoluteString delegate:nil cancelButtonTitle:nil otherButtonTitles:@“确定”, nil]; [alert show];// 复制到剪贴板 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = url.absoluteString; return YES;}然后就能看到弹窗里的urlscheme就可以了,只要拼接出一个一样的urlscheme,就可以启用微信SDK同样的功能。 ...

April 12, 2019 · 1 min · jiezi

一个int变量被__block修饰与否的区别?

更多:iOS面试题大全没有修饰,被block捕获,是值拷贝。使用__block修饰,会生成一个结构体,复制int的引用地址。达到修改数据。1、block截获自动变量(局部变量)值对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。2、 __block 修饰的外部变量对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。3、Block的存储域及copy操作先来思考一下:Block是存储在栈上还是堆上呢?其实,block有三种类型:全局块(_NSConcreteGlobalBlock)栈块(_NSConcreteStackBlock)堆块(_NSConcreteMallocBlock)全局块存在于全局内存中, 相当于单例.栈块存在于栈内存中, 超出其作用域则马上被销毁堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。遇到一个Block,我们怎么这个Block的存储位置呢?(1)Block不访问外界变量(包括栈中和堆中的变量)Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。(2)Block访问外界变量MRC 环境下:访问外界变量的 Block 默认存储栈中。ARC 环境下:访问外界变量的 Block 默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。4、防止 Block 循环引用Block 循环引用的情况:某个类将 block 作为自己的属性变量,然后该类在 block 的方法体里面又使用了该类本身,如下:self.someBlock = ^(Type var){ [self dosomething];};解决办法:(1)ARC 下:使用 __weak__weak typeof(self) weakSelf = self;self.someBlock = ^(Type var){ [weakSelf dosomething];};(2)MRC 下:使用 __block__block typeof(self) blockSelf = self;self.someBlock = ^(Type var){ [blockSelf dosomething];};值得注意的是,在ARC下,使用 __block 也有可能带来的循环引用,如下:// 循环引用 self -> _attributBlock -> tmp -> selftypedef void (^Block)();@interface TestObj : NSObject{ Block _attributBlock;}@end@implementation TestObj- (id)init { self = [super init]; __block id tmp = self; self.attributBlock = ^{ NSLog(@“Self = %@",tmp); tmp = nil; };}- (void)execBlock { self.attributBlock();}@end// 使用类id obj = [[TestObj alloc] init];[obj execBlock]; // 如果不调用此方法,tmp 永远不会置 nil,内存泄露会一直在5、有时候我们经常也会被问到block为什么 常使用copy关键字?block 使用 copy 是从 MRC遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作” ...

April 11, 2019 · 1 min · jiezi

iOS开发常用设计模式

1 代理模式应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。优势:解耦合敏捷原则:开放-封闭原则实例:tableview的 数据源delegate,通过和protocol的配合,完成委托诉求。列表row个数delegate自定义的delegate2 观察者模式应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。优势:解耦合敏捷原则:接口隔离原则,开放-封闭原则实例:Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。kvo,键值对改变通知的观察者,平时基本没用过。3 MVC模式应用场景:是一中非常古老的设计模式,通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。优势:使系统,层次清晰,职责分明,易于维护敏捷原则:对扩展开放-对修改封闭实例:model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。4 单例模式应用场景:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。优势:使用简单,延时求值,易于跨模块敏捷原则:单一职责原则实例:[UIApplication sharedApplication]。注意事项:确保使用者只能通过 getInstance方法才能获得,单例类的唯一实例。java,C++中使其没有公有构造函数,私有化并覆盖其构造函数。object c中,重写allocWithZone方法,保证即使用户用 alloc方法直接创建单例类的实例,返回的也只是此单例类的唯一静态变量。5 策略模式应用场景:定义算法族,封装起来,使他们之间可以相互替换。优势:使算法的变化独立于使用算法的用户敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。注意事项:1.剥离类中易于变化的行为,通过组合的方式嵌入抽象基类2.变化的行为抽象基类为,所有可变变化的父类3.用户类的最终实例,通过注入行为实例的方式,设定易变行为防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。6 工厂模式应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。敏捷原则:DIP依赖倒置原则实例:项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,增加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。更多:iOS面试题大全

April 10, 2019 · 1 min · jiezi

fastclick在移动端使用input=“file”上传文件时label中的内容不能触发change事件的bug

当我的代码如下的时候<label for=“file” style=“display: block; width: 100%;"> <img style=“width:1.20rem;height:1.20rem;border-radius:1.00rem;” :src=“user.avatar ? user.avatar : defultPicUrl” /> <div class=“font32 color33” style=“margin-top:.30rem;">点击编辑头像</div></label>我发现我点击其他地方可以调用系统的相机以及相册,但是点击头像和文字的时候需要双击才能触发,后来排除原因主要是因为项目中引入了fastclick造成的,后来在issue中找到了答案:就是加入css属性:pointer-events:none;这个属性要给label内部的标签加,可以解决这个问题,代码如下<label for=“file” style=“display: block; width: 100%;"> <img style=“width:1.20rem;height:1.20rem;border-radius:1.00rem;pointer-events:none;” :src=“user.avatar ? user.avatar : defultPicUrl” /> <div class=“font32 color33” style=“margin-top:.30rem;pointer-events:none;">点击编辑头像</div></label>这样就解决了这个问题在此插入关于pointer-events的文档

April 10, 2019 · 1 min · jiezi

iOS 检测是否安装某个应用

请支持原文:http://tryenough.com/ios-installcheck步骤一:iOS9后设置白名单在iOS9中,需要在"Info.plist"中设置所涉及到的 URL scheme 到白名单,设置方法如下:打开Info.plist,可以选择以代码方式打开:添加如下例子内容:<key>LSApplicationQueriesSchemes</key><array> <string>twitter</string> <string>fb</string> <string>…这里写到的都是app的scheme名子…</string></array> 常见的app的sheme名字如下:请支持原文:http://tryenough.com/ios-installcheck<key>LSApplicationQueriesSchemes</key><array> <!– 微信 URL Scheme 白名单–> <string>wechat</string> <string>weixin</string> <!– 新浪微博 URL Scheme 白名单–> <string>sinaweibohd</string> <string>sinaweibo</string> <string>sinaweibosso</string> <string>weibosdk</string> <string>weibosdk2.5</string> <!– QQ、Qzone URL Scheme 白名单–> <string>mqqapi</string> <string>mqq</string> <string>mqqOpensdkSSoLogin</string> <string>mqqconnect</string> <string>mqqopensdkdataline</string> <string>mqqopensdkgrouptribeshare</string> <string>mqqopensdkfriend</string> <string>mqqopensdkapi</string> <string>mqqopensdkapiV2</string> <string>mqqopensdkapiV3</string> <string>mqzoneopensdk</string> <string>wtloginmqq</string> <string>wtloginmqq2</string> <string>mqqwpa</string> <string>mqzone</string> <string>mqzonev2</string> <string>mqzoneshare</string> <string>wtloginqzone</string> <string>mqzonewx</string> <string>mqzoneopensdkapiV2</string> <string>mqzoneopensdkapi19</string> <string>mqzoneopensdkapi</string> <string>mqzoneopensdk</string> <!– 支付宝 URL Scheme 白名单–> <string>alipay</string> <string>alipayshare</string></array>步骤二:使用代码检查是否安装了应用例如检查是否安装了twitter:if(![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@“twitter://”]]) { NSLog(@“UnInstall twitter”); return;}请支持原文:http://tryenough.com/ios-installcheck

April 9, 2019 · 1 min · jiezi

iOS 打开AppStore指定app下载页

请支持原文 http://tryenough.com/ios-appstore步骤一,找到AppStore上的appid在pc端浏览器打开网址:https://www.apple.com/itunes/点击搜索你想找的应用,例如facebook:在连接中即可找到id。步骤二:代码中设置跳转到此应用请支持原文 http://tryenough.com/ios-appstore直接跳转 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@“itms-apps://itunes.apple.com/app/id284882215”]];请支持原文 http://tryenough.com/ios-appstore

April 9, 2019 · 1 min · jiezi

2 RAC解析 自定义KVO

知识点概述1.KVO实现原理2.runtime使用目的给NSObject添加一个Category,用于给实例对象添加观察者,当该实例对象的某个属性发生变化的时候通知观察者。大体思路添加观察者的方法中- (void)SQ_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;会用runtime的方式手动创建一个其子类,并且将该对象变为该子类。该子类会复写观察方法中keyPath的setter方法,使这个setter被调用时利用runtime去调用observer的回调方法-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> )change context:(void )context;实现这里只做KVO的基本功能,当被观察者改变属性的时候通知观察者,所以定义如下方法NSObject+SQKVO.h/ 添加观察者 @param observer 观察者 @param keyPath 被观察的属性名 */- (void)SQ_addObserver:(NSObject )observer forKeyPath:(NSString )keyPath;/ 当被观察的观察属性改变的时候的回调函数 @param keyPath 所观察被观察者的属性名 @param object 被观察者 @param value 被观察的属性的新值 */- (void)SQ_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object changeValue:(id)value;@end因为这里要用到runtime所以需要添加runtime的头文件#import <objc/message.h>而且因为用到objc_msgSend所以要改变一下工程的环境变量一.动态生成子类在被观察者调用- SQ_addObserver:forKeyPath:时首先动态生成一个其子类。 // 1.生成子类 // 1.1获取名称 Class selfClass = [self class]; NSString *className = NSStringFromClass(selfClass); NSString *KVOClassName = [className stringByAppendingString:@"_SQKVO"]; const char *KVOClassNameChar = [KVOClassName UTF8String]; // 1.2创建子类 Class KVOClass = objc_allocateClassPair(selfClass, KVOClassNameChar, 0); // 1.3注册 objc_registerClassPair(KVOClass);这里可以看到,我们将子类的类名命名为“类名”+“SQKVO”,譬如类名为“Person”,这个子类是“Person_SQKVO”。这里有个注意点,一般为动态创建的类名应尽量复杂一些避免重复。最好加上“”。二.根据KeyPath动态添加对应的setter1 确定setter的名字举个例子,如果用户给的keyPath是name,应该动态添加一个-setName:的方法。而这个setter的名字是 “set” + “把keyPath变为首字母大写” + “:“所以可以得出NSString *setterString = [NSString stringWithFormat:@“set%@:”, [keyPath capitalizedString]]; SEL setter = NSSelectorFromString(setterString);2 利用class_addMethod()给子类动态添加方法BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);cls: 给哪个类添加方法。即新生成的子类,上面生成的 KVOClass。name:所添加方法的名称。即上一步生成的字符串 setterString。imp:所添加方法的实现。即这个方法的C语言实现,首先在下面先写一个C语言的方法。稍后会讲具体实现。void setValue(id self, SEL _cmd, id newVale) {}types:所添加方法的编码类型。setter的返回值是void,参数是一个对象(id)。void用"v"表示,返回值和参数之间用“@:”隔开,对象用”@“表示。最后我们可以得出结果"v@:@"。具体其他的编码类型可以参考苹果文档。ps: 这里说下为什么返回值和参数之间用“@:”隔开。“:”代表字符串,所有的OC方法都有两个隐藏参数在参数列表的最前面,“发起者”和 “方法描述符”,“@”就是这个发起者,“:”是方法描述符。而这个types其实是imp返回值和参数的编码。因为OC方法中返回值和参数之间必然有“发起者”和“SEL”隔着,所以“@:”自然而然就成了返回值和参数之间的分隔符。当然我们还可以用@encode来得到我们想要的编码类型NSString *encodeString =[NSString stringWithFormat:@"%s%s%s%s”,@encode(void), @encode(id), @encode(SEL), @encode(id)];3 将当前对象的类变为我们所创建的子类的类型,即更改isa指针object_setClass(self, KVOClass);4 将keyPath和观察者关联(associate)到我们的对象上用下面这个函数可以很方便的将一个对象用键值对的方式绑定到一个目标对象上。*如果想了解跟多可以查找《Effective Objective-C》的第10条void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);object目标对象key绑定对象的键,相当于NSDictionary的key这里的key一般采用下面的方式声明:static const void *SQKVOObserverKey = &SQKVOObserverKey;static const void *SQKVOKeyPathKey = &SQKVOKeyPathKey;这样做是因为若想令两个键匹配到同一个值,则两者必须是完全相同的指针才行。value绑定对象,相当于NSDictionary的valuepolicy绑定对象的缓存策略@property (nonatomic, weak) :OBJC_ASSOCIATION_ASSIGN@property (nonatomic, strong) :OBJC_ASSOCIATION_RETAIN_NONATOMIC@property (nonatomic, copy) :OBJC_ASSOCIATION_COPY_NONATOMIC@property (atomic, strong) :OBJC_ASSOCIATION_RETAIN@property (atomic, weak) :OBJC_ASSOCIATION_COPY最后关联的代码:objc_setAssociatedObject(self, SQKVOObserverKey, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);objc_setAssociatedObject(self, SQKVOKeyPathKey, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);三.setValue()的实现这个函数的目的主要是:1.利用objc_msgSend触发原先类的setter2.利用objc_msgSend触发观察者的回调方法1. 触发原先的setter方法 // 保存子类 Class KVOClass = [self class]; // 变回原先的类型,去触发setter object_setClass(self, class_getSuperclass(KVOClass)); NSString *keyPath = objc_getAssociatedObject(self, SQKVOKeyPathKey); NSString *setterString = [NSString stringWithFormat:@“set%@:”, [keyPath capitalizedString]]; SEL setter = NSSelectorFromString(setterString); objc_msgSend(self, setter, newVale);2. 调用观察者的回调方法id observer = objc_getAssociatedObject(self, SQKVOObserverKey); objc_msgSend(observer, @selector(SQ_observeValueForKeyPath:ofObject:changeValue:), keyPath, self, newVale);3.改回KVO类object_setClass(self, KVOClass);四.实现空的回调方法- (void)SQ_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object changeValue:(id)value { }五.调用自定义的KVO恭喜你看到这里,并且恭喜你已经成功了!- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.name = @“A”; [self SQ_addObserver:self forKeyPath:@“name”]; self.name = @“B”;}- (void)SQ_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object changeValue:(id)value { NSLog(@”%@.%@=%@", object, keyPath, value);}六.代码代码下载地址 ...

April 8, 2019 · 2 min · jiezi

Fultter混合App实战

Flutter越来越火了。而且没有兼容性问题,超过React-Native指日可待。最近学习了Flutter,并写了一个Demo,分享给需要案例的小伙伴们,直接fork代码可用哦。已完成静态页面屏幕适配状态管理EvnetBus事件处理页面路由以下是Demo截图项目地址: https://github.com/zhongmeizhi/fultter-example-app撸完代码记得给颗星哦。My Github持续更新中,Github信息更多哦,你的⭐是我最大的支持。查看详情,

April 8, 2019 · 1 min · jiezi

iOS:Block __block修饰符

__block修饰符上一篇文章中说过,auto类型的局部变量,可以被block捕获,但是不能修改值。__block可以解决block内部无法修改外部auto变量的问题。__block int age = 10;void (^myblock)(void) = ^{ NSLog(@"%d",age);};age = 20;myblock();用法就是这么简单,这样我们修改age为20的时候,打印也是20。我们看看编译后的代码。struct __Block_byref_age_0 { void *__isa;__Block_byref_age_0 __forwarding; int __flags; int __size; int age;};//Blockstruct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; __Block_byref_age_0 *age; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 desc, __Block_byref_age_0 _age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};在block内部多了一个指向__Block_byref_age_0类型结构体的age指针。上面我也帖上了这个__Block_byref_age_0结构体的结构。我们发现int类型的age在这个结构体内部了。那也就是说,__block修饰的变量,编译器会把它包装成一个对象,然后我们的这个成员变量放到了这个对象的内部。我们观察一下这个__Block_byref_age_0内部,这些变量可能有疑惑的也就是这个__forwarding。他是一个指向这个结构体自身的指针。而且我们还可以看出来在打印age的时候,是也是通过__forwarding调用的age(age->__forwarding->ag),具体为什么要多加这个字段,我们后面再说。static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (copy)(struct __main_block_impl_0, struct __main_block_impl_0); void (dispose)(struct __main_block_impl_0);}__main_block_desc_0这个结构体中也多了两个指针,这是与内存管理有关的函数。底层分析差不多了,那我们还没说到为什么__block修饰的属性,在block内部可以修改,我们看下面的代码 attribute((blocks(byref))) __Block_byref_age_0 age = {(void)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};void (myblock)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));(age.__forwarding->age) = 20;我们创建了__Block_byref_age_0类型的age对象,同时把外部age的值也就是10,传递了进去。然后初始化了block。关键是下面的修改age的值的时候,直接就是修改的age对象里面的age属性了,然后打印的时候,也是打印的他。这个地方其实还是挺抽象的了,也不是很好理解。怎么前面定义的age变量跟后面修改的就不是一个了?__block int age = 10;NSLog(@"%p",&age);void (^myblock)(void) = ^{ NSLog(@"%d",age);};NSLog(@"%p",&age);age = 20;myblock();这是最简单的方法,打印出两个age的地址,就是不一样的。那我们怎么去判断就是__Block_byref_age_0里面的age呢,大家可以参考下面的做法。struct __Block_byref_age_0 { void *__isa; struct __Block_byref_age_0 *__forwarding; int __flags; int __size; int age;};struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(void); void (*dispose)(void);};struct __block_impl { void *isa; int Flags; int Reserved; void FuncPtr;};struct __main_block_impl_0 { struct __block_impl impl;// 8+4+4+8 struct __main_block_desc_0 Desc;//8 struct __Block_byref_age_0 *age;// 8+8+4+4};int main(int argc, const char * argv[]) { @autoreleasepool { _block int age = 10; void (^block)(void) = ^{ age = 20; NSLog(@“age is %d”, age); }; struct main_block_impl_0 *blockImpl = (bridge struct main_block_impl_0 *)block; NSLog(@"%p", &age); } return 0;}上面的操作就是我们把底层的一些结构拿出来,然后把我们的block类型桥接成__main_block_impl_0类型。然后我们通过debug可以拿到这个blockImpl的地址,然后通过内存中的地址偏移计算出来内部__Block_byref_age_0中的age地址,看看和打印出来的age地址是否一致。我们拿到blockImpl的地址是0x1005002d0(我测试时候的值,每次都不同)。main_block_impl_0内部第一个属性是一个__block_impl结构体,然后__block_impl内部两个指针(一个指针8字节)两个int(一个int4字节),共占24字节。第二个参数是一个指针,8字节。age在第三个参数指向的结构体中,也就是说__Block_byref_age_0类型的age内存地址是0x1005002d0偏移32,也就是0x100500300。然后__Block_byref_age_0内部的age变量前面,有两个指针两个int,24字节,0x100500300再偏移24,也就是0x100500318。跟我们NSLog(@"%p", &age);打印的一致。所以可以得出,我们修改的这个age,其实就是底层age对象内部的age变量。上面我们留下了一个__forwarding指针的疑问,我们先不着急解决,先说说block类型。block的类型block有isa指针,开始我想通过写了几个类型的block,用clang编译看cpp代码,但发现一直是这个样子impl.isa = &NSConcreteStackBlock;。所以我就用最直接的打印[obj class]的方法。注意,因为在ARC的环境下,编译器给我们做了很多内存相关的工作,所以我在研究block类型的过程中切换到了MRC环境。我用过的例子就不写了,下面是一个小总结。一共有三种Block__NSGlobalBlock 内存位于数据区__NSStackBlock 栈区__NSMallocBlock 堆区具体什么样的block对应哪一种类型?NSGlobalBlock:没有访问auto变量__NSStackBlock:访问了auto变量__NSMallocBlock: __NSStackBlock__调用copy提示我们在声明一个block属性的时候,习惯用copy关键字,是为了把栈区的block拷贝到堆区,让我们来管理他的生命周期。ARC环境下会根据情况自动将栈上的block拷贝到堆上。ARC环境下也用copy是为了和MRC环境统一,也可以用strong。__block修饰对象类型当__block变量在栈上时,不会对指向的变量产生强引用。当__block变量copy到堆上时,会根据这个变量的修饰符是__strong,__weak,__unsafe_unretained做出相应的操作形成强引用(retain)或者弱引用。(ARC会retain,MRC不会)。__forwarding指针最后说一下上面的__forwarding指针问题。这个图可以很好地诠释这个问题了。我们的block在内存中可能位于栈上,可能在堆上。使用了这个指针之后,让我们在block位于不同内存位置的情况下,访问到相应准确位置的变量。 ...

April 7, 2019 · 1 min · jiezi

iOS:Block 循环引用问题

循环引用是一个比较常见的问题,之前面试的时候也会被问到,如何解决循环引用问题,其实大家都知道使用__block,__weak这些修饰符可以解决循环引用问题,那今天我们要讨论的就是他们是怎么样解决了循环引用问题的。__weak其实__weak是比较好理解的,它的作用就是在两方相互强引用的时候,把其中一个引用变为弱引用,打破这个循环引用的圈。我们通过代码看一下。MyPerson * person = [[MyPerson alloc] init];person.age = @“10”;__weak typeof(person) weakPerson = person;person.block = ^{ NSLog(@“age is %@”, weakPerson.age);}; MyPerson类里面有一个block,一个string类型的age,在执行block的时候,打印了age,如果不用weakPerson的话,就会产生循环引用,这种用法想必大家都很熟悉。那我们看一下编译后的cpp文件。struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; MyPerson *__weak weakPerson; __main_block_impl_0(void fp, struct __main_block_desc_0 desc, MyPerson __weak _weakPerson, int flags=0) : weakPerson(_weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};可以看到block内部捕获到的是MyPerson __weak weakPerson;,所以不会产生强引用,自然也就不会出现循环引用问题。__weak只在ARC环境下使用。__block最开始我以为__block消除循环引用的方式跟__weak是一样的。//这种用法ARC环境下是错的 MRC可以MyPerson * person = [[MyPerson alloc] init];person.age = @“10”; __block typeof(person) weakPerson = person;person.block = ^{ NSLog(@“age is %@”, weakPerson.age);};我们现在开发一直都是在ARC环境下,首先自己检讨一下,我一直都以为__block可以这么用,而且关键是这样用了确实编译器就没有了关于循环引用的警告了。但是我们如果重写一下MyPerson类的dealloc方法,让对象释放时打印点东西,你会发现如果使用__weak,在main函数结束时,person会调用dealloc释放,但是如果像上面一样用__block,person不会释放,还是存在循环引用。我强调了这种用法在ARC环境下不可以,但是在MRC环境下是可以的,因为MRC环境下block不会对__block修饰的属性强引用。下面是ARC环境正确的__block使用方式。如果就按照__weak的使用方法使用,在block内部把weakPerson置为nil,同时这个block必须要调用。MyPerson * person = [[MyPerson alloc] init];person.age = @“10”; __block typeof(person) weakPerson = person;person.block = ^{ NSLog(@“age is %@”, weakPerson.age); weakPerson = nil;};person.block();//必须有这个代码也可以这样写:__block MyPerson * person = [[MyPerson alloc] init];person.age = @“10”;person.block = ^{ NSLog(@“age is %@”, person.age); person = nil;}; person.block();两种写法都一样,必须手动置为nil,然后必须执行block。下面我们说一下原理。首先呢,我们上面已经说了,如果不手动置为nil的话,使用__block依然有循环引用,我们结合cpp的代码分析一下具体循环引用在什么地方。我们编译下面这种写法。MyPerson * person = [[MyPerson alloc] init];__block typeof(person) weakPerson = person;person.age = @“10”;person.block = ^{ NSLog(@“age is %@”, weakPerson.age);};我们来分析一下下面的代码struct __Block_byref_weakPerson_0 { void __isa;__Block_byref_weakPerson_0 __forwarding; int __flags; int __size; void (__Block_byref_id_object_copy)(void, void); void (__Block_byref_id_object_dispose)(void); typeof (person) weakPerson;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; __Block_byref_weakPerson_0 *weakPerson; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};其实这个__block属性的作用咱们之前已经说过了,就是在block内部把修饰的属性包装成一个对象,也就是这个__Block_byref_weakPerson_0。__Block_byref_weakPerson_0内部有我们的weakPerson属性,typeof (person) weakPerson,这里只是叫weakPerson,他的持有方式还是strong的。所以说我们可以分析出来,__block属性持有我们的person变量,person持有block,block内部持有这个__block属性,就像下面这个图示一样。我们通过置为nil解决它循环引用的方式,就是打断一条强引用。如下图__unsafe_unretainedARC环境下__unsafe_unretained与__weak使用方法相同。MRC环境下,与__block MRC环境下的使用一样。__unsafe_unretained和__weak对比:__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变 ...

April 7, 2019 · 1 min · jiezi

iOS:Block变量捕获

这篇博客我们从一个很常见的题目入手。int age = 10; void (^myblock)(void) = ^{ NSLog(@"%d",age);};age = 20;myblock();这个题目就涉及到了block内访问外部变量,block有个变量捕获机制,我们新建一个mac的命令行工程,把上面代码写进去,然后用clang把main.m文件编译为cpp的文件看一下。具体的block底层结构上一篇文章我们已经说过了,这里我们针对结构就不在赘述,直接说核心点。auto类型局部变量struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};可以看到,在block的内部,多了一个age变量。而且从初始化的函数来看,这个__main_block_impl_0中age的值是外面传进来赋给他的。我们看一下main函数中。 int age = 10;void (myblock)(void) = ((void ()())&__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA, age));age = 20;((void ()(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);可以看出来在这个block初始化的过程中,就把age的值,也就是10,传了进去,赋值给了block内部的age变量。那我再看一下打印的时候的age是什么情况。static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy NSLog((NSString )&__NSConstantStringImpl__var_folders_6l_80sfw0tn35bg4dxc51jlq_bw0000gn_T_main_3b5bf4_mi_0,age);}打印的这个age就是block自己内部的这个ege变量(值为10),所以我们在执行block之前,改变外面的age值为20,其实改变的不是内部要打印的这个age了,所以打印出来结果还是10。好的,我们定义的这个age就是一个普通的局部变量,其实就是auto类型的局部变量(C语言基础)。那我们下面改变一下这个变量的属性,尝试一下静态变量(static)和全局变量。static类型局部变量首先是静态变量。//定义变量时加static标识static int age = 10;运行结果是20。我们依然要看一下clang编译一下,看c++代码。struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; int *age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};我们来分析一下两次的异同,首先block都对变量进行了捕获,但不同的是static类型的变量是捕获了变量的指针,那我们应该就可以理解了,一般情况下这种基本数据类型如果是传指针,就意味要修改值的。我们从main函数中也可以看出来,在初始化block的时候传入了外部age的指针,static int age = 10;void (myblock)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));而且在打印的时候也是打印了这个指针指向的数据。static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_6l_80sfw0tn35bg4dxc51jlq_bw0000gn_T_main_d618bf_mi_0,(age));}所以我们在执行block之前更改了age的值,在执行block的时候打印的也是同一块内存的值,所以值改变了。最后我们在试一下全局变量。全局变量在定义age的时候,定义成一个全局变量。(拿到main方法外面)。int age = 10;struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};从编译后的代码可以看出,在底层其实也是生成了一个age=10的全局变量,然后在block内部并没有捕获这个变量。在不管是重新赋值,还是输出打印,都是操作的这个全局的age,所以这个值也是能被改变的。可能有人会问如果是static int age = 10;这个样子的全局变量呢?全局变量加不加static都是一样的。这里就不上代码细说了。小结局部变量:会被block捕获到内部auto类型的是值传递,内部不能修改值static是指针传递,可以修改值。全局变量:不会被捕获到block内部可以修改值上面我们使用的是基本数据类型,那对象是不是也一样呢?其实对象类型的也是一样的,但是有一个小点我们需要了解一下。看下面这个例子吧。NSMutableArray * arr = [NSMutableArray new]; void (^myblcok)(void) = ^{ [arr addObject:@“1”]; NSLog(@"%@",arr);}; myblcok();按照我们上面总结的这种auto类型的局部变量是要被捕获到内部的,但是应该不可以修改值。上面代码执行后打印数组,是有@“1”这个元素的,这里我们就要搞清楚,我们调用[arr addObject:@“1”];并没有修改arr的值,只是只用了这个指针。如果我们在block的回调中让arr=nil;,这算是改变arr的值,但是xcode就会报错的了。 ...

April 7, 2019 · 1 min · jiezi

1.RAC解析 - 自定义链式编程

目的模仿Masonry连续运用点语法的操作[self.view mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@10).offset(1); }];写出一个连加的操作 make.add(10).add(10);想看结果的请直接跳到“最终结果”分析一.定义SQMath类SQMath.h#import <Foundation/Foundation.h>@interface SQMath : NSObject- (NSInteger)result;- (void)add:(int)number;@endSQMath.m#import “SQMath.h”@interface SQMath ()@property (nonatomic, assign) NSInteger number;@end@implementation SQMath- (NSInteger)result { return self.number;}- (void)add:(int)number { self.number += number;}@end使用这个SQMath的add方法 SQMath *math = [[SQMath alloc] init]; [math add:10]; [math add:20]; NSLog(@"%ld", [math result]);二.将函数调用改为点语法如果要用点语法,需要让-add从一个方法变成一个add的属性。但是这样就没有办法传参了- (NSInteger)add;但是如果返回值是一个 NSInteger (^)(NSInteger) 类型的block就可以了。math.add返回的是这个block,这个block是需要一个NSInteger为参数(加数),返回值是NSInteger(结果)。SQMath.m- (NSInteger (^)(NSInteger count))add { __weak typeof(self) weakSelf=self; NSInteger (^addBlock)(NSInteger) = ^(NSInteger addCount){ __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.number += addCount; return strongSelf.number; }; return addBlock;}或者- (NSInteger (^)(NSInteger count))add { __weak typeof(self) weakSelf=self; return ^(NSInteger addCount) { __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.number += addCount; return strongSelf.number; };}使用这个SQMath的add方法 SQMath math = [[SQMath alloc] init]; NSLog(@"%ld", math.add(10)); NSLog(@"%ld", math.add(20)); NSLog(@"%ld", math.add(30));三.连续使用点语法只要将Block的返回值更改为self。这样每次add返回的则变成了SQMath的实例对象,这样就可以实现连续点语法的效果了。- (SQMath (^)(NSInteger count))add { __weak typeof(self) weakSelf=self; return ^(NSInteger addCount) { __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.number += addCount; return self; };}使用这个SQMath的add方法SQMath *math = [[SQMath alloc] init];NSLog(@"%ld", math.add(10).add(20).add(30).result) ;四.将这个改为NSNumber的CategoryNSNumber+SQMath.h#import <Foundation/Foundation.h>#import “SQMath.h”@interface NSNumber (Math)- (NSInteger)sq_add:(void(^)(SQMath *make))block;@endNSNumber+SQMath.m#import “NSNumber+SQMath.h”@implementation NSNumber (SQMath)- (NSInteger)sq_add:(void(^)(SQMath *))block { SQMath *math = [[SQMath alloc] init]; block(math); return math.result;}@endNSNumber+SQMath 使用 NSInteger result = [@10 sq_add:^(SQMath * make) { make.add(10).add(20); }]; NSLog(@"%ld", result);最终结果SQChainProgrammingps:链式编程什么时候用我还真不太清楚,但我知道面试的时候肯定有用 哈哈。 ...

April 7, 2019 · 1 min · jiezi

私有库(组件化)快速提交脚本

脚本地址: https://github.com/zedxpp/PPPrivatePodPushScript写这个脚本的原因私有库的修改, 提交等操作非常没有技术含量并且繁琐.修改私有库代码文件修改私有库.podspec文件的版本号提交所有修改的文件添加版本的tag并且push所有的提交.cd到私有库.podspec文件所在目录, 验证并push私有库.(push的时候会执行pod lib lint)操作, 所以这里省略了.后续可能还会涉及到修改主工程私有库的版本号, pod install, 运行主工程等操作.在以上的基础上, 编写了private-pod-push-script.sh脚本. 在终端输入几下命令, 便完成了以上2~5的所有操作.使用方式git clone 本仓库, 并cd进入本仓库目录在config.sh里面配置工作路径, 填写所有组件的主仓库地址, 组件项目附加的文件路径以及组件名.(配置好你的组件路径, config.sh文件可以放在任何地方)主仓库地址是http://host.com/iOS/Specs.git(这个是你所有组件存放.podspec文件的仓库地址)/Users/pengpeng/Desktop/GithubTest/PPTestComponent/PPTestComponent.podspec 和 /Users/pengpeng/Desktop/PPKit.podspec 是我的组件地址/Users/pengpeng/Desktop/是工作路径GithubTest/PPTestComponent/是附加地址(附加地址选填)PPTestComponent.podspec的PPTestComponent是组件名在终端输入chmod +x private-pod-push-script.sh, 给private-pod-push-script.sh文件增加可执行权限. (此步骤只需要执行一次).然后输入./private-pod-push-script.sh既可执行脚本.自动获取你配置的所有组件, 根据编号排序, 输入你想提交的组件编号.自动获取当前.podspec版本号和上一次git所提交的tag号, 输入你想设置的版本号, 再输入注释.选择你想push到的主仓库名称, 如果验证成功的情况下, 所有流程已完成.### 做好前期的配置后, 以后只需要cd到脚本所在的文件目录下, 执行第4步中的操作即可.脚本已完成的功能脚本里面我写了很多注释, 可以按需修改, 增减自己的需求进去.脚本可以放在任何文件夹, 可以配置多个组件, 多个组件的路径可以不一致, 1个脚本对应多个组件.根据config.sh配置文件的内容, 展示你所有配置的组件仓库.对配置的组件.podspec文件是否存在进行判断.根据输入的组件编号配置脚本运行环境.获取组件.podspec文件版本号, 并且根据你的输入的版本号修改.(自动获取组件最后一次提交的版本号, 方便你判断最新的版本号).输入注释, 自动提交所有修改的文件并且push.根据选择的本地specs文件夹进行组件推送.后续还会根据需求增加别的功能, 为了方便使用者更新脚本, 所以把配置文件和脚本拆分了. 只需要更新脚本, 再把最新的脚本和配置文件放到一起既可. 也可以把配置文件放到别处, 配置文件的路径在脚本中可以设置.

April 7, 2019 · 1 min · jiezi

IOS出现 code: 18446744073709550594错误问题

问题:IOS开发出现如下错误: NSError NSError domain: @“NSURLErrorDomain” - code: 18446744073709550594 解决:找到info.plist文件,然后添加如下信息OK!

April 6, 2019 · 1 min · jiezi

iOS:Block的本质

我们项目中经常使用block来进行回调传值,之前我对block的认识也就仅仅的停留在基础的层面,包括简单的使用和一些基本的避免循环引用的方法,这篇博客是我在对block进行了更深一层的学习之后的记录和总结,希望对大家有所帮助。Block的本质新建一个命令行项目,写一个简单的block如下面所示。void (^myBlock)(void) = ^{ NSLog(@“11”);}; myBlock();使用clang工具把main.m文件编译为cpp文件。main函数变成了下面这个样子int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; void (myBlock)(void) = ((void ()())&__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA)); ((void ()(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl )myBlock); } return 0;}里面有很多类型转换的代码,但是不难看出,我们的block被编译成了一个__main_block_impl_0类型的变量,我们可以搜索一下,这是一个结构体。struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};这个__main_block_impl_0结构体有两个属性,一个是__block_impl类型的结构体,一个是指向__main_block_desc_0结构体的指针。我们一个一个的看。//__block_impl 结构体struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};我们可以看到这个结构体有一个isa指针,同时在__main_block_impl_0这个结构体中是直接包含__block_impl结构体,从内存结构中其实这个isa指针就在__main_block_impl_0里面,也就是说block被编译成了一个拥有isa指针的结构体。了解过isa的应该知道,这算是oc对象的一个标志了,那也就是说block其实也是一个oc的对象。我们继续看__main_block_impl_0内部 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }这是一个初始化的方法,因为这是c++代码嘛,c语言的结构体应该是不可以这样写的。只要是给这个__block_impl类型的impl赋值,这里我们先主要看这个fp指针。从编译之后main函数中看出来,在初始化__main_block_impl_0的时候传进去的参数是一个__main_block_func_0。// __main_block_func_0static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_pr_dx33d329d7xxgk9kgjdw80000gp_T_main_85af40_mi_0);}可以看出来这是我们block回调执行的方法,也就是说impl.FuncPtr指向了block的回调。__main_block_impl_0还有一个Desc参数,我们可以在文件中查一下。static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};reserved是一个保留字段,Block_size就是记录我们这个block占内存的大小。本节小结Block本质就是一个OC对象,内部有isa指针。Block是封装了函数调用和函数调用的环境的OC对象。我们来个草图来梳理一下最简单的block的内部结构。 ...

April 4, 2019 · 1 min · jiezi

抖音分享和授权(iOS)

准备工作注册appkey 抖音开放平台集成sharesdk 下载地址Xcode配置:urlScheme为注册的appkey, 白名单:douyinsharesdk ,douyinopensdk业务代码初始化#import <ShareSDK/ShareSDK.h>[ShareSDK registPlatforms:^(SSDKRegister *platformsRegister) { //抖音 [platformsRegister setupDouyinByAppKey:@“app_key” appSecret:@“app_secret”];}];分享可以分享图片,相册图片,单个视频,多个视频分享图片// 通用参数设置—-图片分享可以使用相册地址、沙盒路径、网络图片地址NSString *imageURL = @“http://img.hb.aicdn.com/28a4962c297205e0868cdb45bb527e2bc5319f08f019-l7N1A3_fw658";NSMutableDictionary *parameters = [NSMutableDictionary dictionary];[parameters SSDKSetupShareParamsByText:nil images:@[imageURL] url:nil title:nil type:SSDKContentTypeImage]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:shareParams onStateChanged:^(SSDKResponseState state, NSDictionary *userData, SSDKContentEntity *contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@“成功!”); }else{ NSLog(@”%@",error); }}];分享视频// 通用参数设置—-视频分享可以使用相册地址、沙盒路径,不支持网络视频,如果使用网络视频请先下载放到沙盒目录下或相册里 NSString *videoPath = [[NSBundle mainBundle] pathForResource:@“cat” ofType:@“mp4”]; NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; [parameters SSDKSetupShareParamsByText:nil images:nil url:[NSURL URLWithString:videoPath] title:nil type:SSDKContentTypeVideo]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:parameters onStateChanged:^(SSDKResponseState state, NSDictionary *userData, SSDKContentEntity *contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@“成功!”); }else{ NSLog(@"%@",error); } }];分享多个视频 // 平台定制—-只能使用相册且使用相册标识localIdentifier __block NSMutableArray *assetLocalIds = [NSMutableArray array]; __weak typeof(self) weakSelf = self; [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ NSURL *url_1 = [[NSBundle mainBundle] URLForResource:@“cat” withExtension:@“mp4”]; NSURL *url_2 = [[NSBundle mainBundle] URLForResource:@“cat” withExtension:@“mp4”]; PHAssetChangeRequest *req_1 = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url_1]; NSString *localId_1 = req_1.placeholderForCreatedAsset.localIdentifier; [assetLocalIds addObject:localId_1]; PHAssetChangeRequest *req_2 = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url_2]; NSString *localId_2 = req_2.placeholderForCreatedAsset.localIdentifier; [assetLocalIds addObject:localId_2]; } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { dispatch_async(dispatch_get_main_queue(), ^{ NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; [parameters SSDKSetupDouyinParamesByAssetLocalIds:assetLocalIds type:SSDKContentTypeVideo]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:parameters onStateChanged:^(SSDKResponseState state, NSDictionary *userData, SSDKContentEntity *contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@“成功!”); }else{ NSLog(@"%@",error); } }]; }); } }];授权[ShareSDK authorize:SSDKPlatformTypeDouyin settings:nil onStateChanged:^(SSDKResponseState state, SSDKUser *user, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@"%@",[user.credential rawData]); NSLog(@"%@",user.rawData); } else { NSLog(@"%@",error); } }]; ...

April 4, 2019 · 1 min · jiezi

iOS开发现在该如何选择方向?

跳槽,面试,进阶,加薪;这些字眼,相信每位程序员都不陌生!但是方向的选择,却不知如何抉择!其实最好的方向,已经在各个企业面试需求中完美的体现出来了;本文展示了2份面试需求,以及方向的总结,希望对读者有所帮助2份面试需求熟练组件化架构,有较强的解耦能力,深刻理解设计模式熟悉常用的网络通信协议,如http、tcp、udp等,了解socket通信机制熟练使用Objective-C,熟悉iOS的内存管理机制和多线程开发,或具有良好的C/C++语言基础,了解内存和指针概念,对于优化程序的性能有一定经验;有较强的英语能力,拥有良好的代码规范有研读源码的能力:objc runloop dispacth …有较强的三方能力,知其然而知其所以然有OpenGL,OpenGLES,FFmpeg 底层音视频开发者优先有逆向开发经验优先考虑了解算法,数据结构熟练组件化架构,MVP,MVVM,MVC,ROUTER有较强的理解;熟练掌握Objective-C语言,理解面向对象编程思想,具有较强的设计能力;熟练掌握APNS、UI布局、数据库、网络等开发技术;深入理解Objective-C Runtime、RunLoop等基础原理;对App提高用户体验、性能调优、防崩溃、节省流量等的方法有深入了解;良好的编程习惯,逻辑清晰,认真细致,良好的沟通能力,主动的沟通意识;较强的自学能力、自我驱动力、强烈的探索欲。总结:需要掌握了解的技术点架构模式,编程思想,设计模式底层进阶,深层理解三方框架要知其然,而知其所以然多线程与网络内存管理,性能优化数据结构和算法音视频方向逆向方向相信看完,也许发现这些技术领域自己可能都知道,却没行动学习起来,或者没坚持下来!!正如“大道理都懂,但是依然过不好这一生”最后说一句:请合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间"来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!如果想有个学习交流的圈子,可以加iOS高级交流群:624212887;请教的问题,会的都会解答,欢迎入驻推荐文集* BAT—最新iOS面试题总结

April 3, 2019 · 1 min · jiezi

想要更精准的小程序模版消息推送?我们来帮你实现

两年多前,为了让更多的人找到好玩、好用的小程序,我们成立了「知晓程序」。再后来,我们推出了后端云服务平台——知晓云,帮助大家降低创业成本,提升开发效率。「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。随着小程序生态的不断发展、用户规模的不断扩大,开发者的项目也在高速增长。他们不再满足于现有的后端服务支持,开始需要更多开箱即用的功能。为此,我们围绕「简化开发流程,让用户仅需专注于业务」这一目标,相继研发了多个好用的功能。在用户喜爱的众多功能中,使用率最高的是模版消息推送。这一功能自 2018 年 4 月上线后,使用率不断提升,完美的达成了我们设计此功能时的初衷——使用户获得【开箱即用,无需编码】的推送能力,并将其用于小规模的消息通知及用户管理。一年来,知晓云平台上有越来越多的小程序,从数百用户的小项目成长到 UV 过百万级,甚至超过千万的明星项目。模版消息推送数的量级也由早期每天几百条,变为后来的每天数百万条。客户的项目逐渐成熟,其需求也从简单的模版消息推送,逐渐转变为基于推送的转化、留存全链路触达与数据分析服务。因此,我们决定再往前迈进一步。5 月 8 日,我们将正式推出「知晓推送」服务,帮助运营者处理好粉丝转化、消息推送、数据分析等多个层面的麻烦事,真正解决复购难、留存差的问题。决定推出「知晓推送」服务前我们考察了市场上现在服务存在的问题,主要有这几条:平台支持少:只做了微信小程序平台支持,无法实现在支付宝小程序及其他平台发推送的需求。服务价格贵:按年收费且价格高昂,发送一千条推送就需要付出 10 块钱的成本。效果追踪差:模板消息发完就完事儿了,推送效果如何、用户是否点击,点击的用户特征,这些数据都无法追踪与分析。这些问题,我们来逐一解决。平台支持少?知晓云已经支持包括微信小程序和支付宝小程序在内的各大小程序平台的消息推送,对 Android、iOS 平台的支持也将在近期上线。大家的业务版图拓展到哪儿,我们支持到哪儿!服务价格贵?不得不说,友商的定价确实高得离谱。我们来算一笔账:假设小王有一个 2 万用户的小程序,日常运营每周一次推送,一年要发 100 万条模板消息。如用 x 神推的服务,要花 2 万块。2 万块啊!痛大家所痛,我们决定把它除以 200——只要 100 块。效果追踪差?经过长期的实践总结,我们推出 3 大重磅特性:过滤低价值用户,提升回访转化率大数据精准过滤有恶意举报行为或被标记为机器人的用户,有效减少无效发送,提升模板消息的回访转化率。一键埋点,自动收集 formid收集用户的 formid 是发送模板消息的前提条件,知晓云 SDK 提供透明页面自动收集 formid 功能,在不打扰用户的前提下即可收集大量 formid。智能发送规则,实现精准推送开发者可以自行定义用户行为,为满足特定条件的用户发送对应的模板消息。实现消息的精准推送。知晓云结合不同行业的海量行业案例,挖掘了一套专有的用户过滤机制,祝你实现业务量的翻倍增长。「知晓推送」活动预告「知晓推送」的主要特性说完了。考虑到 5 月还比较遥远,我们先预告一下福利吧:如你还不是知晓云用户,赶紧注册用起来吧~「知晓推送」正式上线前完全免费,服务上线当天还有额外福利~如你已经是知晓云用户了,也请您放心。5.8 日前注册的用户都可获赠价值不少于 198 元的资源包哦~支持 iOS、Android 的公测福利(4/2 - 4/10)「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。另外,知晓云支持移动端(iOS、Android)的公测活动即将开启:即日起至 4 月 10 日,通过扫描下方的二维码填写表单报名参与公测活动的用户,在成功接入移动端(iOS、Android)应用后将获得 100 元无门槛优惠券???? 报名点这里????知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。

April 2, 2019 · 1 min · jiezi

抖音分享和授权

准备工作注册appkey (抖音开放平台)集成sharesdk(下载地址)Xcode配置:urlScheme为注册的appkey, 白名单:douyinsharesdk ,douyinopensdk业务代码初始化import <ShareSDK/ShareSDK.h>[ShareSDK registPlatforms:^(SSDKRegister *platformsRegister) {//抖音[platformsRegister setupDouyinByAppKey:@“app_key” appSecret:@“app_secret”];}];分享可以分享图片,相册图片,单个视频,多个视频分享图片// 通用参数设置—-图片分享可以使用相册地址、沙盒路径、网络图片地址NSString *imageURL = @“http://img.hb.aicdn.com/28a4962c297205e0868cdb45bb527e2bc5319f08f019-l7N1A3_fw658";NSMutableDictionary *parameters = [NSMutableDictionary dictionary];[parameters SSDKSetupShareParamsByText:nil images:@[imageURL] url:nil title:nil type:SSDKContentTypeImage]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:shareParams onStateChanged:^(SSDKResponseState state, NSDictionary userData, SSDKContentEntity contentEntity, NSError *error) {if (state == SSDKResponseStateSuccess) { NSLog(@“成功!”);}else{ NSLog(@”%@",error);}}];分享视频// 通用参数设置—-视频分享可以使用相册地址、沙盒路径,不支持网络视频,如果使用网络视频请先下载放到沙盒目录下或相册里NSString *videoPath = [[NSBundle mainBundle] pathForResource:@“cat” ofType:@“mp4”];NSMutableDictionary *parameters = [NSMutableDictionary dictionary];[parameters SSDKSetupShareParamsByText:nil images:nil url:[NSURL URLWithString:videoPath] title:nil type:SSDKContentTypeVideo]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:parameters onStateChanged:^(SSDKResponseState state, NSDictionary *userData, SSDKContentEntity *contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@“成功!”); }else{ NSLog(@"%@",error); }}];分享多个视频// 平台定制—-只能使用相册且使用相册标识localIdentifier__block NSMutableArray *assetLocalIds = [NSMutableArray array];__weak typeof(self) weakSelf = self;[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ NSURL *url_1 = [[NSBundle mainBundle] URLForResource:@“cat” withExtension:@“mp4”]; NSURL *url_2 = [[NSBundle mainBundle] URLForResource:@“cat” withExtension:@“mp4”]; PHAssetChangeRequest *req_1 = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url_1]; NSString *localId_1 = req_1.placeholderForCreatedAsset.localIdentifier; [assetLocalIds addObject:localId_1]; PHAssetChangeRequest *req_2 = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url_2]; NSString *localId_2 = req_2.placeholderForCreatedAsset.localIdentifier; [assetLocalIds addObject:localId_2]; } completionHandler:^(BOOL success, NSError * _Nullable error) { if (success) { dispatch_async(dispatch_get_main_queue(), ^{ NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; [parameters SSDKSetupDouyinParamesByAssetLocalIds:assetLocalIds type:SSDKContentTypeVideo]; [ShareSDK share:SSDKPlatformTypeDouyin parameters:parameters onStateChanged:^(SSDKResponseState state, NSDictionary *userData, SSDKContentEntity *contentEntity, NSError *error) { if (state == SSDKResponseStateSuccess) { NSLog(@“成功!”); }else{ NSLog(@"%@",error); } }]; }); }}];授权[ShareSDK authorize:SSDKPlatformTypeDouyin settings:nil onStateChanged:^(SSDKResponseState state, SSDKUser user, NSError error) { if (state == SSDKResponseStateSuccess) { NSLog(@"%@",[user.credential rawData]); NSLog(@"%@",user.rawData); } else { NSLog(@"%@",error); }}]; ...

April 2, 2019 · 1 min · jiezi

APNs 概述(转载)

苹果推送通知服务(APNs)是远程通知的中心。它是应用程序开发者向 iOS(间接地,watchOS),tvOS,和 macOS 设备传递信息的一个健壮,安全,高效的服务。你的应用程序在用户设备上初次启动时,系统自动地在你的应用程序和APNs 之间建立一条可信的,加密的,持久的 IP 连接。这条连接允许你的应用程序执行一些设置来启用消息接收,如 [配置远程通知支][1]持 说明的那样。连接的另一半用于发送通知 —— provider 服务器和 APNs 之间持久,安全的通道 —— 需要在你的线上[开发者帐号][2]中进行配置并使用苹果提供的加密证书。Provider 是一个服务器,由你部署并管理,由你配置来与 APNs 协同工作。如图,展示了远程通知的传送路径。Provider 的职责在你的 provider 中和你的应用程序中配置完了通知设置之后,你的 providers 就可以给 APNs 发送通知请求了。APNs 传递对应的通知载荷给每个目标设备。收到通知后,系统将载荷传递给设备上适当的应用程序,并管理与用户的交互。如果你的应用程序的通知到达时,设备处于开机状态但应用程序不在运行,系统依然可以显示通知。如果 APNs 发送通知时,设备处于关机状态,则 APNs 保留该通知并在稍后重试(更多详情,请参考 服务质量,存储和转发,合并的通知)。详情见:https://www.jianshu.com/p/13b…

April 2, 2019 · 1 min · jiezi