关于xcode:Xcode15与苹果ios17适配以及遇到的问题

<article class=“article fmt article-content”><p>大家好,我是你们的好敌人咕噜铁蛋!最近,苹果公布了全新的iOS17零碎,而作为开发者,咱们须要确保咱们的应用程序可能与这个新零碎完满适配。因而,明天我将和大家分享一些对于Xcode15与苹果17零碎适配的教训,并介绍一些常见的问题以及解决办法。<br/>首先,让咱们来看看如何进行适配。</p><ol><li>应用最新的Xcode版本<br/>确保你曾经降级到最新的Xcode15版本。苹果会在新版本中提供对最新零碎的反对和优化,因而应用最新的工具能够确保你可能充分利用新零碎的性能和性能。</li><li>更新依赖库和框架<br/>在适配新零碎时,查看你的我的项目所依赖的第三方库和框架是否有更新版本可用。许多库和框架的开发者通常会在新零碎公布后尽快公布相应的更新版本,以解决与新零碎兼容性相干的问题。</li><li>查看API变动<br/>苹果在每个新零碎中都可能会引入一些API变动,因而在适配过程中,仔细检查苹果的文档,理解新零碎中哪些API已被弃用、代替或新增。确保你的应用程序应用的是最新的API,并相应地更新你的代码。</li><li>适配新的用户界面<br/>新零碎通常会引入一些用户界面的变动和更新,例如新的设计语言、布局形式或者控件款式。在适配过程中,确保你的应用程序的用户界面与新零碎保持一致,提供更好的用户体验。<br/>当初,让咱们来看看可能会遇到的一些问题以及解决办法。</li><li>兼容性问题<br/>在适配过程中,你可能会遇到一些兼容性问题,特地是当你的应用程序依赖于某些特定的库或框架时。如果你遇到了这种状况,首先查看是否有更新的版本可用。如果没有,你能够尝试分割库或框架的开发者,询问他们是否有打算公布适配新零碎的更新版本。</li><li>解体和性能问题<br/>新零碎的公布可能会引入一些解体或性能问题,这可能是因为零碎自身的问题或者你的应用程序在新零碎上的不兼容性导致的。如果你遇到了这些问题,倡议你先降级到最新的Xcode版本,并仔细检查你的代码,特地是与新零碎相干的局部。如果问题依然存在,你能够尝试应用调试工具来定位问题,并及时向苹果报告这些问题,以取得更好的反对和解决方案。</li><li>用户界面适配问题<br/>新零碎可能会引入一些用户界面的变动,例如新的字体、色彩或者控件款式。在适配过程中,确保你的应用程序的用户界面与新零碎保持一致,以提供更好的用户体验。你能够通过更新你的应用程序的主题、调整布局或者应用新的控件款式来实现界面适配。<br/>通过应用最新的Xcode版本、更新依赖库和框架、查看API变动以及适配新的用户界面,咱们能够确保咱们的应用程序与苹果17零碎完满适配。同时,在解决兼容性问题、解体和性能问题以及用户界面适配问题时,咱们应该采取相应的措施,并及时向苹果反馈问题,以取得更好的反对。<br/>心愿这篇文章对你有所帮忙。如果你有其余对于Xcode15与苹果17零碎适配的问题,欢送在评论区留言,咱们一起交流学习吧!感激大家的浏览,咱们下期再见!</li></ol></article>

February 27, 2024 · 1 min · jiezi

关于xcode:iOS逆向与安全使用ollvm混淆你的源码

前言当你在钻研他人源码的时候,是不是冀望着他人代码没有进行任何的防护和混同。这时的你,是不是应该考虑一下本人代码的平安.本篇文章将通知你,如何应用ollvm来混同iOS端的代码【此文为入门贴,大佬请绕道】。 一、指标编译ollvm工具,并在Xcode中来混同你的ipa或动静库,减少他人破解你源码的难度。 二、工具ollvm:下载地址:https://github.com/heroims/ob...Xcode13:iOS开发工具 三、步骤1、基础知识LLVMLLVM(Low Level Virtual Machine)是一个开源的编译器基础架构,它蕴含了一组模块化、可重用的编译器和工具,反对多种编程语言和指标架构,包含x86、ARM和MIPS等。LLVM最后由美国伊利诺伊大学香槟分校(University of Illinois at Urbana–Champaign)的Chris Lattner传授开发,当初由LLVM社区进行保护和倒退。LLVM的核心思想是将编译器分为前端和后端两个局部,前端负责将源代码转换为两头示意(IR),后端负责将两头示意转换为指标机器的汇编代码。这种设计使得LLVM能够反对多种编程语言,因为只须要为每种语言编写一个前端,就能够利用后端的通用性反对多种指标架构。除了编译器之外,LLVM还包含了一些工具,例如优化器、调试器、汇编器和反汇编器等,这些工具能够帮忙开发者更好地剖析和调试程序,进步代码的性能和可靠性。LLVM曾经成为了宽泛应用的编译器基础架构,许多编程语言和工具链都采纳了LLVM作为后端,例如C、C++、Objective-C、Swift、Rust、Go等。LLVM还被广泛应用于计算机体系结构钻研、代码安全性剖析、机器学习等畛域。 ClangClang是基于LLVM框架的C、C++、Objective-C和Objective-C++编译器,它是一个开源我的项目,由LLVM社区进行开发和保护。Clang的设计指标是提供高质量的诊断、疾速的编译速度、低内存占用和良好的可移植性。Clang的编译器前端应用了古代的编译器架构,包含基于词法分析器和语法分析器的语法分析,生成形象语法树(AST)并进行类型检查和语义剖析等步骤。这些步骤的优化和并行化使得Clang可能疾速地进行编译,同时提供了更好的谬误和正告信息,有助于开发者更快地发现和修复代码中的问题。除了作为独立的编译器之外,Clang还能够作为其余工具的库应用,例如动态剖析工具、编辑器插件和代码重构工具等。Clang的模块化设计和良好的API使得它能够轻松地被集成到其余工具中,从而提供更好的编程体验。因为Clang的优良性能和良好的设计,它曾经成为了许多我的项目的首选编译器,例如LLVM本身、macOS和iOS的默认编译器等。同时,许多开发者和组织也在踊跃地开发和奉献Clang的代码,使得它在将来仍有广大的倒退空间。 OLLVMOLLVM(Obfuscator-LLVM)是基于LLVM框架的混同器,它能够对程序进行混同以进步程序的安全性。OLLVM的设计指标是提供一种灵便的、可定制的混同计划,使得攻击者更难了解和分析程序的行为。OLLVM通过对程序进行多种混同操作来实现混同成果,例如代码替换、函数内联、控制流平坦化、加密等。这些混同操作能够改变程序的控制流图和数据流图,使得程序更难以被了解和逆向剖析。同时,OLLVM还提供了一些额定的平安机制,例如加密程序的字符串、应用栈爱护和地位无关代码等,以减少程序的安全性。因为OLLVM是基于LLVM框架开发的,它能够与现有的LLVM工具和编译器集成,例如Clang和LLDB等。这使得开发者能够轻松地在现有的开发环境中应用OLLVM,并且能够应用现有的工具对混同后的程序进行调试和剖析。只管OLLVM的次要目标是进步程序的安全性,但它也能够用于其余畛域,例如代码爱护、代码压缩和代码优化等。因为其灵活性和可定制性,OLLVM曾经被广泛应用于许多畛域,例如网络安全、游戏开发和金融等。IR之间的pass,就是混同器工作的中央。相干代码位于obfuscator/llvm/lib/Transforms/Obfuscation/ 2、编译ollvm命令如下: witchan@witchandeMacBook-Air ~ % git clone -b llvm-13.x https://github.com/heroims/obfuscator.gitwitchan@witchandeMacBook-Air ~ % $cd obfuscatorwitchan@witchandeMacBook-Air ~ % mkdir buildwitchan@witchandeMacBook-Air ~ % cd buildwitchan@witchandeMacBook-Air ~ % cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_CREATE_XCODE_TOOLCHAIN=ON -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" -DLLVM_ENABLE_NEW_PASS_MANAGER=OFF ../llvmwitchan@witchandeMacBook-Air ~ % make -j8witchan@witchandeMacBook-Air ~ % sudo make install-xcode-toolchainwitchan@witchandeMacBook-Air ~ % mv /usr/local/Toolchains /Library/Developer/注:make -j8这命令编译的快慢,取决于你的硬件设施挨个执行以上命令后,ollvm就编译并装置实现,成果如下: 3、混同命令及成果比照混同命令简介fla:该选项应用函数级别的混同来暗藏程序的构造。这通过随机重命名函数、增加不必要的控制流和删除调用的函数来实现。这减少了反编译和剖析代码的难度。bcf:该选项应用基本块级别的混同来暗藏代码的构造。这通过扭转基本块之间的控制流、增加不必要的基本块和移除基本块之间的条件分支来实现。sub:该选项应用字符串混同来暗藏代码中的常量字符串。这通过将字符串分成几个小块、将其存储在数组中并在运行时重新组合来实现。这使得剖析代码和查找敏感信息更加艰难。split:该选项应用控制流混同来减少程序的复杂性。这通过将函数分成几个基本块、增加随机的跳转指令和在运行时随机重组这些基本块来实现。这使得代码的流程更难以跟踪,从而减少了破解和反编译的难度。sobf:该选项应用源代码混同技术来暗藏代码的逻辑和构造。这通过应用相似加密的形式对代码进行变换,使其难以了解和剖析。这能够通过运行时解密来执行,从而暗藏代码的实在性能。示例代码如下: - (void)testMethod { NSString *name = @"wit"; int age = 18; NSArray *list = @[@1,@3,@5,@18]; for (int i=0; i<list.count; i++) { int value = [list[i] intValue]; if (value == age) { age += 10; name = @"chan"; } } NSLog(@"name = %@", name); NSLog(@"age = %d", age);}未应用混同: ...

February 27, 2023 · 8 min · jiezi

关于xcode:虚拟机的防火墙处理

每过段时间就要去网上搜一下,还不如放我本人这儿,省的去试错。查看firewall服务状态:systemctl status firewalld开启:service firewalld start重启:service firewalld restart敞开:service firewalld stop一劳永逸:systemctl disable firewalld.service

November 2, 2022 · 1 min · jiezi

关于xcode:csm移动端h5用什么样的视觉方案

我的项目需要求在挪动端H5须要展现一些数据统计的图表,尽管第一工夫想到的是echarts,常 用还有Highcharts,D3等,antv家族的图表UI难看一些,再加上F2是挪动端可视化计划于是 就抉择F2,关上官网果然眼前一亮F2提供的正能满足需要,F2官网地址: https://antv.alipay.com/zh-cn...首次应用记录一下掘坑之路: 一,疾速上手(此处参考官网api文档就能够实现 https://www.yuque.com/antv/f2/getting-started)1.习惯在vue外面应用cnpm装置,执行 cnpm install @antv/f2 --save  2.装置实现之后就是应用了,依据api提供的援用形式在vue外面是玩不转的,波及到图表 交互的须要点击的图表会报js谬误( 'interaction' is not a function),当然也不是 没有解决的方法的,看过源码会发现能够批改援用门路解决:const F2 = require('@antv/f2/lib/index')复制代码二,开始应用应用也特地的简略,官网api给到 的十分的具体,首先须要创立 <canvas> 标签,咱们就能够进行简略的图表绘制:创立 Chart 图表对象,指定图表 ID、指定图表的宽高、边距等信息;载入图表数据源;应用图 形语法进行图表的绘制;渲染图表。在官网demo中抉择相应本人想要的图表能够查看到代 码,利用到vue我的项目中就能够了,依据需要作相应的更改。 至此就能够看到成果了,是不是很简略大厂的货色就是香,真正做到了开箱即用 

October 14, 2022 · 1 min · jiezi

关于xcode:Xcode13-适配之打印启动时间

Xcode13上统计启动时长的变量DYLD_PRINT_STATISTICS生效了。团队中须要保留每次的启动工夫以作测验优化规范。在网上找到上面文章,写了个获取启动工夫工具类。 import "AppLaunchTime.h" import <sys/sysctl.h> import <mach/mach.h> @implementation AppLaunchTime double __t1; // 创立过程工夫 double __t2; // before main double __t3; // didfinsh /// 获取过程创立工夫 (CFAbsoluteTime)processStartTime { if (__t1 == 0) { struct kinfo_proc procInfo; int pid = [[NSProcessInfo processInfo] processIdentifier]; int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; size_t size = sizeof(procInfo); if (sysctl(cmd, sizeof(cmd)/sizeof(*cmd), &procInfo, &size, NULL, 0) == 0) { __t1 = procInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + procInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0; }} ...

December 10, 2021 · 1 min · jiezi

关于xcode:iOS平台-快速集成华为AGC认证服务

介绍如何让用户依据已有的账号来进行登录注册呢?在利用中集成华为AGC认证服务SDK来轻松疾速地实现这个性能。 本篇内容依据官网文档领导集成过程总结实现,对于集成步骤,官网的材料写的有点多,当初我总结一下步骤: 1、开明华为认证服务 2、在iOS我的项目中集成SDK 3、设计登录注册页面 4、开启认证形式 5、注册登录性能开发 6、打包测试 开明华为认证服务在AppGallery Connect页面点击"我的我的项目",在我的项目的利用列表中抉择须要开明认证的利用,导航抉择"构建 > 认证服务"。如果是首次应用认证服务,请点击"立刻开明"开明服务。 集成SDK针对Xcode开发环境,您能够通过Cocoapods集成形式将认证 SDK集成到您的Xcode开发环境中。 1. 在Xcode我的项目中增加以后利用的AppGallery Connect配置文件。1) 登录AppGallery Connect网站,点击“我的我的项目”。 2) 在我的项目列表中找到您的我的项目,在我的项目下的利用列表中抉择您须要集成SDK的利用。 3) 点击“我的项目设置”中“利用”栏下的“agconnect-services.plist”下载配置文件。 4) 将“agconnect-services.plist”文件拷贝到利用级根目录下。 ## 2. 创立Podfile文件。 关上命令行窗口,导航至Xcode我的项目所在的地位,创立Podfile文件。如果曾经存在,可跳过本步骤。 cd project-directorypod init3. 编辑Podfile文件。1) 集成认证SDK 编辑Podfile文件,减少pod依赖pod ‘'AGConnectAuth'。 target 'AGConnectAuthDemo' do pod 'AGConnectAuth' end2) 执行 pod install,而后关上.xcworkspace文件查看该我的项目。 # 界面设计本次Codelab中您能够在Xcode工程中创立一个布局页面,参照下图进行UI设计,通过手机号、邮箱账号进行注册登录。 开启认证形式1. 登录AppGallery Connect网站,点击“我的我的项目”。2. 在我的项目列表中找到您的我的项目,在我的项目下的利用列表中抉择您的利用。3. 在“构建”栏下找到“ 认证服务”,在"认证形式"页签下“操作”一栏里,点击启用手机号码、邮箱地址。 注册登录性能开发1. 在将故事板的控件关联到ViewController中,以便您通过输入框取得登录注册所需的参数。 @IBOutlet weak var phoneText: UITextField!//手机账号输入框 @IBOutlet weak var phoneVertifyText: UITextField!//手机验证码输入框 @IBOutlet weak var phonePassword: UITextField!//手机账号密码输入框 @IBOutlet weak var emailText: UITextField!//邮箱账号输入框 @IBOutlet weak var emailVertifyText: UITextField!//邮箱验证码输入框 @IBOutlet weak var emailPassword: UITextField!//邮箱账号密码输入框2. 如果应用的是未注册过的手机号,首先您须要发送验证码到手机,来验证是否自己注册。输出手机号码,点击“发送验证码”按钮,代码执行到申请手机验证码的办法,在返回中能够看到发送后果。@IBAction func phoneSendVertifyCode(_ sender: Any) { let setting = AGCVerifyCodeSettings.init(action: AGCVerifyCodeAction.registerLogin, locale: nil, sendInterval: 30) AGCPhoneAuthProvider.requestVerifyCode(withCountryCode: "86", phoneNumber: phoneText.text ?? "", settings: setting).onSuccess { (results) in //手机验证码发送胜利 }.onFailure { (error) in //手机验证码发送失败 } }3. 手机收到验证码后,输出验证码和您要设置的明码,就能够进行注册了。在页面上点击“register”按钮,执行手机号注册的办法,注册后果您也能够在block返回中看到。@IBAction func register(_ sender: Any) { AGCAuth.instance().createUser(withCountryCode: "86", phoneNumber: phoneText.text ?? "", password: phonePassword.text ?? "", verifyCode: phoneVertifyText.text ?? "").onSuccess { (result) in //手机注册胜利 }.onFailure { (error) in //手机注册失败 } }4. 注册胜利后,输入您的手机号和设置的明码,点击“login”按钮进行登录。这样您就实现了,应用AGConnectAuth进行手机号注册登录的步骤。@IBAction func login(_ sender: Any) { let credential = AGCPhoneAuthProvider.credential(withCountryCode: "86", phoneNumber: phoneText.text ?? "", password: phonePassword.text ?? "") AGCAuth.instance().signIn(credential: credential).onSuccess { (result) in //登录胜利 }.onFailure { (error) in //登录失败 } }5. 如果应用的是未注册过的邮箱账号,首先须要发送验证码到邮箱来验证。输出邮箱号码,点击“发送验证码”按钮,代码执行到申请邮箱验证码的办法,在返回中您也能够看到发送后果。@IBAction func emailSendVertifyCode(_ sender: Any) { let setting = AGCVerifyCodeSettings.init(action: AGCVerifyCodeAction.registerLogin, locale: nil, sendInterval: 30) AGCEmailAuthProvider.requestVerifyCode(withEmail: emailText.text ?? "", settings: setting).onSuccess { (result) in //邮箱验证码发送胜利 }.onFailure { (error) in //邮箱验证码发送失败 } }6. 邮箱收到验证码后,输出验证码和您要设置的明码,点击“register”按钮,就能够实现注册了。@IBAction func register(_ sender: Any) { AGCAuth.instance().createUser(withEmail: emailText.text ?? "", password: emailPassword.text ?? "", verifyCode: emailVertifyText.text ?? "").onSuccess { (result) in //邮箱注册胜利 }.onFailure { (error) in //邮箱注册失败 } }7. 注册胜利后,输入您的邮箱号和设置的明码,点击“login”按钮进行登录。这样您就实现了,应用AGConnectAuth进行邮箱注册登录的步骤。@IBAction func login(_ sender: Any) { let credential = AGCEmailAuthProvider.credential(withEmail: emailText.text ?? "", password: emailPassword.text ?? "") AGCAuth.instance().signIn(credential: credential).onSuccess { (result) in //登录胜利 }.onFailure { (error) in //登录失败 } }8. 调用signOut办法,退出登录状态。@IBAction func logout(_ sender: Any) { AGCAuth.instance().signOut() }打包测试1. 在Xcode上点击运行按钮,在手机或模拟器上运行利用。输出手机号码,点击“发送验证码”按钮,待手机收到验证码,输出验证码和您要设置的明码,点击“register”,打断点查看是否注册胜利,注册胜利后点击“login”。打断点查看返回后果,result能够看到登录胜利后的用户信息。 ...

October 19, 2021 · 2 min · jiezi

关于xcode:xcode-更多版本下载

之前的地址是:https://developer.apple.com/d... 然而点进去却是这样的 what?苹果竟然改地址了,我一搜寻上面内容,发现第一条记录就是Apple developer问答 https://developer.apple.com/download/more The page you’re looking for can’t be found.发现外面也有https://developer.apple.com/d... 的地址,心想苹果本人的答复,当初也不能应用?在贴子里点击more地址,发现他重链接到了以下地址:所以当初想下载其余版本xcode,只能用上面链接了https://developer.apple.com/d...

June 12, 2021 · 1 min · jiezi

关于xcode:升级Xcode12-遇到的问题

谬误一ReactNative报'event2/event-config.h' file not found解决方案关上testApp.xcworkspace编译呈现'event2/event-config.h' file not found 查阅是因为Flipper-Folly版本导致的,将iOS文件下的 Podfile 文件做如下批改 use_flipper! 批改为 use_flipper!({ 'Flipper-Folly' => '2.3.0' })改完后保留,将Podfile.lock文件删除,而后从新 pod installinstalled实现后,返回下层目录执行 yarn ios (或react-native run-ios)就能够启动了解决方案来自:https://github.com/facebook/r... 谬误二[!] Unable to find a specification for `Bugly (= 2.5.9)` You have either: * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`. * mistyped the name or version. * not added the source repo that hosts the Podspec to your Podfile.在podfile中增加 ...

April 15, 2021 · 1 min · jiezi

关于xcode:双十二日渐临近如何用vika维格表薅羊毛到爽

双十二还不晓得买什么的敌人们别放心~给大家奉上来自 vika 用户——勤俭持家小达人 @Never 用vika 维格表制作的双十二购物 List(已获分享受权),一起来抄作业吧~奉上链接:https://vika.cn/share/shro62a...戳一戳,另存为模板吧~ —分割线— vika 维格表,作为科技新贵的内容合作神器,不仅是新一代的团队数据合作和项目管理工具,也是一款反对 API 的连贯型智能多维表格。 操作繁难灵便,不懂技术的零根底小白也能分分钟上手,so easy!无论你是企业还是个人用户,都能够像玩积木乐高一样无拘无束,纵情施展创造力,通过 vika 维格表自由组合和拼接数据,搭建专属的「数据城堡」。 接下来就是 vika 维格表的 solo 工夫啦,一起来康康吧~ 多维度视图 灵便出现数据 一年一度的大型剁手节日渐邻近,目不暇接的商品、迷人的优惠折扣,是不是乱花渐欲美人眼呢?wait!放着 vika 维格表来,帮你安顿得明明白白。得益于 vika 维格表 「智能视图」 的性能,同一张维格表能够变换出多种数据视图模式,如「维格视图」、「相册视图」、「甘特图」、「看板」等等,数据能够在不同视图间疾速切换查看,无时无刻满足你对数据可视化的体验。 举个栗子,vika 维格表能够将同一张双十二购物清单,变幻无穷出各式各样的出现形式。 通过维格列丰盛弱小的数据存储属性,将双十二必「buy」清单划分为品名、类别、折扣价、赠品、商品图片展现等多种内容。如果感觉商品图片不够清晰,还能够一键转换成「相册视图」,以大图的模式将商品图文并茂地展现进去,鼠标微微一点,单击你想查看的商品折扣信息,便能和盘托出~ 哇哦~vika 维格表的 「可视化视图」,让商品数据能够在不同视图中游离,自若切换,从全方位、多维度展示商品动静,将迷人的优惠折扣赤裸裸地出现在咱们背后,真的很难不心动啊~除此之外,「彩虹标签」也是 vika 维格表不得不提的小心机~利用 「彩虹标签」,将商品进行逐个分类:面膜、彩妆、护肤等等,每一个标签还能够任由你 DIY,自定义成你心仪的色彩属性。 丰盛字段类型 符合多种数据 上文提到,vika 维格表让数据变得丰富多彩,能够批量上传双十二所要选购的商品信息,如品类、价格等,还能够将图文、视频、音频等多种数据类型「一键加购」入维格列的字段中,切实太有意思啦~那么,这就不得不提维格表领有的弱小且实用的多种字段类型了。基于电子表格的个性,为了不便记录,vika 维格表把内容划分为一个个的维格列,每个维格列能够自由选择想要的字段类型。可供选择的类型十分丰盛,除了文本、数字、日期这些根底类型外,vika 维格表还反对单选、多选、长文本、附件(反对图片/视频/文档等)、地址、货币、评分等等多达十余种类型,符合不同内容的输出模式。 多种数据类型,vika 维格表让数据查看如便当贴般方便快捷,得心应手,想看就看~ 在双十二必「buy」清单中,能够自行添加商品购买渠道链接,随时追踪最新的优惠折扣资讯。如果你无从下手选购,还能够各种宝藏博主的测评指南,将清单内的商品进行打分,直击你最想要剁手的那个它。 其实,vika 维格表的实质是 「智能数据库」,不仅能让数据出现简洁明了,还能够通过「神奇援用」「神奇关联」等人性化的功能设计,无需反复机械化地进行数据输出,只需简略的操作便能逾越不同的表单,链接多条的数据记录,轻松穿插援用你想要的数据,省时,省力,更省心不再局限于纯文本的表格展现模式,将其余零散的数据信息通通整合在 vika 维格表内,不愧为一款高效的数据管理神器。 分组筛选 数据高深莫测 vika 维格表不仅反对对同一个表格提供多种视图模式,每个视图还能够通过 「筛选」、「分组」、「排序」 等微妙操作,帮你从繁杂的数据丛林中解脱,迅速筛选出你须要的数据信息。就拿这份双十二必「buy」清单来说吧,如果你想看到同品种别的商品,how to do?通过「分组」性能,将整张表单的数据进行分组,点击「分组」,再点击「增加分组条件」,依据字段类型和该列名称筛选出相应的数据。如将「商品品类」进行一一分组,所有商品的分类便清晰直观的出现在你眼前,即可马上浏览到品类商品的相干介绍。 当然,还能够将 「筛选」、「分组」、「排序」 进行组合应用,只需几下点击便能整合、统计并查看到所须要的所有记录,灵便直观地查看数据。 ...

December 11, 2020 · 1 min · jiezi

关于xcode:升级Xcode12遇到No-architectures-to-compile-for-ONLYACTIVEARCHYES

明天在降级完Xcode之后运行模拟器报错, 这是因为模拟器短少 x86_64指令集,咱们须要在Xcode 的TARGETS中批改VALID_ARCHS,增加你所短少的指令集,如图所示: 须要在DEBUG 前面增加x86_64 ,在从新运行即可

September 26, 2020 · 1 min · jiezi

关于xcode:Mojave-Xcode-真机-iOS-13-报错

问题形容应用 react native 开发 app Xcode真机调试报错:Could not locate device support files 应用的零碎版本为 macOS Mojave 10.14.6 无奈也不想降级 Catalina Xcode 版本为 10 解决办法网上搜到的办法通常是: 下载已有的真机设备反对包,比方在 Github 上下载 而后拷贝对应的文件夹到目录: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/这里留神文件夹的命名,真机连贯xcode后,会生成一个对应的文件夹名称在目录: /Users/xxxx/Library/Developer/Xcode/iOS DeviceSupport前者的文件夹命名要与后者保持一致,比方我的文件夹名称模式为 13.5 (17F75) arm64e拷贝实现后,重启 Xcode 在 Depoly Target 中手动填写 iOS 版本,我的是 13.5 而后运行 仍然报错,这个报错消耗了我两个多小时,无奈解决... 直到第二天,搜寻一些材料,想到了起因,并通过了验证,发现起因是: 上述办法只在 Xcode 11 无效 ! 所以降级 Xcode 到 11 ,上述办法就能够失效了,不再报错,具体降级办法参照我的上一篇文章。 降级前能够删除原来的 Xcode 版本,也能够重命名保留。

August 13, 2020 · 1 min · jiezi

使用XCODE快速发布IOS-ad-hoc测试版本

首先,你必须得有开发者账号,如何申请开发者账号,这里就不再累赘,请自行百度。 一、什么时候使用ad hoc证书 app发布之前,一般都要在真机上面进行测试,所以需要打包测试版(这不废话吗) 二、证书的创建与使用流程 在创建证书之前我们需要在我们的电脑上生成一个Certificate Signing Request即证书注册请求文件,找到mac下的“钥匙串访问”点击进入操作界面 选择“存储到磁盘”,点击存储之后我们会获得这样一个文件,请记住文件路径(笑脸)接下来我们登陆苹果开发者账号,Certificates-Development点+号新增证书 Choose file 选择刚刚下载的证书 此时你应该拥有了一个测试证书了,点击download并安装,你在钥匙串里面就能看到你的测试证书了。 三.创建app ids 点击右上角的+号 Bundle ID栏中填写的必须和你的xcode -Bundle Identifier中的内容保持一致完善表单,提交submit就可以完成这一步的操作 四、创建测试设备 点击左侧菜单的Devices下的all 来添加我们所支持的运行设备,填写设备的nama,UDID可以通过itunes查看,完善表单添加测试设备 五、创建Provisioning Profiles文件 选择我们之前创建的app id Continue 选择我们创建的调试证书 双击安装 六、xcode 如图操作点击preferences添加你的开发者账号 七、接下来我们去xcode,Team这行里选择你刚才账号中的team Name中显示的名字 设置Target-General-Signing,x-code8 有一个Automatically manage signing,此时要去掉勾选,然后配置Provisioning Profile 为刚刚创建好的 adhoc 配置文件 八.此时所有环境都配置完成 菜单选择 Product -> Archive,然后等待编译 九.还不打包? ...

October 8, 2019 · 1 min · jiezi

RxSwift-MVVM-初体验

一、原起作为一名iOS开发者,必须跟上时代的潮流,随着swift ABI越来越稳定,使用swift开发iOS APP 的人越来越多。从网上看了很多文章,也从github上下载了很多demo进行代码学习。最近使用RxSwift+MVVM+Moya进行了swift的体验之旅。加入到swift开发的大潮中去。 二、目录结构这个demo的项目结构包括:View、Model、ViewModel、Controller、Tool、Extension。 ViewModel是MVVM架构模式与MVC架构模式最大的区别点。MVVM架构模式把业务逻辑从controller集中到了ViewModel中,方便进行单元测试和自动化测试。 ViewModel的业务模型如下: viewmodel相当于是一个黑盒子,封装了业务逻辑,进行输入和输出的转换。 其中View、Model与MVC架构模式下负责的任务相同。controller由于业务逻辑移到了Viewmodel中,它本身担起了中间调用者角色,负责把View和Viewmodel绑定在一起。 demo的整体目录结构如下: 三、使用到的第三方库开发一个App最基本的三大要素:网络请求、数据解析、UI布局,其它的都是这三大要素相关联的,或者更细的功能划分。 网络请求库使用的Moya,数据解析使用的是ObjectMapper,UI布局使用的是自动布局框架Snapkit,图片加载和缓存使用的是Kingfisher,刷新组件使用的MJRefresh,网络加载提示使用的是SVProgressHUD。使用到的三方库的cocoapod目录如下: 四、具体实现4.1 viewmodel的协议viewmodel的实现需要继承NJWViewModelType这个协议,需要实现输入->输出这个方法。这个算是viewmodel的一个基本范式吧。 protocol NJWViewModelType { associatedtype Input associatedtype Output func transform(input: Input) -> Output}4.2 viewmodel的具体实现这里包括了输入、输出的具体实现,与及func transform(input: NJWViewModel.NJWInput) -> NJWViewModel.NJWOutput 这个输入转输出方法具体的实现逻辑。具体代码如下: class NJWViewModel: NSObject { let models = Variable<[GirlModel]>([]) var index: Int = 0}extension NJWViewModel: NJWViewModelType{ typealias Input = NJWInput typealias Output = NJWOutput struct NJWInput { var category = BehaviorRelay<ApiManager.GirlCategory>(value: .GirlCategoryAll) init(category: BehaviorRelay<ApiManager.GirlCategory>) { self.category = category } } struct NJWOutput { let sections: Driver<[NJWSection]> let requestCommand = PublishSubject<Bool>() let refreshStatus = Variable<NJWRefreshStatus>(.none) init(sections: Driver<[NJWSection]>) { self.sections = sections } } func transform(input: NJWViewModel.NJWInput) -> NJWViewModel.NJWOutput { let sections = models.asObservable().map{ (models) -> [NJWSection] in return [NJWSection(items: models)] }.asDriver(onErrorJustReturn: []) let output = Output(sections: sections) input.category.asObservable().subscribe{ let category = $0.element output.requestCommand.subscribe(onNext: { [unowned self] isReloadData in self.index = isReloadData ? 0 : self.index + 1 NJWNetTool.rx.request(.requestWithcategory(type: category!, index: self.index)) .asObservable() .mapArray(GirlModel.self) .subscribe({[weak self] (event) in switch event{ case let .next(modelArr): self?.models.value = isReloadData ? modelArr : (self?.models.value ?? []) + modelArr NJWProgressHUD.showSuccess("加载成功") case let .error(error): NJWProgressHUD.showError(error.localizedDescription) case .completed: output.refreshStatus.value = isReloadData ? NJWRefreshStatus.endHeaderRefresh : NJWRefreshStatus.endFooterRefresh } }).disposed(by: self.rx.disposeBag) }).disposed(by: self.rx.disposeBag) }.disposed(by: rx.disposeBag) return output }}4.3 controller中数据绑定的具体实现把输入、输出和collectionview进行绑定,建立联系,达到操作UI进行数据刷新的目的。具体的绑定逻辑如下: ...

September 11, 2019 · 2 min · jiezi

Xcode利用软链接将-code-snippets断点templates-和主题备份到云存储中

#!/bin/bashBackup_Path="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Xcode"Xcode_config_path="$HOME/Library/Developer/Xcode"echo "Restoring code snippets..."ln -s "$Backup_Path/UserData/CodeSnippets" "$Xcode_config_path/UserData"echo "Restoring user breakpoints..."ln -s "$Backup_Path/UserData/xcdebugger" "$Xcode_config_path/UserData"echo "Restoring font and color themes..."ln -s "$Backup_Path/UserData/FontAndColorThemes" "$Xcode_config_path/UserData"echo "Restoring templates..."ln -s "$Backup_Path/Templates" "$Xcode_config_path"echo "All set!"也可以用 restore_xcode_configuration 修改 Backup_Path 和 Xcode_config_path 为对应的路径即可。

September 9, 2019 · 1 min · jiezi

只有程序员才懂的痛

小区新搬来一户人家,一个30岁左右的年轻漂亮女人,带着一个4岁的男孩。每天独自一人操持家务,买菜做饭,接送孩子。 时间久了,邻居们都有些议论,知道有一天,一个男人背着大包小包敲开了这家的门。男人刚进屋,女人哭声即传了出来。 女人叫到:“你TMD给我站阳台去,大声喊,我回家了!” 男人说:“天天不是出差就是996加班到半夜,难得今天下班那么早,能回家,你闹啥呢?” 女人:“你就给我站阳台上去,你让街坊邻居都看一遍,老娘不是二奶,不是小三,更TM不是寡妇,老娘有男人,老娘的男人是程序员!!!”

August 20, 2019 · 1 min · jiezi

对iOS代码重构的一点看法

一、原起基本上每一个项目都会经历这样的一个过程,前期的快速迭代,去做市场的试探,这个时候的要求是怎么快怎么来,经过市场试探,找到对应的盈利模式,与及摸准了用户的使用习惯,这个时候产品会进入一个稳步发展的阶段,这个时候很多公司就会开始考虑怎么样更好的去维护这个产品,这个时候重构就来了。 项目重构一方面是对之前开发不合理的地方进行整理重写,另一方面是为后续的扩展和维护打基础。 二、代码重构建议一览图 三、代码重构的具体细节3.1 操作提示操作提示是几乎所有App都会使用到的控件吧,一般都是三方开源的,现在比较流行的都是HUD相关库吧,这种库不仅有文案提示,一般还有对应的图标,显示和消失还伴随着动画。这才更符合移动App的使用体验。对于toast这种老旧的操作提示,直接放弃,UI太难看,不符合时代发展。 3.2 使用系统提供的新控件随着iOS系统的逐年更新,苹果会为我们提供一些更好用的控件来代替就控件。UIAlertView和UIActionSheet这两个控件属于旧时代的产物,苹果已经为我们提供了更好用的UIAlertController,一旦遇到前两个直接使用UIAlertController替换即可。 3.3 机型兼容问题早几年的时候,iOS还有32位操作系统,不过经过几年的发展iOS目前都是64位操作系统了。所以对于一些变量的定义直接使用64位的即可。定义整形变量就使用NSInteger,int就不要使用了;定义浮点型变量的时候,float也不要用了,直接CGFloat。 3.4 系统版本兼容问题对于系统版本的兼容,太老的系统版本就直接放弃吧,对于iOS来说兼容三个大版本,最多4个版本就足够了,对于那些4都不升级的用户,个人认为可以直接放弃了。 3.5 删除不再使用的代码版本的快速迭代过程中或所或少有一些功能是尝试之后失败的,对于这样的功能代码,如果确定是不再使用的,就删除吧。减少工程的体量,代码的维护高质量也会少一些。 3.6 数据模型项目开发中数据尽量做成数据模型,重构的过程中如果遇到这种情况,还是尽量做成模型,方便理解和维护。 不要直接使用字典进行传值,这样对于后期的维护不利;网络请求封装的时候,请求参数尽量单独写成一个参数,这样虽然多写了代码,但是见名知意,方便后续的维护,不要一个字典把所有的参数都扔进去,这样对于后期的维护很不利;网络请求的结果也尽量做成数据模型,不要直接使用字典进行传值。3.7 变量定义变量定义的时候尽量明确类型,除非万不得已,不然不要使用id类型。 3.8 尽量使用大众化的书写方式有的时候一段代码逻辑的编写可能有很多种方式,对于这种情况,我们尽量使用大众化的编写方式,不要为了偷懒,使用一些晦涩难懂的书写方式。导致过段时间自己都看不懂了。 3.9 组件化代码重构一方面是对之前代码的整理,另一方面是对后续扩展和维护打基础。所以重构的工程中,我们应该深入思考,对于一些使用场景比较多的代码,哪怕是多花点时间,也要把它做成通用组件,这样后续的开发能够事半功倍。 3.10 集合初始化对于集合的初始化,比如NSDictionary和NSArray,尽量使用简介的语法糖初始化方式,那种老旧的初始化方式就放弃吧。 3.11 枚举的使用对于一些多类型判断的场景,尽量使用枚举来定义场景类型。这样后续维护方便,代码看上去也更有逼格。

July 16, 2019 · 1 min · jiezi

swift的一些面试题

一、open与public的区别public:可以别任何人访问,但是不可以被其他module复写和继承。open:可以被任何人访问,可以被继承和复写。二、struct与class 的区别struct是值类型,class是引用类型。 值类型的变量直接包含它们的数据,对于值类型都有它们自己的数据副本,因此对一个变量操作不可能影响另一个变量。引用类型的变量存储对他们的数据引用,因此后者称为对象,因此对一个变量操作可能影响另一个变量所引用的对象。二者的本质区别:struct是深拷贝,拷贝的是内容;class是浅拷贝,拷贝的是指针。property的初始化不同:class 在初始化时不能直接把 property 放在 默认的constructor 的参数里,而是需要自己创建一个带参数的constructor;而struct可以,把属性放在默认的constructor 的参数里。变量赋值方式不同:struct是值拷贝;class是引用拷贝。immutable变量:swift的可变内容和不可变内容用var和let来甄别,如果初始为let的变量再去修改会发生编译错误。struct遵循这一特性;class不存在这样的问题。mutating function: struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用。继承: struct不可以继承,class可以继承。struct比class更轻量:struct分配在栈中,class分配在堆中。三、swift把struct作为数据模型3.1优点安全性: 因为 Struct 是用值类型传递的,它们没有引用计数。内存: 由于他们没有引用数,他们不会因为循环引用导致内存泄漏。速度: 值类型通常来说是以栈的形式分配的,而不是用堆。因此他们比 Class 要快很多!拷贝:Objective-C 里拷贝一个对象,你必须选用正确的拷贝类型(深拷贝、浅拷贝),而值类型的拷贝则非常轻松!线程安全: 值类型是自动线程安全的。无论你从哪个线程去访问你的 Struct ,都非常简单。3.2 缺点Objective-C与swift混合开发:OC调用的swift代码必须继承于NSObject。继承:struct不能相互继承。NSUserDefaults:Struct 不能被序列化成 NSData 对象。参考文章Swift 浅谈Struct与Class

July 14, 2019 · 1 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

直播知识结构整理

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

July 2, 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

Flutter

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

June 18, 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

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

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

关于常量的思考与总结

写在前面全局常量作为开发人员一定是一个比较熟悉的概念。全局常量的写法自然也比较多,最近在进行项目的常量重构时看到了各种各样的写法,其中宏定义占大部分,然而有很多使用宏定义是不规范的,而且宏定义只是在预编译阶段进行文本替换,不进行类型检查,从网上看到大量使用宏定义会拖慢编译速度。 所以在定义全局常量时,为了提高开发过程中的规范度和编译速度,宏定义并不是最佳选择。所以我重构的原则是: 能声明成外部常量的,尽量声明成外部常量,万不得已的才使用宏定义。 一、宏计算机科学里的宏(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

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

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-原生骨架库网络过渡动画封装

最新版 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

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

写在前面马甲包审核被拒,目前网上流传最广的就是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

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

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

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

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

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

April 22, 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

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

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

一个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

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

如何为Xcode添加漂亮的主题

由来作为一名iOS开发者,Xcode是我们每天必须使用的开发工具,但是时间久了,相信很多人每天对着代码就已经很乏味了。时间长了,对我们自己的眼睛也不太好,一个漂亮的主题,既能减少我们写代码、看代码时候的乏味度,还能保护我们的眼睛。主题选择好了,还能看上去高大上,提升我们的逼格,让iOS开发在外人眼里成为艺术的创作。为Xcode添加主题如果你对Xcode自带的主题换了又换,还是感觉不满意;或者你想提升Xcode的逼格,让自己看起来更像艺术创作者,那么接着往下看。下面就向你展示添加Xcode新主题,提升逼格的秘籍。下面请跟着我的招式走:首先下载主题解压压缩包。将压缩包中的dvtcolortheme文件,拷贝到~/Library/Developer/Xcode/UserData/FontAndColorThemes目录下。重启Xcode,在Xcode的主题选项里已经有了有了你新添加的主题,选择预览,选择自己喜欢的即可。附上图解:根据上图所示,通过前往文件夹,在弹出框中输入~/Library/Developer/Xcode/UserData/FontAndColorThemes把下载主题压缩包里V1&V2文件夹中的内容进行拷贝到FontAndColorThemes目录下,重启Xcode。重启Xcode之后,即可选择自己喜欢的主题。主题源码该主题由WWDC在2016年提供,并且开源了源码。源代码名称:WWDC2016-Xcode-Color-Scheme。源代码网址WWDC2016-Xcode-Color-Scheme源代码文档

April 5, 2019 · 1 min · jiezi

七牛云图床上传工具-iUpload

软件介绍: iUpload主要功能将图片上传至七牛云,返回 Markdown 格式的链接到剪粘板功能介绍:图片本地压缩图片右键上传图片复制上传图片拖拽上传https加密上传开发: 继承七牛云SDK,使用swift开发,App自签上传凭证,自动选择存储区域,通过https加密上传。下载: https://github.com/iChochy/iUpload/releases/download/1.0.1/App.dmg联系方系:邮箱:iChochy@qq.com网站:http://www.chochy.cnGitHub: https://github.com/iChochy/iUpload注: 处女作

April 3, 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

你会如何存储用户的一些敏感信息,如登录的token

使用keychain来存储,也就是钥匙串,使用keychain需要导入Security框架iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个ios程序都有一个独立的keychain存储。相对于 NSUserDefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因App被删除而丢失,所以在 重装App后,keychain里的数据还能使用。从ios 3。0开始,跨程序分享keychain变得可行。如何需要在应用里使 用使用keyChain,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。直接使用SecItem.h里方法操作keychain,需要写的代码较为复杂,为减轻 咱们程序员的开发,我们可以使用一些已经封装好了的工具类,下面我会简单介绍下我用过的两个工具类:KeychainItemWrapper和 SFHFKeychainUtils。自定义一个keychain的类CSKeyChain.h@interface CSKeyChain : NSObject+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service;+ (void)save:(NSString *)service data:(id)data;+ (id)load:(NSString *)service;+ (void)delete:(NSString *)service;@endCSKeyChain.m#import “CSKeyChain.h”#import<Security/Security.h>@implementation CSKeyChain+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service { return [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass, service, (__bridge_transfer id)kSecAttrService, service, (__bridge_transfer id)kSecAttrAccount, (__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible, nil];}+ (void)save:(NSString *)service data:(id)data { // 获得搜索字典 NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; // 添加新的删除旧的 SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery); // 添加新的对象到字符串 [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData]; // 查询钥匙串 SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);}+ (id)load:(NSString *)service { id ret = nil; NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; // 配置搜索设置 [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData]; [keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit]; CFDataRef keyData = NULL; if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { @try { ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData]; } @catch (NSException *e) { NSLog(@“Unarchive of %@ failed: %@”, service, e); } @finally { } } return ret;}+ (void)delete:(NSString *)service { NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);}@end在别的类实现存储,加载,删除敏感信息方法// 用来标识这个钥匙串static NSString * const KEY_IN_KEYCHAIN = @“com.cs.app.allinfo”;// 用来标识密码static NSString * const KEY_PASSWORD = @“com.cs.app.password”;+ (void)savePassWord:(NSString *)password { NSMutableDictionary *passwordDict = [NSMutableDictionary dictionary]; [passwordDict setObject:password forKey:KEY_PASSWORD]; [CSKeyChain save:KEY_IN_KEYCHAIN data:passwordDict];}+ (id)readPassWord { NSMutableDictionary *passwordDict = (NSMutableDictionary *)[CSKeyChain load:KEY_IN_KEYCHAIN]; return [passwordDict objectForKey:KEY_PASSWORD];}+ (void)deletePassWord { [CSKeyChain delete:KEY_IN_KEYCHAIN];}原文更多:iOS面试题大全 ...

April 1, 2019 · 1 min · jiezi

程序员,金三银四该不该跳槽?

“金三银四”跳槽季,成了职场人跳槽旺季的代名词,同时也给了职场人一个极强的心理暗示:只要在这个旺季跳槽,那也大概率能比其他时间跳槽到一个更好的下家。然而职场规则比职场人想象的还要理性,一个岗位对于应聘者的要求并不会因为求职淡旺季有太大的区别,反而会因为招聘旺季提升选拔标准。就像一池鱼都想跳进一个筐的时候,织筐的人反而会把筐编得更高些,筛选掉那些弹跳力弱的鱼,从而选到最有活力的鱼。据《2018年春季白领跳槽指数调研报告》显示,在18年的“金三银四” 中12.9%的白领正在办理离职/入职手续,56.7%的白领已更新简历正在求职,也就是说在跳槽季积极行动的白领比例高达69.6%;有趣的是,55.8%的跳槽者在跳槽后明确表明对新工作比较失望。这个数据为职场人敲了警铃,“金三银四”为60%的优质人才提供了繁荣的氛围与充足的机会,而剩余40%的求职者在“金三银四”制造出的跳槽泡沫中,被折射的短暂斑斓晃了眼,乱了心,瞎跳比不跳更可怕。金三银四跳不跳:被动原因的跳槽忍则炼 主动原因的跳槽需慎重说到跳槽旺季,不得不说说职场人跳槽的心理,暂且不提被辞退后逼不得已的换工作,大多数跳槽的原因可以分为主动原因及被动原因两种。被动原因跳槽,指跳槽的原因大部分源于被动接收的某些情况,如与同事关系不融洽、在工作中受到了委屈等。大部分的被动原因都不建议贸然跳槽,每个企业的职场状况千奇百态,但糟心事的套路却大同小异。跳槽后,同样的状况未必不会发生,且可能还有其他不可预估的职场困难。而且,有时候不舒服的职场环境反而是职场人成长的加速器。当然,少部分挑战价值观的情况下,如被公司指派做违法职业道德的事等,还是当跳则跳。今天我们更多要聊的是主动原因跳槽的情况,主动原因跳槽最为普遍且重要的驱动因素不外乎三点:想要涨薪、寻求晋升、追求自我发展。在金三银四这样跳槽的活跃期,由这三点主动原因引发的跳槽想法,更需要多角度评估。以下针对涨薪、晋升、自我发展三个主动原因的跳槽tips,希望能够帮助职场人减少跳错的几率。Tip1:高薪3坑,跳槽勿踩2018年金三银四白领跳槽的原因中,排名第一的是薪酬水平,约占55.8%,薪资成了职场人衡量工作职位优劣的重要标准。在互联网行业,一般跳槽的薪资涨幅在20-30%左右,这个可观的数字对金三银四跃跃欲试的职场人来说,是个不小的诱惑,但这诱惑背后隐藏的坑,准备跳槽涨薪的职场人还需擦亮眼后再行动。第1坑,薪资涨幅小于等于其他成本的增加例如:朋友A在某二线城市互联网公司税前薪资10k,在跳槽旺季接到了某一线城市公司offer,允诺工资15k。心动于高薪的A在和一位资深HR朋友聊天时咨询朋友是否应该接受新offer,朋友给A算了一笔账,一线城市每个月房租比目前要增加2k,交通和吃饭等生活成本每月增加1k,往返回家的路费每月增加1k,社交费用每月增加0.5k,算下来和现在的工资并无差别。当跳槽会带来其他连锁成本的增加时,若增加成本>薪资涨幅,因为想涨薪而跳槽的你则要三思而后行了。第2坑,薪资构成里可能藏着圈套例如:朋友B春季跳槽到一家开出高薪的影视公司,待了三个月后怨声载道。原来新公司的高薪是由20%的基本工资+80%的绩效组成,签合同时HR再三口头强调平均每月绩效能拿90%。而实际情况是,无论工作完成多出色,员工的绩效都被各种理由扣去近一半。类似的情况时有发生,有的公司开出高价年薪,却有近一半是所谓的“年底绩效分红“,或使用各种理由进行克扣。所以,当收到一份高薪offer时,先把薪资构成和HR谈清楚后再开心也不迟。第3坑,公司状况不明朗导致降薪危机拿到高薪offer的职场人,在以上两个坑都排除的情况下,最好了解清楚公司现阶段业务及资金状况,特别是创业型公司,确保公司的状况能够长期支付高额薪资且不会发生转部门降薪等情况。Tip2:跳槽≠一定晋升除了涨薪之外,晋升也是职场人跳槽的强动力。对于在金三银四想通过跳槽来晋升的职场人,我们今天主要讨论两个词:职业轨迹和现有机会。例如:朋友C,含实习在内有3家知名互联网公司的工作经验,且每一份经验都在一年左右。当她再次跳槽到一家和前公司体量相似的公司时,依旧没能如愿以偿的升为管理岗。在和她的聊天中得知,她的每一份工作都是基础岗,做了一年没有得到晋升消息,也不愿再寻求内部晋升机会,便赶集似的找起了下家,而下家的评估多是因无管理岗经验,则需从基础岗做起。C的第一个问题在于,职业轨迹的停滞导致晋升的瓶颈。试想一下,如果B在某一份工作中耐住性子积累更多的能力和经验,从基础岗升到初级管理岗后再选择跳槽,有了管理岗经验的C通过跳槽从经理岗晋升到高级经理岗便容易的多。C的另一个问题在于,欠缺寻求现有机会的职场嗅觉。内部晋升的机会在某些方面也是有迹可循的,最为关键的一点是要看上级岗位是否有空缺,且同级的同事中自己是否具有竞争力。若经过评估,现有公司有升职机会且自己有获得晋升的能力,不妨在现有公司多待一段时间,也为下一次跳槽积累更多筹码。所以,当跳槽的最大诉求是晋升时,先回头审视一下自己的职业轨迹是否清晰可发展,再抬头看看现有公司是否藏着努力可得的升职机会。也许,目前的稳当是为了日后跳得更高。Tip3:自我定位与规划要先于跳槽的动作关于自我发展的问题,年后的种种跳槽诱惑也是陷阱颇多,而准确的自我职业发展定位就成了重中之重。一味的求变并不能解决问题,规划好自我职业定位与发展方向后再计划性谋动才是治本之道。很多职场人稀里糊涂就选择了一份工作,比如,自己好像不适合做技术,但偏偏选择了研发部门;自己好像不喜欢做销售,但为了尽快拿到offer,却选择了销售岗。之所以用“好像”这个词,是因为这些人中的绝大部分并不清楚自己想要什么样的工作,适合什么样的岗位,很多职场人把这种情况产生的焦虑感误以为是需要跳槽解决的自我发展需求,却忽略了在职场中自我发展的本质。不论身处职场哪个阶段,自我的审视是很有必要的,这也决定了你在后面长时间的职业规划。首先要自我判断和定位,按阶段制定自我发展的计划,拔高自己的格局,找到自我提升的动力,而盲目的跳槽可能适得其反,扰乱自己的思考。金三银四好似职场人一年一度的跳槽狂欢,更像是中国职场现状的缩影。战略布局的快速变化导致公司岗位需求波动频繁,适应力较弱的职场人被动选择跳槽,适应力强的职场人因为对自我职业要求的提升也总想往高处跳,而一些认知不清晰的职场人受到周围环境氛围的影响,也动起了跳槽的念头。跳槽,本应是在职业道路中往上攀登的助力工具,现在却好像成了大部分职场人不得不做的事。当一件事变得不得不做时,压迫感会阻碍思考的空间,从而出现盲目跳槽的情况。又是一年金三银四,愿每一个想要跳槽的职场人都明确初衷,擦亮眼睛,跳出一片似锦前程。想跳槽推荐一看BAT—最新iOS面试题总结iOS面试题大全(附答案)

March 13, 2019 · 1 min · jiezi

程序员—10条求职的黄金规律

来看一下金三银四的招聘旺季下,10条求职的黄金规律。可以说每一条都很有一定深度01:很多时候,HR不要你,不是因为你水平的问题,也不是因为你专业技能的问题。而是HR自己对自己没信心,HR没把握你这样的候选人,会不会踏实地在部门内做事。HR觉得你够聪明,够优秀,但不敢用你,因为他们担心花了很大的精力去培养你,最后你没花心思放在这份工作上,这对HR和用人部门都是很大的打击。【不要怀疑自己】02:薪水高是否意味着一份好工作,答案无疑是否定的。一般情况下,薪水和期待成正比,既然有人给了你更高的经济回馈,那就意味着对你的期待更高。而一个人创造的价值并不完全由自己决定,还依赖于客观的条件,比如团队、客户、同事、客户、周期等。如果你要先享受更大的收益,然后再去创造价值,往往翻车的概率会很大。03:手里攥着Offer 来谈更高条件的候选人,一般不会被待见。如果单从薪酬上看,永远都有可能比当下更高薪的工作在等着自己,拿着Offer 来谈条件的候选人往往会被认定稳定性存疑。收入不是不重要,但不应该是决定一个人是否加入一家公司的先决条件。特别是工作数年后还对薪酬非常纠结的话,可能压根就没有对自己和外部环境有一个清晰的认识。04:企业对外招聘的时候,大家都不要太在意招聘广告上的薪酬范围数值,这个数值往往并不是公司实际对这个岗位的定薪标准。确实,薪酬写的越高越能吸引人,但职位工作的内容和挑战,会因为薪资的关系被弱化甚至被忽视。像在阿里,看官网上的招聘,不会放出某个职位的薪资范围,销售岗位偶尔例外。05:如果你真的有两把刷子,学历限制、工作年限条件、专业背景要求都不是问题。公司的 JD 是 HR 部门写的,HR希望能够最大程度上用高效率的方式筛选到合适人才。但实际的用人部门的需求更现实,用人部门只在乎来的人能不能解决问题。在阿里也有大专甚至中专的同事,一点都不影响他们成为公司的优秀员工,在职场上的员工优秀与否和学历有时候并不是正向关系。06:如果你现在的领导,排斥异己,容不下不同的声音,搞裙带关系,专心培养自己的所谓派系,评定业绩的时候做不到看业绩说话,那就早点离开,不要把自己有限的人生浪费在无聊的蝇营狗苟上。而且离职也并不是一件坏事,离职在另外一方面有助于提升自己的认知,扩大自己的视野,机会也会更多,所以别总纠结着或依依不舍,成年人都懂得取舍。1条观点07:人有三观,企业也有。但三观约束自己还行,不能用它来界定他人。因为你不是对方,你不了解对方,你对其他一切知之甚少。不要因为局部而否定整体,每家公司都有自己的问题,我们是选择一个适合自己的平台,不是扮演企业的道德和伦理的警察,用自己的三观来判断一家公司的好坏,这很幼稚,所谓“三观正”其实是个简称啦,全称是:“三观正好和我一样”。08:如果真的想好好锻炼自己的能力,那一开始就不要先去环境特别稳定、管理特别健全的公司。我们以HR来举例,现在人力资源工作在一些超大型的企业里,已经分工的非常细,某些环节跟工厂的流水线差不多,流水线一多,就会让HR学习能力不够强,学习速度不够快,影响了个人发展。倘若你已经在超大公司的内部工作,那也尽量选择有挑战的事业部。09:求职受挫,简历被虐,面试碰壁… …这些都不是你可以气馁的理由。求职中的挫折在工作挑战面前有时候都不值得一提,失败的场景以后还会经常遇到,所以你还是提前让自己内心坚强一点,别总玻璃心,没人同情你的脆弱内心。受挫之余,抓紧学习,在别人玩的时候你在偷偷练级,这才是你应该做的事情。10:没有什么企业或单位是完美的,没有缺陷的,每个公司都会有一些自己的问题,就算公司很好,你也有很大概率会遇到一些不那么好的同事,上司或者合作伙伴。你不可避免会和自己不喜欢的人一起共事,但重要的是你的耐心,有耐心的人和任何人都能配合好工作,没耐心的人半年就换一份工作。推荐一看BAT—最新iOS面试题总结iOS面试题大全(附答案)参考原文地址

March 9, 2019 · 1 min · jiezi

iOS开发—音视频入门学习必看

音视频学习从零到整–(2)音视频学习从零到整–(3)音视频学习从零到整–(4)音视频学习从零到整–(5)音视频学习从零到整–(6)音视频学习从零到整–(7)一.音频基础复习1.1 声音的产生相对于视频,可观察这个现象.音频在学习过程,就缺乏了想象的空间.但是如果从原理出发,就不会那么难了.声音是什么?声音是波,靠物体的振动产生1.2 声波的3要素声波的三要素,是频率,振幅,波形.频率代表音阶的高低,振幅代表响度,波形则代表音色.频率越高,波长就会越短.而低频声响的波长则较长.所以这样的声音更容易绕过障碍物,能量衰减就越小.声音就会传播的越远.响度,就是能量大小的反馈.用不同的力度敲打桌面,声音的大小势必发生变换.在生活中,我们用分贝描述声音的响度.==小贴士==分贝(decibel),是度量声音的强度单位,常用dB表示.是由美国发明家亚历山大.格雷厄姆.贝尔 名字命名的.长期在夜晚接受50 分贝的噪音, 容易导致心血管疾病; 55 分贝, 会对儿童学习产生负面影响; 60分贝, 让人从睡梦中惊醒; 70 分贝,心肌梗死的发病率增加30%左右; 超过110 分贝, 可能导致永久性听力损伤.音色,在同样的频率和响度下,不同的物体发出的声音不一样.比如钢琴和古筝声音就完全不同.波形的形状决定了声音的音色.因为不同的介质所产生的波形不同.就会产生不一样的音色.1.3 声音传播声音的发生,来源于振动.人类说话,从声带振动发生声音之后,经过口腔,颅腔等局部区域的反射,在经过空气传播到别人耳朵中.这是我们说话到听到的过程.声音的传播,可以通过空气,液体,固定传播.介质不同,会影响声音的传播速度.吸音棉:通过声音反射而产生的嘈杂感,吸音材料选择使用可以衰减入射音源的反射能量,从而对原有声音的保真效果.比如录音棚墙壁上就会使用吸音材质隔音:主要解决声音穿透而降低主体空间的吵闹感,隔音棉材质可以衰减入射声音的透射能量.从而达到主体空间安静状态,比如KTV墙壁上就会安装隔音棉材料.二.数字音频2.1 模拟信号数字化过程将模拟信号转换为数字信号的过程,分别是采样,量化和编码.音频采样对模型信号进行采样,采样可以理解为在时间轴上对信号进行数字化.而,根据奈斯特定理(采样定理),按比声音最高频率高2倍以上的频率对声音进行采样.这个过程称为AD转换.比如,前面提到高质量音频信号,其频率范围是20Hz-20KHz.所以采样频率一般是44.1KHz.这样可以保证采样声音达到20KHz也能被数字化.而且经过数字化处理后的声音,音质也不会降低.44.1KHZ,指的是1秒会采样44100次奈斯特定理(采样定理) 资料量化量化,指的是在幅度轴上对信号进行数字化.简单的说,就是声音波形的数据是多少位的二进制数据,通常用bit做单位.比如16比特的二进制信号来表示声音的一个采样.它的取值范围[-32768,32767].一共有65536个值.如16bit、24bit。16bit量化级记录声音的数据是用16位的二进制数,因此,量化级也是数字声音质量的重要指标。我们形容数字声音的质量,通常就描述为24bit(量化级)、48KHz采样,比如标准CD音乐的质量就是16bit、44.1KHz采样.既然每个量化都是一个采样,那么声音这么多采样,该如何将这些数据存储起来?编码什么叫编码?按照一定格式记录采样和量化后的数据.音频编码的格式有很多种,而通常所说的音频裸数据指的是脉冲编码调制(PCM)数据.如果想要描述一份PCM数据,需要从如下几个方向出发:量化格式(sampleFormat)采样率(sampleRate)声道数(channel)举例:以CD音质为例,量化格式为16bite,采样率为44100,声道数为2.这些信息描述CD音质.那么可以CD音质数据,比特率是多少?44100 * 16 * 2 = 1378.125kbps那么一分钟的,这类CD音质数据需要占用多少存储空间?1378.125 * 60 /8/1024 = 10.09MB如果sampleFormat更加精确或者sampleRate更加密集,那么所占的存储空间就会越大,同时能够描述的声音细节就会更加精确.存储在这些二进制数据即可理解为将模型信号转化为数字信号.那么转为数字信号之后,就可以对这些数据进行存储播放复制获取其他任何操作.推荐文集* 抖音效果实现* BAT—最新iOS面试题总结* iOS面试题大全(附答案)原文作者:集才华美貌于一身的—C姐

March 7, 2019 · 1 min · jiezi

有了这些你们团队的代码也很规范

最近重构项目组件,看到项目中存在一些命名和方法分块方面存在一些问题,结合平时经验和 Apple官方代码规范 在此整理出 iOS 工程规范。提出第一个版本,如果后期觉得有不完善的地方,继续提出来不断完善,文档在此记录的目的就是为了大家的代码可读性较好,后来的人或者团队里面的其他人看到代码可以不会因为代码风格和可读性上面造成较大时间的开销。软件的生命周期贯穿产品的开发,测试,生产,用户使用,版本升级和后期维护等过程,只有易读,易维护的软件代码才具有生命力。一些原则长的,描述性的方法和变量命名是好的。不要使用简写,除非是一些大家都知道的场景比如 VIP。不要使用 bgView,推荐使用 backgroundView见名知意。含义清楚,做好不加注释代码自我表述能力强。(前提是代码足够规范)不要过分追求技巧,降低代码可读性删除没必要的代码。比如我们新建一个控制器,里面会有一些不会用到的代码,或者注释起来的代码,如果这些代码不需要,那就删除它,留着偷懒吗?下次需要自己手写在方法内部不要重复计算某个值,适当的情况下可以将计算结果缓存起来尽量减少单例的使用。提供一个统一的数据管理入口,不管是 MVC、MVVM、MVP 模块内提供一个统一的数据管理入口会使得代码变得更容易管理和维护。除了 .m 文件中方法,其他的地方"{“不需要另起一行。- (void)getGooodsList{ // …}- (void)doHomework{ if (self.hungry) { return; } if (self.thirsty) { return; } if (self.tired) { return; } papapa.then.over;}变量一个变量最好只有一个作用,切勿为了节省代码行数,觉得一个变量可以做多个用途。(单一原则)方法内部如果有局部变量,那么局部变量应该靠近在使用的地方,而不是全部在顶部声明全部的局部变量。运算符1元运算符和变量之间不需要空格。例如:++n2元运算符与变量之间需要空格隔开。例如: containerWidth = 0.3 * Screen_Width当有多个运算符的时候需要使用括号来明确正确的顺序,可读性较好。例如: 2 << (1 + 2 * 3 - 4)条件表达式当有条件过多、过长的时候需要换行,为了代码看起来整齐些//goodif (condition1() && condition2() && condition3() && condition4()) { // Do something}//badif (condition1() && condition2() && condition3() && condition4()) { // Do something }在一个代码块里面有个可能的情况时善于使用 return 来结束异常的情况。- (void)doHomework{ if (self.hungry) { return; } if (self.thirsty) { return; } if (self.tired) { return; } papapa.then.over;}每个分支的实现都必须使用 {} 包含。// badif (self.hungry) self.eat() // goodif (self.hungry) { self.eat()}条件判断的时候应该是变量在左,条件在右。 if ( currentCursor == 2 ) { //… }switch 语句后面的每个分支都需要用大括号括起来。switch 语句后面的 default 分支必须存在,除非是在对枚举进行 switch。switch (menuType) { case menuTypeLeft: { // … break; } case menuTypeRight: { // … break; } case menuTypeTop: { // … break; } case menuTypeBottom: { // … break; }}类名大写驼峰式命名。每个单词首字母大写。比如「申请记录控制器」ApplyRecordsViewController每个类型的命名以该类型结尾。ViewController:使用 ViewController 结尾。例子:ApplyRecordsViewControllerView:使用 View 结尾。例子:分界线:boundaryViewNSArray:使用 s 结尾。比如商品分类数据源。categoriesUITableViewCell:使用 Cell 结尾。比如 MyProfileCellProtocol:使用 Delegate 或者 Datasource 结尾。比如 XQScanViewDelegateTool:工具类代理类:DelegateService 类:Service类的注释有时候我们需要为我们创建的类设置一些注释。我们可以在类的下面添加。枚举枚举的命名和类的命名相近。typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) { UIControlContentVerticalAlignmentCenter = 0, UIControlContentVerticalAlignmentTop = 1, UIControlContentVerticalAlignmentBottom = 2, UIControlContentVerticalAlignmentFill = 3,};宏全部大写,单词与单词之间用 _ 连接。以 K 开头。后面遵循大写驼峰命名。「不带参数」#define HOME_PAGE_DID_SCROLL @“com.xq.home.page.tableview.did.scroll”#define KHomePageDidScroll @“com.xq.home.page.tableview.did.scroll"属性书写规则,基本上就是 @property 之后空一格,括号,里面的 线程修饰词、内存修饰词、读写修饰词,空一格 类 对象名称根据不同的场景选择合适的修饰符。@property (nonatomic, strong) UITableView *tableView;@property (nonatomic, assign, readonly) BOOL loading; @property (nonatomic, weak) id<#delegate#> delegate;@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);单例单例适合全局管理状态或者事件的场景。一旦创建,对象的指针保存在静态区,单例对象在堆内存中分配的内存空间只有程序销毁的时候才会释放。基于这种特点,那么我们类似 UIApplication 对象,需要全局访问唯一一个对象的情况才适合单例,或者访问频次较高的情况。我们的功能模块的生命周期肯定小于 App 的生命周期,如果多个单例对象的话,势必 App 的开销会很大,糟糕的情况系统会杀死 App。如果觉得非要用单例比较好,那么注意需要在合适的场合 tearDown 掉。单例的使用场景概括如下:控制资源的使用,通过线程同步来控制资源的并发访问。控制实例的产生,以达到节约资源的目的。控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。+ (instancetype)sharedInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //because has rewrited allocWithZone use NULL avoid endless loop lol. _sharedInstance = [[super allocWithZone:NULL] init]; }); return _sharedInstance;}+ (id)allocWithZone:(struct _NSZone *)zone{ return [TestNSObject sharedInstance];}+ (instancetype)alloc{ return [TestNSObject sharedInstance];}- (id)copy{ return self;}- (id)mutableCopy{ return self;}- (id)copyWithZone:(struct _NSZone *)zone{ return self;}私有变量推荐以 _ 开头,写在 .m 文件中。例如 NSString * _somePrivateVariable代理方法类的实例必须作为方法的参数之一。对于一些连续的状态的,可以加一些 will(将要)、did(已经)以类的名称开头- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;方法方法与方法之间间隔一行大量的方法尽量要以组的形式放在一起,比如生命周期函数、公有方法、私有方法、setter && getter、代理方法..方法最后面的括号需要另起一行。遵循 Apple 的规范对于其他场景的括号,括号不需要单独换行。比如 if 后面的括号。如果方法参数过多过长,建议多行书写。用冒号进行对齐。一个方法内的代码最好保持在50行以内,一般经验来看如果一个方法里面的代码行数过多,代码的阅读体验就很差(别问为什么,做过重构代码行数很长的人都有类似的心情)一个函数只做一个事情,做到单一原则。所有的类、方法设计好后就可以类似搭积木一样实现一个系统。对于有返回值的函数,且函数内有分支情况。确保每个分支都有返回值。函数如果有多个参数,外部传入的参数需要检验参数的非空、数据类型的合法性,参数错误做一些措施:立即返回、断言。多个函数如果有逻辑重复的代码,建议将重复的部分抽取出来,成为独立的函数进行调用- (instancetype)init{ self = [super init]; if (self) { <#statements#> } return self;}- (void)doHomework:(NSString *)name period:(NSInteger)second score:(NSInteger)score;方法如果有多个参数的情况下需要注意是否需要介词和连词。很多时候在不知道如何抉择测时候思考下苹果的一些 API 的方法命名。//good- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;//bad- (instancetype)initWithAge:(NSInteger)age andName:(NSString *)name;- (void)tableView:(UITableView *)tableView :(NSIndexPath )indexPath;.m 文件中的私有方法需要在顶部进行声明方法组之间也有个顺序问题。在文件最顶部实现属性的声明、私有方法的声明(很多人省去这一步,问题不大,但是蛮多第三方的库都写了,看起来还是会很方便,建议书写)。在生命周期的方法里面,比如 viewDidLoad 里面只做界面的添加,而不是做界面的初始化,所有的 view 初始化建议放在 getter 里面去做。往往 view 的初始化的代码长度会比较长、且一般会有多个 view 所以 getter 和 setter 一般建议放在最下面,这样子顶部就可以很清楚的看到代码的主要逻辑。所有button、gestureRecognizer 的响应事件都放在这个区域里面,不要到处乱放。文件基本上就是#import “ViewController.h”/ViewController//View&&Util//model//NetWork InterFace//Vender/@interface ViewController ()@end@implementation ViewController#pragma mark - life cycle- (void)viewWillAppear:(BOOL)animated{ [super viewDidAppear:animated];}- (void)viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; }- (void)viewDidLoad{ [super viewDidLoad]; self.title = @“标准模版”;}- (void)viewWillDisappear:(BOOL)animated{ [super viewDidAppear:animated]; }- (void)viewDidDisappear:(BOOL)animated{ [super viewDidAppear:animated]; }- (void)dealloc{ NSLog(@"%s”,func);}#pragma mark - public Method#pragma mark - private method#pragma mark - event response#pragma mark - UITableViewDelegate#pragma mark - UITableViewDataSource//…(多个代理方法依次往下写)#pragma mark - getters and setters@end图片资源单个文件的命名 文件资源的命名也需要一定的规范,形式为:功能模块名_类别_功能_状态@nx.png Setting_Button_search_selected@2x.png、Setting_Button_search_selected@3x.png Setting_Button_search_unselected@2x.png、Setting_Button_search_unselected@3x.png资源的文件夹命名最好也参考 App 按照功能模块建立对应的实体文件夹目录,最后到对应的目录下添加相应的资源文件。注释对于类的注释写在当前类文件的顶部对于属性的注释需要写在属性后面的地方。 //<userId/对于 .h 文件中方法的注释,一律按快捷键 command+option+/。三个快捷键解决。按需在旁边对方法进行说明解释、返回值、参数的说明和解释对于 .m 文件中的方法的注释,在方法的旁边添加 //。注释符和注释内容需要间隔一个空格。 例如: // fetch goods list版本规范采用 A.B.C 三位数字命名,比如:1.0.2,当有更新的情况下按照下面的依据版本号右说明对齐标题示例A.b.c属于重大内容的更新1.0.2 -> 2.0.0a.B.c属于小部分内容的更新1.0.2 -> 1.1.1a.b.C属于补丁更新1.0.2 -> 1.0.3改进我们知道了平时在使用 Xcode 开发的过程中使用的系统提供的代码块所在的地址和新建控制器、模型、view等的文件模版的存放文件夹地址后,我们就可以设想下我们是否可以定制自己团队风格的控制器模版、是否可以打造和维护自己团队的高频使用的代码块?答案是可以的。Xcode 代码块的存放地址:~/Library/Developer/Xcode/UserData/CodeSnippetsXcode 文件模版的存放地址:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/代码块的改造我们可以将属性、控制器生命周期方法、单例构造一个对象的方法、代理方法、block、GCD、UITableView 懒加载、UITableViewCell 注册、UITableView 代理方法的实现、UICollectionVIew 懒加载、UICollectionVIewCell 注册、UICollectionView 的代理方法实现等等组织为 codesnippets思考封装好 codesnippets 之后团队除了你编写这个项目的人如何使用?如何知道是否有这个代码块?方案:先在团队内召开代码规范会议,大家都统一知道这个事情在。之后大家共同维护 codesnippets。用法见下属性:通过 Property_类型 开头,回车键自动补全。比如 Strong 类型,编写代码通过 Property_Strong 回车键自动补全成如下格式@property (nonatomic, strong) <#Class#> *<#object#>;方法:以 Method_关键词 回车键确认,自动补全。比如 Method_UIScrollViewDelegate 回车键自动补全成 如下格式#pragma mark - UIScrollViewDelegate- (void)scrollViewDidScroll:(UIScrollView *)scrollView {}各种常见的 Mark:以 Mark_关键词 回车确认,自动补全。比如 Method_MethodsGroup 回车键自动补全成 如下格式#pragma mark - life cycle #pragma mark - public Method #pragma mark - private method #pragma mark - event response #pragma mark - UITableViewDelegate #pragma mark - UITableViewDataSource #pragma mark - getters and setters- 封装好 codesnippets 之后团队内如何统一?想到一个方案,可以将团队内的 codesnippets 共享到 git,团队内的其他成员再从云端拉取同步。这样的话团队内的每个成员都可以使用最新的 codesnippets 来编码。编写 shell 脚本。几个关键步骤:1. 给系统文件夹授权2. 在脚本所在文件夹新建存放代码块的文件夹3. 将系统文件夹下面的代码块复制到步骤2创建的文件夹下面4. 将当前的所有文件提交到 Git 仓库## 文件模版的改造我们观察系统文件模版的特点,和在 Xcode 新建文件模版对应。所以我们新建 Custom 文件夹,将系统 Source 文件夹下面的 Cocoa Touch Class.xctemplate 复制到 Custom 文件夹下。重命名为我们需要的名字,我这里以“Power”为例进入 PowerViewController.xctemplate/PowerViewControllerObjective-C修改 ___FILEBASENAME___.h 和 ___FILEBASENAME___.m 文件内容在替换 .h 文件内容的时候后面改为 UIViewController,不然其他开发者新建文件模版的时候出现的不是 UIViewController 而是我们的 PowerViewController 修改 TemplateInfo.plist思考:- 如何使用商量好一个标识(“Power”)。比如我新建了单例、控制器、Model、UIView4个模版,都以为 Power 开头。- 如何共享以 shell 脚本为工具。使用脚本将 git 云端的代码模版同步到本地 Xcode 文件夹对应的位置就可以使用了。关键步骤:1. git clone 代码到脚本所在文件夹2. 进入存放 codesnippets 的文件夹将内容复制到系统存放 codesnippets 的地方3. 进入存放 file template 的文件夹将内容复制到系统存放 file template 的地方## 使用./syncSnippets.sh // 同步git云端代码块和文件模版到本地./uploadMySnippets.sh //将本地的代码块和文件模版同步到云端## PS目前新建了大概30个代码段和4个类文件模版(UIViewController控制器带有方法组、模型、线程安全的单例模版、带有布局方法的UIView模版)shell 脚本基本有每个函数和关键步骤的代码注释,想学习 shell 的人可以看看代码。代码传送门———- ...

March 4, 2019 · 3 min · jiezi

腾讯—最新iOS面试题总结

关于面试题,可能没那么多时间来总结答案,有什么需要讨论的地方欢迎大家指教。主要记录一下准备过程,和面试的一些总结,希望能帮助到正在面试或者将要面试的同学吧。腾讯一面1、介绍一下实习的项目,任务分工,做了哪些工作?介绍实习内容2、网络相关的:项目里面使用到什么网络库,用过ASIHTTP库吗3、断点续传怎么实现?需要设置什么?4、在杭州HTTP请求服务器响应快,可能离服务器距离近,而在深圳访问就很慢很慢,会是什么原因?如果用户投诉,怎么分析这个问题?5、HTTP请求的哪些方法用过?什么时候选择get、post、put?6、TCP建立连接的过程,断开连接的过程,为什么是四次握手?7、项目里面的数据存储都用了哪些?知道iOS里面有哪些数据存储方法?什么时候该用哪些方法存储?8、MVVM如何实现绑定9、block和通知的区别,分别适用什么场景10、算法。连续问了好几个,都是数组,层层递进的,但是我忘了,只记得最后是找出数组11、中重复的数字12、进程和线程的区别13、程序在运行时操作系统除了分配内存空间还有什么14、进程间通信的方式15、如何检测应用是否卡顿16、发布出去的版本,怎么收集crash日志?不使用bugly等第三方平台或者这些第三方平台是怎么收集crash日志的?17、在block里面使用_property会造成循环引用吗?怎么解决?除了使用self->_property,可以使用valueforkey来访问吗 在block里面可以修改它的值吗setvalueforkey?可以修改它的值,可以用valueforkey来解决,显式的的使用self,block外先持有self的弱引用。二面1、OC中对象的结构2、多态3、Ping是什么协议4、知道MTU吗5、ARC和MRC的本质区别是什么?6、NSThread,GCD,NSOperation相关的。开启一条线程的方法?线程可以取消吗?7、子线程中调用connection方法,为什么不回调?因为没有加入runloop,执行完任务就销毁了,所以没有回调。8、MVC和MVVM的区别9、了解哪些设计模式10、存一个通讯录,包括增删改查,用什么数据结构11、autorelease变量什么时候释放?手动添加的是大括号结束的时候释放,系统自动释放是在12、当前runloop循环结束的时候13、那子线程中的autorelease变量什么时候释放?14、子线程里面,需要加autoreleasepool吗15、GCD和NSOperation的区别?16、项目里面遇到过死锁吗?怎么解决?数据库访问本来就是线程安全的,不会造成死锁啊。什么是死锁?17、Viewcontroller的生命周期?18、在init方法里面,设置背景颜色,会生效吗 会生效。为什么会?19、WWDC2016公布了哪些新特性?对苹果系列的最新特性有关注吗20、看过哪些源码,讲讲思路21、两个链表找第一个相同结点22、字符串旋转23、找链表的倒数第k个结点24、把一个链表比某个值大的放在左边,比它小的放在右边25、二叉树的中序遍历,非递归更多:iOS面试题(附答案)另外附上一份收集的各大厂面试题(附答案) ! 要的可加iOS高级技术群:624212887,群文件直接获取

February 28, 2019 · 1 min · jiezi

iOS数字倍数动画

前言一个简单的利用 透明度和 缩放 实现的 数字倍数动画实现思路上代码 看比较清晰// 数字跳动动画- (void)labelDanceAnimation:(NSTimeInterval)duration { //透明度 CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@“opacity”]; opacityAnimation.duration = 0.4 * duration; opacityAnimation.fromValue = @0.f; opacityAnimation.toValue = @1.f; //缩放 CAKeyframeAnimation *scaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@“transform.scale”]; scaleAnimation.duration = duration; scaleAnimation.values = @[@3.f, @1.f, @1.2f, @1.f]; scaleAnimation.keyTimes = @[@0.f, @0.16f, @0.28f, @0.4f]; scaleAnimation.removedOnCompletion = YES; scaleAnimation.fillMode = kCAFillModeForwards; CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; animationGroup.animations = @[opacityAnimation, scaleAnimation]; animationGroup.duration = duration; animationGroup.removedOnCompletion = YES; animationGroup.fillMode = kCAFillModeForwards; [self.comboLabel.layer addAnimation:animationGroup forKey:@“kComboAnimationKey”];}利用一个透明度从 0 ~ 1之间的alpha,然后缩放 之后加到动画组实现一下就好了切记动画完成最好移除 否则可能引起动画内存问题这里设置斜体字体self.comboLabel.font = [UIFont fontWithName:@“AvenirNext-BoldItalic” size:50];看着比较明显最后按钮点击的时候调用- (IBAction)clickAction:(UIButton *)sender { self.danceCount++; [self labelDanceAnimation:0.4]; self.comboLabel.text = [NSString stringWithFormat:@"+ %tu",self.danceCount];}如果实现 dozen动画的话很简单, danceCount % 10 == 0 求模就行了.总结这个动画比较适合 有些直播场景的点击操作计数相关.iOS数字倍数动画Demo获取,可加iOS开发交流群:624212887,获取Demo,以及更多iOS技术资料 ...

February 27, 2019 · 1 min · jiezi

iOS | NSProxy

Objective-C作为一种动态消息型语言,其机制不同于Java ,C#等编译型语言.它将数据类型的确定等工作推迟到了运行时期来执行,并且它调用方法的方式实质是像对象发送消息,根据selector在对象的本类以及父类中的方法列表进行查找,如果都找不到就会启动消息转发机制.回到正题,这个话题我想谈下OC的单继承原则.OC确实是只能单继承的语言,但是基于运行时的机制,却有一种方法让它来实现一下"伪多继承".就是利用NSProxy这个类.NSProxy是和NSObject同级的一个类,可以说它是一个虚拟类,它只是实现了<NSObject>的协议.它的作用有点类似于一个复制类,有人曾经笑谈它是卡卡西的复制忍术,想想其实也挺贴切的,其实原理确实如此.过程:用一个继承于NSProxy的子类,在它内部实现一些方法,暴露一个公开方法transform,这个方法是使它变身的关键.然后它变身之后可以对那些对象发送消息,并且可以在内部拦截消息的内容并修改.可以说,几乎可以变身成为任何对象.直接上个代码来展示下JanProxy.h#import <Foundation/Foundation.h>@interface JanProxy : NSProxy- (void)transformObjc:(NSObject *)objc;@endJanProxy.m#import “JanProxy.h”@interface JanProxy ()@property(nonatomic,strong)NSObject *objc;@end@implementation JanProxy- (void)transformObjc:(NSObject *)objc{ //复制对象 self.objc = objc;}//2.有了方法签名之后就会调用方法实现- (void)forwardInvocation:(NSInvocation *)invocation{ if (self.objc) { //拦截方法的执行者为复制的对象 [invocation setTarget:self.objc]; if ([self.objc isKindOfClass:[NSClassFromString(@“Cat”) class]]) { //拦截参数 Argument:表示的是方法的参数 index:表示的是方法参数的下标 NSString *str = @“拦截消息”; [invocation setArgument:&str atIndex:2]; } //开始调用方法 [invocation invoke]; } }//1.查询该方法的方法签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{ NSMethodSignature *signature = nil; if ([self.objc methodSignatureForSelector:sel]) { signature = [self.objc methodSignatureForSelector:sel]; } else { signature = [super methodSignatureForSelector:sel]; } return signature;}@end使用方法 Dog *dog = [[Dog alloc]init]; //OC中方法的调用本质上是给这个对象发送一个消息 Cat *cat = [[Cat alloc] init]; //开始复制拦截方法 JanProxy *proxy = [JanProxy alloc]; //开始变身成猫 [proxy transformObjc:cat]; //开始调猫的方法 [proxy performSelector:@selector(eat:) withObject:@“猫发出消息”]; //开始变身成狗 [proxy transformObjc:Dog]; //开始调用学生的方法 [proxy performSelector:@selector(shut)];最后的结果发现没有,猫发出消息已经被子类的内部拦截并且做出了修改.总结OC中存在这么一个默默无闻的类NSProxy,填补了"多继承"这个空白区. ...

February 26, 2019 · 1 min · jiezi

Block中可以修改全局变量,全局静态变量,局部静态变量吗?

原文:iOS面试题大全可以.深入研究Block捕获外部变量和__block实现原理全局变量和静态全局变量的值改变,以及它们被Block捕获进去,因为是全局的,作用域很广静态变量和自动变量,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量Block就分为以下3种_NSConcreteStackBlock:只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。 StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了,是不持有对象的_NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上_NSConcreteMallocBlock:有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制,是持有对象的_NSConcreteGlobalBlock:没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束,也不持有对象ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上ARC下,Block中引用id类型的数据有没有__block都一样都是retain,而对于基础变量而言,没有的话无法修改变量值,有的话就是修改其结构体令其内部的forwarding指针指向拷贝后的地址达到值的修改

February 25, 2019 · 1 min · jiezi

如何优化 App 的启动耗时?

原文:iOS面试题大全iOS 的 App 启动主要分为以下步骤:打开 App,系统内核进行初始化跳转到 dyld 执行。这个过程包括这些步骤:1)分配虚拟内存空间;2)fork 进程;3)加载 MachO (自身所有的可执行 MachO 文件的集合)到进程空间;4)加载动态链接器 dyld 并将控制权交给 dyld 处理。在这个过程中内核会产生 ASLR(Address space layout randomization) 随机数值,这个值用于加载的 MachO 起始地址在内存中的偏移,随机的地址可防止 MachO 代码扫描并被 hack,提升安全性。通过 ASLR 虽然可随机化各内存区基地址,但无法将程序内的代码段和数据段随机化,如果绕过(bypass) ASLR 依然可进行篡改,就需要结合 PIE(Position Independent Executable) 共同使用。与之相似的还有 PIC(Position Independent Code),位置无关代码,作用于共享库代码。PIE/PIC 技术需要在编译阶段开启。顾名思义,PIC 可将程序代码装载到任意地址,这样就内部的指针不能靠固定的绝对地址访问,而通过相对地址指令如 adrp 来获取代码和数据。进入 dyld 动态链接器,它负责将一个 App 处理为一个可运行的状态,包含:加载 MachO 的依赖库(这些依赖库也是 MachO 格式的文件)。dyld 从可执行 MachO 文件的依赖开始, 递归加载所有依赖的动态库。 动态库包括:iOS 中用到的所有系统动态库:加载 OC runtime 方法的 libobjc,系统级别的 libSystem(例如 libdispatch(GCD) 和 libsystem_blocks(Block));其他 App 自己的动态库。根据 Apple 的描述,大部分 App 所加载的库在 100~400 个。不过 iOS 系统库已经被特殊优化过,如提前加入共享缓存,提前做好地址修正等。Fix-ups(地址修正),包括 rebasing 和 binding 等。ASLR + PIE 技术增强了程序的安全性,使得依赖固定地址进行攻击的方法失效,但也增加了程序自身的复杂度,MachO 文件的 rebase 和 bind info 等部分以及启动时的 fix-ups 地址修正阶段就是配合它而产生的。ObjC 环境配置。经过了 MachO 程序和依赖库的加载以及地址修正之后,dyld 所做的大部分事情已经完成了。在这一阶段,dyld 开始对主程序的依赖库进行初始化工作,而初始化的执行部分会回调到依赖库内部执行,如 ObjC 的运行时环境所在的 libobjc.A.dylib 以及 libdispatch.dylib 等。ObjC Setup 的过程,主要是对 ObjC 数据进行关联注册:1)dyld 将主程序 MachO 基址指针和包含的 ObjC 相关类信息传递到 libobjc;2)ObjC Runtime 从 __DATA 段中获取 ObjC 类信息,由于 ObjC 是动态语言,可以通过类名获取其实例,所以 Runtime 维护了一个映射所有类的全局类名表。当加载的数据包含了类的定义,类的名字就需要注册到全局表中;3)获取 protocol、category 等类相关属性并与对应类进行关联;4)ObjC 的调用都是基于 selector 的,所以需要对 selector 全局唯一性进行处理。以上步骤由 dyld 启动 libSystem.dylib 统一对基础库进行调用执行,这里面就包含了 libobjc 的 Runtime,同时 Runtime 会在 dyld 绑定回调,当 dyld 处理完相关数据后就会调用 ObjC Runtime 执行 Setup 工作。执行各模块初始化器。从这一步就开始接近上(业务)层:1)通过 ObjC Runtime 在 dyld 注册的通知,当 MachO 镜像准备完毕后,dyld 会回调到 ObjC 中执行 +load() 方法,包括以下步骤:a)获取所有 non-lazy class 列表;b)按继承以及 category 的顺序将类排入待加载列表;c)对待加载列表中的类进行方法判断并调用 +load() 方法。2)执行 C/C++ 初始化构造器,如通过 attribute((constructor)) 注解的函数。3)如果包含 C++,则 dyld 同样会回调到 libc++ 库中对全局静态变量、隐式初始化等进行调用。查找并跳转到 main() 函数入口。到了最后,dyld 回到 Load command,找到 LC_MAIN,拿到 entryoff 再加上 MachO 在内存的加载首地址(首地址就是内核传来的 slide 偏移)就得到了 main() 的入口地址,从而进入我们显式的程序逻辑。进入 main() -> UIApplicationMain -> 初始化回调 -> 显示UI。iOS 的 App 启动时长大概可以这样计算:t(App 总启动时间) = t1(main 调用之前的加载时间) + t2(main 调用之后的加载时间)。t1 = 系统 dylib(动态链接库)和自身 App 可执行文件的加载。t2 = main 方法执行之后到 AppDelegate 类中的 application:didFinishLaunchingWithOptions:方法执行结束前这段时间,主要是构建第一个界面,并完成渲染展示。在 t1 阶段加快 App 启动的建议:尽量使用静态库,减少动态库的使用,动态链接比较耗时。如果要用动态库,尽量将多个 dylib 动态库合并成一个。尽量避免对系统库使用 optional linking,如果 App 用到的系统库在你所有支持的系统版本上都有,就设置为 required,因为 optional 会有些额外的检查。减少 Objective-C Class、Selector、Category 的数量。可以合并或者删减一些 OC 类。删减一些无用的静态变量,删减没有被调用到或者已经废弃的方法。将不必须在 +load 中做的事情尽量挪到 +initialize 中,+initialize 是在第一次初始化这个类之前被调用,+load 在加载类的时候就被调用。尽量将 +load 里的代码延后调用。尽量不要用 C++ 虚函数,创建虚函数表有开销。不要使用 atribute((constructor)) 将方法显式标记为初始化器,而是让初始化方法调用时才执行。比如使用 dispatch_once(),pthread_once() 或 std::once()。在初始化方法中不调用 dlopen(),dlopen() 有性能和死锁的可能性。在初始化方法中不创建线程。在 t2 阶段加快 App 启动的建议:尽量不要使用 xib/storyboard,而是用纯代码作为首页 UI。如果要用 xib/storyboard,不要在 xib/storyboard 中存放太多的视图。对 application:didFinishLaunchingWithOptions: 里的任务尽量延迟加载或懒加载。不要在 NSUserDefaults 中存放太多的数据,NSUserDefaults 是一个 plist 文件,plist 文件被反序列化一次。避免在启动时打印过多的 log。少用 NSLog,因为每一次 NSLog 的调用都会创建一个新的 NSCalendar 实例。每一段 SQLite 语句都是一个段被编译的程序,调用 sqlite3_prepare 将编译 SQLite 查询到字节码,使用 sqlite_bind_int 绑定参数到 SQLite 语句。为了防止使用 GCD 创建过多的线程,解决方法是创建串行队列, 或者使用带有最大并发数限制的 NSOperationQueue。线程安全:UIKit只能在主线程执行,除了 UIGraphics、UIBezierPath 之外,UIImage、CG、CA、Foundation 都不能从两个线程同时访问。不要在主线程执行磁盘、网络、Lock 或者 dispatch_sync、发送消息给其他线程等操作。 ...

February 21, 2019 · 2 min · jiezi

iOS App卡顿监控(Freezing/Lag)

iOS App卡顿监控(Freezing/Lag)笔记(写在前面):关于应用的性能监控,需要从多方面进行综合考虑,此处仅从其中一个方面,进行学习研究。如何判断主线程卡顿:监测NSRunLoop耗时情况。NSRunLoop的调用主要在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间,以及kCFRunLoopAfterWaiting之后。因此,若是发现这个两个时间内耗时过长,就可以判定此时主线程出现卡顿情况。一、监控NSRunLoop状态变化使用CFRunLoopObserverRef,实时获得这些状态值的变化,如下:/// RunLoop状态观察回调static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void info){ <#MyClass#> object = (__bridge <#MyClass#>)info; // 记录状态值 object->activity = activity;}/// 注册RunLoop状态观察- (void)registerRunLoopObserver { CFRunLoopObserverContext context = {0,(__bridge void)self,NULL,NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);}二、RunLoop耗时计算另外开启一个线程,实时计算两个状态区域之间的耗时,是否达到阈值。dispatch_semaphore_t让子线程更及时地获知主线程NSRunLoop状态变化卡顿覆盖范围:多次连续小卡顿、单次长时间卡顿添加计算逻辑,如下:/// RunLoop状态观察回调static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void info){ <#MyClass#> object = (__bridge <#MyClass#>)info; // 记录状态值 object->activity = activity; // 发送信号 dispatch_semaphore_t semaphore = object->semaphore; dispatch_semaphore_signal(semaphore);}/// 注册RunLoop状态观察,并计算是否卡顿- (void) registerRunLoopObserver { CFRunLoopObserverContext context = {0,(__bridge void)self,NULL,NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); // 创建信号 semaphore = dispatch_semaphore_create(0); // 在子线程监控时长 dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (YES) { // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms) long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); if (st != 0) { if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting) { if (++timeoutCount < 5) { continue; } // 发现卡顿 NSLog(@“卡、卡、卡、顿、顿、了”); } } timeoutCount = 0; } });}三、记录卡顿的函数调用目击卡顿现场,记录此时的调用函数信息,作为卡顿证据。此处,使用第三方Crash收集组件PLCrashReporter,它不仅可以收集Crash信息,也可用于实时获取各线程的调用堆栈,使用示例如下:PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];NSData *data = [crashReporter generateLiveReport];PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS];NSLog(@"————\n%@\n————", report);特别注意:PLCrashReporter虽然能提供较为准确的堆栈信息,用于定位问题,特别是使用符号化策略PLCrashReporterSymbolicationStrategyAll时,能够对堆栈信息进行符号化,但会消耗大量资源,需要占用较多时间,导致卡死现象(自测时,耗时超过7s,层多次到10s以上)。不使用符号化策略PLCrashReporterSymbolicationStrategyNone,测试时,平均耗时也接近3s。因此,加入该信息采集,需要特别注意,建议仅在开发调试阶段使用。为了投入线上使用,还需要再想想如何解决该问题。四、上报服务器检测到卡顿,获取到调用堆栈信息,客户端再根据实际情况进行一定程度的过滤处理,将有价值的信息上报服务器。后续对服务器收集到的数据进行分析,定位需要优化的功能逻辑。 ...

February 5, 2019 · 1 min · jiezi

[App探索]JSBox中幽灵触发器的实现原理探索

前言幽灵触发器是钟颖大神的JSBox中的一个功能,在app进程被杀死的情况下,也可以将通知固定在通知栏,即便用户点击清除,也能马上再弹出,永远不消失,除非用户关闭App的通知权限或者卸载App,才可以消失。这个功能确实比较有意思,而且钟颖大神在介绍视频里有提到是目前JSBox独有的,说明实现得非常巧妙,自己研究的话还是很难想到的,非常值得学习,而且当你了解它的实现原理的话,会发现其实可以做很多其他的事情。当某天产品经理对App推送点击率不满意时,可以向她祭出这件大杀器(哈哈,开玩笑的,无线推送这种功能其实苹果很不推荐,因为确实有可能会被一些不良App采用,然后无限推送,让用户反感)。以下内容仅供学习讨论,JSBox是一个很强大的App,有很多值得学习的地方,强烈推荐大家去购买使用。简短的效果视频完整的介绍视频https://weibo.com/tv/v/G79vjv…:1f37179499e39dbc8a7472897b9e056c从2分6秒开始探索历程因为没有可以用来砸壳的越狱手机,而且PP助手也没有JSBox的包,一开始是去搜幽灵触发器,无限通知的实现,发现没找到答案,stackoverflow上的开发者倒是对无限通知比较感兴趣,问答比较多,但是没有人给出答案,基本上也是说因为苹果不希望开发者用这种功能去骚扰用户。所以只能自己阅读通知文档,查资料来尝试实现了。难道是使用时间间隔触发器UNTimeIntervalNotificationTrigger来实现的吗?因为看通知清除了还是一个接一个得出现,很自然就能想到是通过绕过苹果的检测,去改UNTimeIntervalNotificationTrigger的timeInterval属性来实现的,所以写出了一下代码:UNTimeIntervalNotificationTrigger timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1.0f repeats:YES];UNMutableNotificationContent content = [[UNMutableNotificationContent alloc] init];content.title = @“推送标题”;UNNotificationRequest request = [UNNotificationRequest requestWithIdentifier:@“requestIdentifier” content:content trigger:timeTrigger];[center addNotificationRequest:request withCompletionHandler:nil];通过传入创建时间间隔为1s的实际间隔触发器来实现,运行后,第一个通知能正常显示出来,清除第一个通知后,显示第二个通知时,app崩溃了,时间间隔不能小于60s。UserNotificationsDemo[14895:860379] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ’time interval must be at least 60 if repeating’ First throw call stack:(0x1ae2a3ea0 0x1ad475a40 0x1ae1b9c1c 0x1aeca7140 0x1b8738d0c 0x1b8738bdc 0x102d508ac 0x1db487658 0x1dad09a18 0x1dad09720 0x1dad0e8e0 0x1dad0f840 0x1dad0e798 0x1dad13684 0x1db057090 0x1b0cd96e4 0x1030ccdc8 0x1030d0a10 0x1b0d17a9c 0x1b0d17728 0x1b0d17d44 0x1ae2341cc 0x1ae23414c 0x1ae233a30 0x1ae22e8fc 0x1ae22e1cc 0x1b04a5584 0x1db471054 0x102d517f0 0x1adceebb4)libc++abi.dylib: terminating with uncaught exception of type NSExceptiontimeInterval是只读属性,看来苹果早有防范@property (NS_NONATOMIC_IOSONLY, readonly) NSTimeInterval timeInterval;但是这年头,还能活着做iOS开发的谁没还不会用KVC呀,所以很自然得就能想到使用KVC来改UNTimeIntervalNotificationTrigger *timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1.0f repeats:YES];UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];content.title = @“推送标题”;UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@“requestIdentifier” content:content trigger:timeTrigger];[timeTrigger setValue:@1 forKey:@“timeInterval”];[center addNotificationRequest:request withCompletionHandler:nil];而且我打断点看,确实改成功了,但是,很快,当我把第一个通知清除时,手机变成这样了有那么一刻,我心里很慌,我一定好好做人,不去改苹果爸爸的只读属性了。苹果是在显示第二个通知的时候才去判断的,而我们的代码只能控制到将通知请求request添加到UNUserNotificationCenter这一步,所以不太好绕过。难道是使用地点触发器UNLocationNotificationTrigger来实现的吗?UNLocationNotificationTrigger可以通过判断用户进入某一区域,离开某一区域时触发通知,但是我去看了一下设置里面的权限,发现只使用这个功能的时候JSBox并没有请求定位的权限,所以应该不是根据地点触发的。继续阅读文档然后我就去钟颖大神的JSBox社区仔细查看开发者文档,查看关于通知触发相关的api,结果发现不是通过repeats字段,而是通过renew这个字段来决定是否需要重复创建通知的,所以很有可能不是通过时间触发器来实现的,是通过自己写代码去创建一个通知,然后将通知进行发送。在大部分iOS开发同学心中(包括我之前也是这么认为的),普遍都认为当app处于运行状态时,这样的实现方案自然没有问题,因为我们可以获取到通知展示,用户对通知操作的回调。当app处于未运行状态时,除非用户点击通知唤醒app,我们无法获取到操作的回调,但其实在iOS 10以后,苹果公开的UserNotifications框架,允许开发者通过实现UNUserNotificationCenter的代理方法,来处理用户对通知的各种点击操作。具体可以看苹果的这篇文章Handling Notifications and Notification-Related Actions,翻译其中主要的一段:你可以通过实现UNUserNotificationCenter的代理方法,来处理用户对通知的各种点击操作。当用户对通知进行某种操作时,系统会在后台启动你的app并且调用UNUserNotificationCenter的代理对象实现的userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:方法,参数response中会包含用户进行的操作的actionIdentifier,即便是系统定义的通知操作也是一样,当用户对通知点击取消或者点击打开唤醒App,系统也会上报这些操作。核心就是这个方法// The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from application:didFinishLaunchingWithOptions:.- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __OSX_AVAILABLE(10.14) __TVOS_PROHIBITED;所以我就写了一个demo来实现这个功能,核心代码如下:AppDelegate.m- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; [self applyPushNotificationAuthorization:application];//请求发送通知授权 [self addNotificationAction];//添加自定义通知操作扩展 return YES;}//请求发送通知授权- (void)applyPushNotificationAuthorization:(UIApplication *)application{ if (([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0)) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (!error && granted) { NSLog(@“注册成功”); }else{ NSLog(@“注册失败”); } }]; [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { NSLog(@“settings========%@",settings); }]; } else if (([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)){ [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound ) categories:nil]]; } [application registerForRemoteNotifications];}//添加自定义通知操作扩展- (void)addNotificationAction { UNNotificationAction *openAction = [UNNotificationAction actionWithIdentifier:@“NotificationForeverCategory.action.look” title:@“打开App” options:UNNotificationActionOptionForeground]; UNNotificationAction *cancelAction = [UNNotificationAction actionWithIdentifier:@“NotificationForeverCategory.action.cancel” title:@“取消” options:UNNotificationActionOptionDestructive]; UNNotificationCategory *notificationCategory = [UNNotificationCategory categoryWithIdentifier:@“NotificationForeverCategory” actions:@[openAction, cancelAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]; [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:notificationCategory]];}# pragma mark UNUserNotificationCenterDelegate//app处于前台时,通知即将展示时的回调方法,不实现会导致通知显示不了- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{ completionHandler(UNNotificationPresentationOptionBadge| UNNotificationPresentationOptionSound| UNNotificationPresentationOptionAlert);}//app处于后台或者未运行状态时,用户点击操作的回调- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler { [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {//点击系统的清除按钮 UNTimeIntervalNotificationTrigger *timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.0001f repeats:NO]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = @“App探索-NotFound”; content.body = @"[App探索]JSBox中幽灵触发器的实现原理探索”; content.badge = @1; content.categoryIdentifier = @“NotificationForeverCategory”; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:response.notification.request.identifier content:content trigger:timeTrigger]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil]; } completionHandler();}- (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.}- (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.}- (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.}- (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.}- (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.}ViewController.m- (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [button addTarget:self action:@selector(sendNotification) forControlEvents:UIControlEventTouchUpInside]; [button setTitle:@“发送一个3s后显示的通知” forState:UIControlStateNormal]; button.frame = CGRectMake(0, 200, [UIScreen mainScreen].bounds.size.width, 100); [self.view addSubview:button];}//发送一个通知- (void)sendNotification { UNTimeIntervalNotificationTrigger *timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:3.0f repeats:NO]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = @“App探索-NotFound”; content.body = @"[App探索]JSBox中幽灵触发器的实现原理探索"; content.badge = @1; content.categoryIdentifier = @“NotificationForeverCategory”; UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@“requestIdentifier” content:content trigger:timeTrigger]; UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center addNotificationRequest:request withCompletionHandler: nil];}必须在didFinishLaunchingWithOptions的方法返回前设置通知中心的代理,这个文档里面都有提及,大家都知道,但是有两个文档里面未曾提及的难点需要注意:隐藏关卡一 必须给通知添加自定义的通知操作1.必须给通知添加自定义的通知操作,并且给发送的通知指定自定义的通知操作的categoryIdentifier,这样系统在用户对通知进行操作时才会调用这个代理方法,- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler自定义通知操作是用户长按通知,下方弹出的actionSheet,在我们的Demo中,是“打开App”和“取消”两个操作,其实不添加这些自定义操作的话,系统的这些“管理”,”“查看”,“清除”也是有的,但是当用户点击“清除”时,我们的代理方法didReceiveNotificationResponse就不会被调用了,文档里面没有提及这个,我也是试了好久才试出来的。隐藏关卡二 必须使用上一个通知的requestIdentifier当用户点击“清除”按钮时,即便app处于未运行状态,系统也会在后台运行我们的app,并且执行didReceiveNotificationResponse这个代理方法,在这个方法里面我们会创建一个UNNotificationRequest,把他添加到通知中心去,然后通知会展示出来。但是系统好像对于在app正常运行时添加的UNNotificationRequest跟在didReceiveNotificationResponse方法里添加的UNNotificationRequest做了区分,后者在被用户点击“清除”按钮后,app不会收到didReceiveNotificationResponse回调方法,可能系统也是考虑到开发者可能会利用这个机制去实现无限通知的功能。所以我在创建UNNotificationRequest时,使用的identifier是前一个通知的identifier,这也是实现无限通知的最巧妙的地方,可能很多开发者是知道实现这个代理方法来接受用户点击“清除”的回调,然后做一些通知上报,隔一段时间再次发送通知事情,但是再次创建并发送的通知在被点击“清除”时已经不会再执行didReceiveNotificationResponse回调了。 UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:response.notification.request.identifier content:content trigger:timeTrigger];扩展如果我们做的是效率工具类型的App,利用这个功能做一些固定通知之类的功能,如果我们做的是一些资讯类的App,可以做一些不定间隔推送的功能,而不需要每次用户点击“清除”后,将用户操作通过网络请求上报给服务器,然后服务器根据情况给用户发推送。更多的玩法有待我们探索。Demo https://github.com/577528249/...Demo 演示Gif写文章太耗费时间了,可以的话,求大家给我点个关注吧,会定期写原创文章,谢谢了! ...

January 2, 2019 · 3 min · jiezi

iOS导航栏样式方案梳理

1.背景在iOS开发中每个页面都有可能被个性化设计,但如果页面是以push方式进行管理,那么多个视图控制器共享一个导航栏,导航栏的适配显示就是一个问题。因此需基于系统导航进一步调整和修改才能满足需求。本文参考下面两篇博客进行分析梳理。Kenshin Cui’s Blog美团技术团队2.关注点页面样式自定义(包括隐藏或显示导航栏)之后,关注点如下:导航栏内容Title和Item容易编码维护。页面过渡导航栏内容渐变动画(参见系统导航效果)。页面过渡导航栏背景颜色变化不突兀。支持滑动手势pop。3.导航配置导航栏透明self.navigationBar.isTranslucent = true //需要开启半透明self.navigationBar.setBackgroundImage(UIImage(), for: .default)self.navigationBar.shadowImage = UIImage()导航栏隐藏// 导航栏显示(含animated,否则页面有无导航切换可能会突变,在手势pop时最明显) self.navigationController?.setNavigationBarHidden(true, animated: true)导航栏颜色导航栏半透明开启:既然开启半透明一般是想用模糊效果的,因此明显应使用下列第①种:// ① 半透明开启,此种方式设置颜色有明显模糊效果,展开图层树UINavigatuionBar -> background视图 -> UIVisualEffectView -> UIVisualEffectBackdropView, 发现进行UIVisualEffectBackdropView颜色变化(箭头代表内部子视图),但是因为UIVisualEffectView是模糊控制视图,因此会有模糊效果显现出来self.navigationController?.navigationBar.backgroundColor = UIColor.kcRed// ② 半透明开启,此种方式设置颜色没有模糊效果,展开图层树UINavigationBar ->background视图 -> imageView, 发现imageView颜色变化(箭头代表内部子视图)self.navigationController?.navigationBar.setBackgroundImage(UIImage(color:UIColor.kcRed), for: .default)// ③ 半透明开启,此种方式设置颜色有轻微模糊感,但不如第一种那样明显,展开图层树UINavigatuionBar -> background视图 -> UIVisualEffectView -> _UIVisualEffectSubview,发现_UIVisualEffectSubview颜色变化(箭头代表内部子视图),因为UIVisualEffectView视模糊控制视图,因此会有模糊效果显现出来self.navigationController?.navigationBar.barTintColor = UIColor.kcRed导航栏半透明关闭:建议采用第②种// ① 半透明关闭,此种方式不能设置导航栏背景颜色,展开图层树发现设置backgroundcolor仅仅影响UINavigationBar的颜色,但是UINavigationBar有一个background子视图(默认白色)遮盖了设置的颜色self.navigationController?.navigationBar.backgroundColor = UIColor.kcRed// ② 半透明关闭,此种方式可以设置导航栏颜色,展开图层树UINavigationBar ->background视图 -> imageView,发现imageView颜色变化(箭头代表内部子视图)self.navigationController?.navigationBar.setBackgroundImage(UIImage(color:UIColor.kcRed), for: .default)// ③ 半透明关闭,此种方式可以设置导航栏颜色。展开图层树发现是设置UINavigationBar的子视图background的颜色,但根据API语义(barTintColor)明显不是设置背景专属,可能会影响内部子视图颜色,因此一般不建议采用此种方法来设置背景色self.navigationController?.navigationBar.barTintColor = UIColor.kcRed隐藏导航栏下线 // 展开图层树发现黑线是一个高度为0.33的imageView(iphoneX显示),图层树UINavigationBar ->background视图 -> imageView,颜色为透明度0.3的黑色, self.navigationBar.shadowImage = UIImage()3.方案讨论方案一方案说明:用系统导航栏,且导航栏颜色控制仅仅在每个视图控制器viewWillAppear中进行配置,透明导航栏也可以使用颜色控制。当然也可根据需要部分页面隐藏导航栏。存在问题:此方案过于简单,页面过渡和手势滑动时导航栏颜色效果变化突兀。样例:参见KenshinCui博客的名为原始方式的方案(见其博客代码示例的Demo1)。关注点:不满足关注点3,页面过渡导航栏背景颜色变化突兀。方案二方案说明:隐藏系统导航栏,切换不同颜色的导航条则只需要隐藏用这个方法隐藏导航条然后自定义一个UINavigationBar增加到导航条的位置(添加一个假的导航条)。不过这种方式的由于隐藏了导航条,那么侧滑返回手势也会消失。透明导航条直接隐藏导航条。存在问题:①需要自己添加UINavigationBar。②由于隐藏了系统的导航栏,造成侧滑手势丢失。解决方式是重新设置当前控制器的interactivePopGestureRecognizer.delegate=self,但是多次push、pop会出现界面错乱操作失效的问题(解决方式就是在适当的时候禁用侧滑或者禁止手势shouldReceiveTouch)。样例:参见KenshinCui博客的方案1(见其博客代码示例的Demo2)。关注点:由于需要添UINavigationBar所以不满足关注点1;此方案导航栏内容和背景随视图渐进平移,背景不突兀,不满足关注点2,但满足关注点3;对于关注点4需要控制好手势的响应。此方案实现起来复杂,并且导航栏原生的特殊效果没有(自适应调整滚动视图、 iOS 11的大标题特效等),但此方案并没有突兀点,不影响需求的话可以采用。方案三方案说明:系统导航栏透明,自定义导航栏背景视图,将系统原有导航栏的背景设置为透明色,同时在每个 ViewController上添加一个View或者 NavigationBar来充当我们实际看到的导航栏,每个ViewController同样只需要关心自身的样式即可。当然也可根据需要部分页面隐藏导航栏。存在问题:基本上满足需求,但和系统原生比较起来,需要自己实现半透明效果,另外可在转场过程中通过self.transitionCoordinator?.animateAlongsideTransition设置navigationBar透明度。样例:参见KenshinCui博客的方案2(见其博客代码示例的Demo3)。关注点:基本满足所列4个关注点。方案四方案说明:隐藏导航栏,每个页面包含一个NavigationController ,每个页面有2个ViewController和一个NavigationController,一个ViewController交给所属导航管理页面跳转,且其子视图为NavigationController(寄宿到另一个ViewController)。我们具体细节内容布局在导航内层那个ViewController。存在问题:视图结构复杂,过渡时导航内容的没动画,手势处理需谨慎(面临两个导航)。样例:网传网易云音乐是这样的关注点:看起来和方案二相似,更好的满足关注点1。不满足关注点2。满足关注点3,如果手势处理好可满足关注点4。相对每个自身页面而言,导航栏的原生特殊效果可以通过内层NavigationController达到。方案五方案说明:使用系统导航栏,页面过渡添加Fake Bar在转场的过程中隐藏原有的导航栏并添加假的 NavigationBar,当转场结束后删除假的 NavigationBar 并恢复原有的导航栏,这一过程可以通过 Swizzle 的方式完成,而每个 ViewController 只需要关心自身的样式即可。当然也可根据需要部分页面隐藏导航栏。存在问题:但在解决 Bug 的时候,Swizzle 这种方式无疑会增加解决问题的时间成本和学习成本。样例:美团关注点:不满足关注点2,其它满足。4.推荐方案优先推荐方案3,简单易用;方案3为避免出乱子,需要良好的团队代码规范和完善的技术文档来做辅助。如果旧项目并且历史问题较多采用方案5。方案2和方案4满足需求的情况下也可选用,但这两个方案较复杂。 ...

October 30, 2018 · 1 min · jiezi

解决Xcode10不支持libstdc++的问题

Apple在iOS12中取消了对stdlic++的支持临时的解决方案是将老版本的Xcode中的文件copy到新版Xcode的目录中模拟器/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/需要说明的是,iOS12模拟器即使添加了这个文件仍然不支持libstdc++,需要低于iOS12版本的模拟器才能正常使用真机/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/

October 26, 2018 · 1 min · jiezi

iOS开发手册

1.工程文件结构所有的文件应放在工程中的项目目录下。项目文件和物理文件需保持一致。Xcode创建的任何组(group)都必须在文件系统中有映射。项目文件不仅可以按照业务类型分组,也可以根据功能分组。2.代码格式规范2.1 代码注释格式文件注释:采用Xcode自动生成的注释格式。//// AppDelegate.h// 项目名称//// Created by 开发者姓名 on 2018/6/8.// Copyright © 2018年 公司名称. All rights reserved.//import注释:如果有一个以上的import语句,对这些语句进行分组,每个分组的注释是可选的。// Framework#import <UIKit/UIKit.h>// Model#import “WTUser.h”// View#import “WTView.h"方法注释:Xcode8之后快捷键自动生成(option + command + /)。/**<#Description#>@param application <#application description#>@param launchOptions <#launchOptions description#>@return <#return value description#>*/- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;代码块注释:单行的用 “// + 空格” 开头, 多行用“/ /”。2.2 代码结构与排版声明文件:方法顺序和实现文件的顺序保持一致,根据需要用”#pragma mark -“将方法分组。实现文件:必须用”#pragma mark -“将方法分组。分组前后优先级:Lifecycle方法 > Public方法 > UI方法 > Data方法 > Event方法 > Private方法(逻辑处理等) > Delegate方法 > 部分Override方法 > Setter方法 > Getter方法。#pragma mark - Lifecycle- (instancetype)init {}- (void)viewDidLoad {}- (void)viewWillAppear:(BOOL)animated {}- (void)viewDidAppear:(BOOL)animated {}- (void)viewWillDisappear:(BOOL)animated {}- (void)viewDidDisappear:(BOOL)animated {}- (void)didReceiveMemoryWarning {}- (void)dealloc {}#pragma mark - Public- (void)refreshData {}#pragma mark - UI- (void)initSubViews {}#pragma mark - Data- (void)initData {}- (void)constructData {}#pragma mark - Event- (void)clickButton:(UIButton *)button {}#pragma mark - Private- (CGFloat)calculateHeight {}#pragma mark - UIScrollViewDelegate- (void)scrollViewDidScroll:(UIScrollView *)scrollView {}#pragma mark - Override- (BOOL)needNavigationBar {}#pragma mark - Setter- (void)setWindow:(UIWindow *)window {}#pragma mark - Getter- (UIWindow *)window {}变量:优先使用属性声明而非变量声明,注意属性修饰符、变量类型、变量之间的间隔。@property (strong, nonatomic) UIWindow *window;点语法:应始终使用点语法来访问和修改属性。间距要求如下:一个缩进使用四个空格。在”-“或者”+“号之后应该有一个空格,方法的大括号和其它大括号始终和声明在同一行开始,在新的一行结束,另外方法之间应该空一行。- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { if (door.isClosed) { // Do something } else { // Do something } return YES;}长度要求如下:每行代码的长度不应该超过100个字符。单个函数或方法的实现代码控制在50行以内。单个文件里的代码行数控制在500600行之内。3.代码命名规范3.1 代码命名基础最好是既清晰又简短,但不要为简短丧失清晰性,并使用驼峰命名法。名称通常不缩写,即使名称很长也要拼写完全(禁止拼音),然而可使用少数非常常见的缩写,部分举例如下:常用缩写词含义常用缩写词含义appapplicationmaxmaximumaltalternateminminimumcalccalculatemsgmessageallocallocterectrectangledeallocdealloctemsgmessageinitinitializetemptemporaryintintegerfuncfunction由于Cocoa(Objective-C)没有C++一样的命名空间机制,需添加前缀(公司名首字母)防止命名冲突,前缀使用2个字符(以下统称项目前缀)。常见的单词略写:ASCII,PDF,HTTP,XML,URL,JPG,GIF,PNG,RGB等3.2 类和协议命名类名应明确该类的功能,并且要有项目前缀防止命名冲突。协议组合一组方法作为一个类的部分接口使用, 用类名作为协议名,例如:NSObject。协议仅仅组合一组方法而不关联具体类,这种协议的命名应采用动名词形式(ing)。委托形式的协议命名为类名加上Delegate,例如:UIScrollViewDelegate。3.3 变量和属性命名变量名应前置下划线“”,属性名没有下划线。属性本质上是存取方法setter/getter,可进行重写(注意内存管理)。@property (strong, nonatomic) UIWindow *window;- (void)setWindow:(UIWindow *)window;- (UIWindow *)window;可以适当的对setter/getter进行别名设置。@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled;3.4 方法和函数命名方法名和函数名一般不需要前缀,但函数(C语言形式)作为全局作用域的时候最好加上项目前缀。 表示行为的方法名称以动词开头,但不要使用do/does等无实际意义的助动词。参数前面的单词要能够描述该参数,并且参数名最好能用描述该参数的单词命名。- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;方法中多个参数可以使用适当的介词进行连接。// 后续多个参数使用with- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;// 添加适当介词能够使方法的含义更明确- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;// 第一个参数用了with,后面的参数不使用with- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage;只有在方法返回多个值的时候使用get单词进行明确。- (void)getLineDash:(nullable CGFloat *)pattern count:(nullable NSInteger *)count phase:(nullable CGFloat *)phase;方法返回某个对象实例。// 类方法创建对象+ (instancetype)buttonWithType:(UIButtonType)buttonType;// 单例命名+ (UIApplication *)sharedApplication; 委托或代理方法命名第一个参数最好能相关某个对象。- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;私有方法不要以下划线““开头,因为系统私有方法保留此方式。自定义方法和系统方法重名,建议在方法开头加前缀”xx_methodName“。3.5 常量和宏的命名const常量外部声明:在Objective-C文件中优先采用FOUNDATION_EXTERN和UIKIT_EXTERN,而非C语言中的extern。const常量采用驼峰命名原则。const常量根据作用域适当加上前缀(含项目前缀):可供外部使用需加上相应的类名或者模块前缀,仅文件内部使用需要加上小写字母“k”.宏定义每个字母采用大写,单词之间用下划线“”间隔。宏定义也可根据作用范围加上适当前缀,避免命名冲突。3.6 枚举的命名使用枚举来定义一组相关的整数常量,增强代码的可读性。枚举可根据作用域添加前缀(含项目前缀),格式:[相关类名或功能模块名] + [描述] + [状态]。建议优先采用Objective-C的声明NS_ENUM和NS_OPTIONS,少采用C语言形式的enum等枚举声明.枚举定义时需指定None状态,并且其rawValue一般为起始值0。// NS_ENUMtypedef NS_ENUM(NSInteger, UIStatusBarAnimation) { UIStatusBarAnimationNone = 0, UIStatusBarAnimationFade = 1, UIStatusBarAnimationSlide = 2,}typedef NS_OPTIONS(NSUInteger, UIRemoteNotificationType) { UIRemoteNotificationTypeNone = 0, UIRemoteNotificationTypeBadge = 1 << 0, UIRemoteNotificationTypeSound = 1 << 1, UIRemoteNotificationTypeAlert = 1 << 2, UIRemoteNotificationTypeNewsstandContentAvailability = 1 << 3,}3.7 通知命名外部声明:在Objective-C文件中优先采用FOUNDATION_EXTERN和UIKIT_EXTERN,而非C语言中的extern。通知的命名一般都是跨文件使用的,需添加项目前缀。// [相关联类名或者功能模块名] + [will/Did](可选) + [描述] + NotificationUIApplicationDidEnterBackgroundNotification UIApplicationWillEnterForegroundNotification 3.8 类型别名命名根据作用域添加前缀(含项目前缀),格式:[类名或功能模块名] + [描述]。4.文件资源命名规范资源文件命名也需加上项目前缀。资源文件名全小写,单词之间用下划线“”间隔。资源文件命名格式:[项目前缀] + [业务] + [文件名]图片文件命名格式:[项目前缀] + [业务] + [类型] + [状态]。// 常见类型:logol,icon,img// 常见状态:normal,selected,highlightUIImage *image = [UIImage imageNamed:@“wt_setting_icon_normal”];5.代码警告处理注意警告问题的隐蔽性,因此最好修复警告。警告类型的查看步骤:选中警告 -> 右键Reveal in Log(不编译Reveal in Log是灰色的,因此先编译) ->查看方括号的内容如果需要忽略警告,建议优先代码push或者pop处理。#pragma clang diagnostic push#pragma clang diagnostic ignored “-Warc-retain-cycles”// 造成警告的代码#pragma clang diagnostic pop如果警告数量过大,检查警告类型以及必要性,可xcode配置忽略此类型警告。步骤:选中工程 -> TARGETS -> Build Settings -> Other Warning Flags。忽略单个和全局配置稍有差别,如下举例:push/pop Other Warning Flags-Wformat —-> -Wno-format -Wunused-variable —-> -Wno-unused-variable -Wundeclared-selector —-> -Wno-undeclared-selector -Wint-conversion —-> -Wno-int-conversion也可以在pch等大范围作用域的头文件中添加代码来忽略后续警告:#pragma clang diagnostic ignored “警告名称” 。6.外部库文件引入库文件引入最好把警告处理掉。库文件引入优先采用CocoaPods引入,并且指定版本号。源文件方式需引入文件到工程目录下。源文件方式需注意有无版本说明信息(可能在README文件中,也可能在某个.h头文件中,又或者有Version文件)没有时需在库文件目录下新增版本说明文件,7.代码版本管理版本管理工具:svn 或 git。svn文件管理配置:目录/.subversion打开config文件配置global-ignore。git文件管理配置:.gitignore文件记录了被git忽略的文件,作用于本仓库,常见语法如下:井号(#)用来添加注释用的,比如 “#注释”。build/ : 星号()是通配符,build/则是要说明要忽略 build 文件夹下的所有内容。.pbxuser : 表示要忽略后缀名为.pbxuser的文件。!default.pbxuser : 感叹号(!)是取反的意思,.pbxuser 表示忽略所有后缀名为.pbxuser的文件,如果加上!default.pbxuser则表示,除了default.pbxuse忽略其它后缀名为pbxuse的文件。提交信息规范:BUG类型为“Fix + [BUG编号] + [BUG描述]”。任务类型为“Done + [任务编号] + [任务描述]”。任务中间态为“Doing + [任务编号] + [任务描述]”。引入类库为“import + [类库名]”。8.构建和分发手动构建:Xcode界面化构建注、xcodebuild终端命令构建。自动化构建:Jenkins+Fastlane、xcodebuild脚本执行 。内测分发渠道:fir.im、蒲公英等。线上分发渠道:AppStore。 ...

October 23, 2018 · 2 min · jiezi