
暂无个人介绍
我们应该都有用过这个功能,你的朋友微信给你分享了一个淘宝里面的商品链接,然后当你复制这个链接打开淘宝APP的时候,就会弹出一个弹窗,像这样: example.PNG 这个功能想必大家都挺熟悉,受这个启发我们产品也想在我们APP上添加这样一个功能,与这个不一样的是,当我们复制一段网址的时候打开我们的APP会弹出框填一些信息后上传到我们的“资源库”。大体功能就这样,所以记录一下实现的过程。 一、弹窗视图功能 .h中:两个信号一个是确定信号一个是取消信号 两个方法,一个显示一个隐藏方法 1 2 3 4 5 @property (nonatomic, strong) RACSubject *uploadSureSignal;//确定上传信号 @property (nonatomic, strong) RACSubject *hideSucSignal;//隐藏 - (void)show; - (void)hide; .m中:主要是两个textview,还有涉及到在keywindow上,IQKeyboard的一些操作 1 2 3 @property (nonatomic, assign) CGFloat keyboardHeight;//键盘高度 @property (nonatomic, strong) CustomUITextView *nameTV; @property (nonatomic, strong) CustomUITextView *desTV; 因为发现IQKeyboard在这个弹出界面有问题,所以在显示这个界面的时候,将IQKeyboard禁用取之使用系统的keyboard监听方法 在(void)show方法中: 1 2 3 4 5 6 7 8 -(void)show { //键盘通知 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillShowNotification object:nil]; [defaultCenter addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillHideNotification object:nil]; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 //监听方法 - (void)keyboardWillShowOrHide:(NSNotification *)notification { //获取通知名 NSString *notificationName = notification.name; //获取通知内容 NSDictionary *keyboardInfo = notification.userInfo; //键盘弹出时,让画面整体稍稍上移,并伴随动画 //键盘回收时反之 CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGFloat height = keyboardFrame.size.height; self.keyboardHeight = height; //动画结束后self.view的frame值 CGRect selfViewFrame = self.bgView.frame; //通过通知名字判断弹出还是回收 if ([notificationName isEqualToString:UIKeyboardWillShowNotification]) { selfViewFrame.origin.y = SCREEN_HEIGHT - PANELHEIGHT - height; } else { selfViewFrame.origin.y = SCREEN_HEIGHT - PANELHEIGHT; } //取出动画时长 NSTimeInterval duration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; //使用动画更改self.view.frame [UIView animateWithDuration:duration animations:^{ //这里填入一些view的最终状态属性设置,即会自动产生过渡动画 self.bgView.frame = selfViewFrame; }]; } 同时在show方法中显示keyWindow,进而改变界面的frame进行显示 1 2 3 4 5 6 7 8 9 10 - (void)show { UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; [keyWindow addSubview:self]; CGRect frame = self.bgView.frame; if (frame.origin.y == SCREEN_HEIGHT) { frame.origin.y = SCREEN_HEIGHT - PANELHEIGHT; [UIView animateWithDuration:0.4 animations:^{ self.bgView.frame = frame; }]; } hide方法这里要考虑到键盘弹出后将self.bgView向上提高后frame的变化。 1 2 3 4 5 6 7 8 9 10 11 12 13 CGRect selfFrame = self.bgView.frame; if (selfFrame.origin.y == SCREEN_HEIGHT - PANELHEIGHT || selfFrame.origin.y == SCREEN_HEIGHT - PANELHEIGHT - self.keyboardHeight) { [self resignFirstResponder]; selfFrame.origin.y = SCREEN_HEIGHT; [UIView animateWithDuration:0.4 animations:^{ self.bgView.frame = selfFrame; } completion:^(BOOL finished) { [IQKeyboardManager sharedManager].enable = YES; [[NSNotificationCenter defaultCenter] removeObserver:self]; // [self.hideSucSignal sendNext:nil]; [self removeFromSuperview]; }]; } delegate中的操作 这里首先要弄懂APPdelegate中的这几个代理方法的意思: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 //App已经启动 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. return YES; } //App挂起状态 - (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. } //APP进入后台 - (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. } //APP将重新回到前台 - (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. } //APP进入活跃状态 - (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:. } 在上面的这些代理方法中,我们需要用到的是 applicationDidBecomeActive方法。在这个方法中我们去检查系统的粘贴板UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 if (pasteboard.string) { NSLog(@"string:%@", pasteboard.string); NSString *urlStr = pasteboard.string; if ([urlStr hasPrefix:@"https://"] || [urlStr hasPrefix:@"http://"]) { //如果粘贴板中的字符串包含https或http字段,我们去检查当前的控制器 如果当前的控制器是我们弹出做操作的控制器的话 isPopVC = NO; BOOL isPopVC = NO; UIViewController * Rootvc = self.window.rootViewController; if ([Rootvc isKindOfClass:[UINavigationController class]]) { UINavigationController * nav = (UINavigationController *)Rootvc; UIViewController * v = [nav.viewControllers lastObject]; if ([v isKindOfClass:[UploadResCofingVC class]]) { isPopVC = YES; } } //如果popView == nil 并且isPopVC == NO 弹出popView弹窗视图 进行操作 if (!self.popView && !isPopVC) { UploadResourcesPopupView *popView = [UploadResourcesPopupView new]; [popView show]; self.popView = popView; [self.popView.hideSucSignal subscribeNext:^(id x) { @strongify(self); self.popView = nil; }]; } } } } 总结 以上大体就是实现这个功能的基本思路,细节方面因项目而异了,比如我们需要判断当前用户的角色,当前用户是否登录,对弹窗视图后续的一些操作。当然并不完美,欢迎批评指正。 转自:http://www.cocoachina.com/ios/20180508/23307.html
常见问题 你昨天/这周学习了什么? 你为什么热衷于软件开发? 你对哪一种控制系统比较熟悉? 是否参与过GitHub项目? 是否参与过GitHub或其他同类型网站的iOS开源项目? 请描述一下你的iOS应用开发流程。 是否熟知CocoaPods?它是什么?如何运行的? 请概括一下你对软件授权的理解,及其对软件开发的影响。 请概括一下你在构建iOS应用时的测试过程。iOS应用如何实现对其他语言、日期格式以及货币单位的支持? 请描述一下Instruments及其作用。 关于iOS技术 请解释一下Handoff是什么,并简述它是如何实现iOS、Mac/网页应用互通的。 iCloud包含了哪些技术与服务? iOS扩展是指?能否列举一些热门或常见的范例? HealthKit是什么? HomeKit是什么? Apple Pay是什么?能否描述一下如何在应用中使用Apple Pay? 请解释一下iOS应用沙盒机制。 VoiceOver是什么?请举例解释一下iOS中的辅助功能(Accessibility)。开发者如何使用这些功能? iOS应用是如何实现后台多任务处理(Multitasking)的? Game Center针对iOS游戏有哪些功能? iBeacons是什么? Cocoa/Cocoa Touch是什么? 请概括一下Core Audio,Core Data以及Core Location各是什么。它们对iOS应用有何意义? 请描述SpriteKit和SceneKit的作用。 Metal是什么? 响应链(Responder Chain)是什么?它是如何发挥作用的? 按钮和其他控制方式对哪些操作做出回应? AppDelegate扮演着什么样的角色? 请解释一下NSUserDefaults。就你而言,你会如何在磁盘中对数组对象进行序列化? 你会如何储存用户的认证信息? 请问何为Keychain服务? 为什么移动设备上的缓存和压缩是不可或缺的? 请解释一下~/Documents,~/Library和~/tmp。 iOS中的~属于什么目录? AirPlay是如何运行的?换做是你,你会如何通过编程提高应用的实用性以及演示效果? 传感器,IO以及WiFi、拨号等连接方式如何在iOS平台上运作?它们有何利用价值?请扼要地谈谈你的观点。 iPad 2,iPad mini 1-3,iPad Retina,iPad Air 2,iPhone 5、5S、6以及6+在硬件性能方面有何差异?这对注重性能的应用有何限制? 关于编程 Cocoa Touch包含什么?不包含什么? 为什么Cocoa Touch的类名称是以两个大写字母开头的? Swift和Objective-C分别是什么?两者相比有何不同之处,又有何联系? 为什么Optional在Swift语言中非常重要? 请解释一下NSError。在Swift中,什么情况下能使用NSError ,什么情况下不能? 请说明如何使用Instancetype及其重要性。 在Swift中,什么时候该用let,什么时候该用var? 为什么map函数必不可少?该在什么情况下使用它? 你会选择什么工具来追踪Bug? 如果在Cocoa中发现一个Bug,你会如何处理? 如果应用的新版本出现了Regression的情况,该如何补救?如何防止用户在使用过程中遇到新的Bug? Objective-C的类是怎么执行的?Objective-C Runtime是如何实现的? iOS是如何提高安全性,保护用户隐私信息的? 应用可以下载并即刻显示数据。如何根据MVC来判断下载的最佳位置? MVC对代码库(Codebase)的设计有何影响? Controller Life-Cycle以及View Life-cycle分别有哪些调试方法? iOS使用的是哪些设计模式(Design Patterns)?你的代码库使用的是哪些设计模式? iOS提供哪些线程?如何充分利用这些线程? 请简要描述一下UIScrollView的执行过程。它是如何响应手势识别(Gesture Recognizer)、多点触控(Multi-Touch)和Run Loop的? 你认为iOS需要添加或改进哪些API? 关于界面 iPhone5、6、6+以及iPad Air 2的屏幕分辨率分别是多少? 分辨率的计算单位是什么? 请解释一下Interface Builder的作用以及NIB文件的概念。 iOS UI的图像储存类型是什么? 请描述一下Storyboard和标准NIB文件的差别。 设备状态栏(Device Status Bar)是什么?高度如何?是否透明?在手机通话或者导航状态下,它是如何显示的? 导航栏(Navigation Bar)是什么?能否拿出你的iPhone,指出你下载的哪些应用运用了导航栏? 选项卡(Tab Bar)和工具栏(Toolbar)分别是什么?两者之间有何共同点和不同点? 表视图(Table View)是什么?集合视图(Collection View)又是什么? 什么时候用“弹出(Popover)”属性最为合适? Split-view Controller是什么? 选取器视图(Picker View)适合存放哪类内容? 应该在什么情况下使用标签、文本域和文本视图? 分段控件(Segmented Control)的作用是什么? 模态视图(Modal View)是什么? iOS通知属于什么类型? 关于设计 iOS应用图标是指什么?请尽可能详细地描述一下。 最小尺寸和最大尺寸的应用图标分别是什么样子的? 应用图标能否包含透明的部分? Newsstand的图标与常规应用有何不同? 请解释一下启动画面(Launch Images)。 自动布局(Auto Layout)的作用是什么?请概括一下它是如何运行的。 设计软件时为什么要加上动画? 请描述一下软件设计中的交互和Feedback有什么作用。 设计iPhone和iPad应用时,应分别考虑哪些因素? 请描述一下原型设计对于软件开发的意义。其作用是什么? 关于App Store 应用内购买(In-App Purchases)是怎么回事?IAP能够为用户带来哪些新体验? 你是否在App Store上发布过应用?能否概括一下过程? iTunes Connect是什么? Provisioning Profiles是指? App ID是什么? iOS的开发和发布签名证书有何异同? 如何使用TestFlight?通过Ad-hoc发布应用的话,该如何使用UUID? 应何时验证购买收据? 发布iAds(苹果平台广告)有哪些要求? 趣味问答 最近有没有开发什么好玩的东西?你最引以为豪的作品是什么? 谈一谈你常用的开发工具都有哪些优势? 你最敬佩的独立Mac或者iOS应用开发者是谁? 最喜欢什么项目?哪种类型的? 你觉得Xcode有哪些需要改进的地方? iOS上你最喜欢哪些API? 是否有最中意的错误报告? 你最爱以哪种方式来检验一项新技术是否好用? 为什么词典被称作Dictionaries,而不是HashTable或HashMap?
在 iOS 上面开发界面,需要创建视图、配置界面、视图分层等等很多步骤,也就不可避免的需要书写 N 多的代码。这还仅仅是界面设计,除此之外,完成 controllers 的回调、控制内部事务在界面上的显示效果、界面的操控和内部事务的联系等等多方面的事情都需要手动解决。即便是界面很简单的 App,如果存在这种复杂的双向数据流的关系,那么代码也会变得很复杂很容易出错。Qt 的信号、槽和 iOS 的 Target-Action 机制其实也是很容易实现这种双向数据流的关系,但是没有办法解决界面和事务之间的联系,也有很多其他的问题:性能、测试等。 这些问题曾经困扰了我们多年。News Feed 是有着复杂的列表样式外观的 iOS 软件,由许多的 Row Type 组成,每一个 Row 都有各种各样不同的很烦的界面样式和交互方式,这个就很坑了。每次维护这个东西都像是在清理厕所,尤其是它的功能还在不断增加,它的代码在不断变多,版本迭代速度快到你都没办法直到每天都到底增添了什么新代码,上司还要拿着报告说“你这个软件太慢了,影响用户体验,给你三个小时把这个 App 的速度提高 80%”。 为了解决这一挑战性的问题,我们从自己的 ReactJS 得到启发,把很多具体的东西抽象出来,做出一个功能性的、响应式编程模型的 iOS 原生 UI 框架 ComponentKit,目前 News Feed 在应用这个框架。 ComponentKit 简介 ComponentKit 使用功能性和声明性(declarative)的方法来进行创建界面,和以往不同的是,ComponentKit 使用单向数据流的形式从 不可变的模型 映射到不可变的组件来确定视图的显示方式。ComponentKit 的 declarative 看上去和 declarative UI(QML) 差不多,其实差得远。QML 更偏向于 UI 设计的描述性,而 ComponentKit 则是做好基本 UI 和事件之间的联系,让事件设计和 UI 设计可以分开单独完成。 内在决定外在,组件的功能和内部的层次决定了用户界面该如何规划,界面的规划决定了 UI Kit 的元素层次结构的设计。 传统做法的结果是大部分时间都被浪费在 UI 该如何实现,ComponentKit 却可以让你把时间都用在在 UI 该怎么设计上面。 例如,传统的 iOS 开发中,为了开发一个带有 header、text 和 footer 的视图,需要以下步骤: 分别创建 header 视图、text 视、footer 视图的实例 将三个视图添加为 container 的子视图 添加约束条件,让每个视图和 container 的宽度相同 添加更多的约束条件,确保每个视图的摆放位置 但是 ComponentKit 不一样,ComponentKit 是一种描述性的开发包:你只需要提供你希望得到什么便能得到什么,而不和传统的 iOS 开发一样,再去一个一个地创建视图、修改视图样式、添加视图、添加约束条件。如图所示,想要得到这个布局,只需要使用描述性的语言描述“我想要一个 header 组件,一个 text 组件,一个 footer 组件,他们的宽度相同,从上到下排列在一起”。单单从这点来看,和 QML 相比,ComponentKit 更类似于 Bootstrap:提供已经完成的组件,你只需要决定组件如何摆放,便可轻松地开发出 UI 界面。 ComponentKit 已经完全把如何渲染 UI 的事情抽象出来,程序员完全可以不去考虑具体是如何实现渲染的,也不用去考虑界面渲染该如何优化。ComponentKit 使用后台线程进行界面布局,也实现了智能组件重用,你完全可以不去考虑界面导致的内存泄露问题。ComponentKit 不仅仅可以极大地提高开发效率,界面响应速度和软件的运行效率也会有极大地提升。 News Feed 移植到 ComponentKit ComponentKit 极大地提升了 News Feed 的 UI 响应速度和稳定性,也让整个软件的内部编码更容易理解。ComponentKit 达到了如下的目标: 减少了 70% 的界面渲染代码,麻麻再也不用担心我每次去维护之前都要看那本又臭又长的手册然后花一上午的时间去理解那个错综复杂的布局了。 显著地提高了滑屏的性能。ComponentKit 消除了许多的 container视图,尽力将所有的视图结构化简。更简洁的视图结构意味着界面的渲染性能和执行效率更高。 提高测试覆盖率。ComponentKit 对于 UI 模块化的设计保证了每一部分都可以被分离开来单独进行测试。再加上 snapshot tests,我们现在几乎已经可以对 News Feed 的所有部分都进行测试了。 引入了 ComponentKit 之后,我们能够维护更少的代码,有更少的 bug 需要修复,有更大的测试覆盖率:我们现在可以有更多的时间做羞羞的事情了 ComponentKit 已经在生产环境的 News Feed 上用了六个月,我们觉得可以一直用下去。现在将 ComponentKit 开源,让整个 iOS 开发者社区的人都有 Facebook 的生产效率,也都能和 Facebook 一样做出高性能的 App。很希望你也能在你的开发环境中使用 ComponentKit,然后给我们反馈。 我们重新定义了如何在 iOS 上开发界面,希望你也能用 ComponentKit 开发出更优雅的 App。 快速入门 ComponentKit 已经在 CocoaPods 中可用了,只需要在 Podfile 添加如下代码即可: pod 'ComponentKit', '~> 0.9' pod try ComponentKit
Copying 在 iOS 中有很多概念,例如浅拷贝与深拷贝、copy 与 mutableCopy、NSCopying 协议,一直想彻底搞明白这些概念,刨根问底不搞懂不罢休嘛。于是搜 Google 看了一些博客,又去翻了 Apple 相关的文档,发现网上许多博客都理解错了,下面说说自己的理解。 浅拷贝与深拷贝 对于浅拷贝(Swallow Copy)与深拷贝(Deep Copy),经常看到这样的说法:浅复制是指针拷贝,仅仅拷贝指向对象的指针;深复制是内容拷贝,会拷贝对象本身。 这句话并没有说错,但需要注意的是指针/内容拷贝针对的是谁,无论浅拷贝还是深拷贝,被拷贝的对象都会被复制一份,有新的对象产生,而在复制对象的内容时,对于被拷贝对象中的指针类型的成员变量,浅拷贝只是复制指针,而深拷贝除了复制指针外,会复制指针指向的内容。下面我们以 Apple 官方文档中的图片进行说明: 对普通对象 ObjectA 进行 copy,无论浅拷贝还是深拷贝,都会复制出一个新的对象 ObjectB,只是浅拷贝时 ObjectA 与 ObjectB 中的 textColor 指针还指向同一个 NSColor 对象,而深拷贝时 ObjectA 和 ObjectB 中的 textColor 指针分别指向各自的 NSColor 对象(NSColor 对象被复制了一份)。 对集合对象 Array1 进行 copy,无论浅拷贝还是深拷贝,都会复制出一个新的对象 Array2,只是浅拷贝时 Array1 与 Array2 中各个元素的指针还指向同一个对象,而深拷贝时 Array1 和 Array2 中各个元素的指针分别指向各自的对象(对象被复制了一份)。 Copy 与 MutableCopy 在说明 copy 与 mutableCopy 之前,我们思考一下:拷贝的目的是什么?在动态库加载时,只读的 TEXT 段是被所有使用动态库的程序共享的, 而可写的 DATA 段会使用 COW(Copy On Write)技术,当某个程序需要修改 DATA 段时会拷贝一份,供此程序专用。因此,拷贝的目的主要用于拷贝一份新的数据进行修改,而不会影响到原有的数据。如果不修改,拷贝就没有必要。 在 iOS 中,有一些系统类根据是否可变进行了区分,例如 NSString 与 NSMutableString,NSArray 与 NSMutableArray 等。为了在两者之间进行转换(我理解这是主要目的),NSObject 提供了 copy 与mutableCopy 方法, copy 复制后对象是不可变对象,mutableCopy 复制后对象是可变对象。对象有不可变对象和可变对象,复制方法有 copy 和 mutableCopy,因此存在四种情况: 不可变对象 copy:对象是不可变的,再复制出一份不可变对象没有意义,因此根本没有发生任何拷贝,对象只有一份。 不可变对象 mutableCopy:可变对象的能够修改,原来的不可变对象不支持,因此需要复制出一个新对象,是浅拷贝。 可变对象 copy:不可变对象不能修改,原来的可变对象不支持,因此需要复制出新对象,是浅拷贝。 可变对象 mutableCopy:可变对象的修改不应该影响到原来的可变对象,因此需要复制出新对象,是浅拷贝。 如何进行深拷贝呢? 对于集合类型的对象,将 initWithArray:copyItems: 第二个参数设置成 YES 时,会对集合内每一个元素发送 copyWithZone: 消息,元素进行复制,但是对于元素中指针类型的成员变量,依然是浅拷贝,因此这种拷贝被称为单层深拷贝(one-level-deep copy)。 如果想进行完全的深拷贝,可以先通过 NSKeyedArchiver 将对象归档,再通过 NSKeyedUnarchiver 将对象解归档。由于在归档时,对象中每个成员变量都会收到 encodeWithCoder: 消息,相当于将对象所有的数据均序列化保存到磁盘上(可以看成换了种数据格式的拷贝),再通过 initWithCoder: 解归档时,就将拷贝过的数据经过转换后读取出来,深拷贝。 NSCopying 如果自定义的类也想要支持 copy 和 mutableCopy 方法,就需要实现 NSCopying 和NSMutableCopying 协议。在实现 copyWithZone: 方法时需要注意: copyWithZone: 相当于新创建一个对象,并将当前对象的值复制到新创建的对象中。设置时应直接访问成员变量而不是通过属性访问。 直接从 NSObject 继承的类,应使用 [[[self class] allocWithZone:zone] init],使得在创建新对象时能够使用正确的类。 父类中已经实现了 copyWithZone: 时,应先调用父类的方法,让父类创建对应的对象(self class 能保证创建对象是正确的),并拷贝父类中定义的成员变量。 1 2 3 4 5 - (id)copyWithZone:(NSZone *)zone { YourClass *object = [super copyWithZone:zone]; _property = xxx; return object; }
小编这里有个自己的学习交流群681+503+716(验证编号:大鲨) * 1.自定义监听类* /** * 短信**,用于自动填充验证码 */ public class SMSContentObserver extends ContentObserver { public final String SMS_URI_INBOX = "content://sms/inbox";//收信箱 private Activity activity = null; private String smsContent = "";//验证码 private EditText verifyText = null;//验证码编辑框 private String SMS_ADDRESS_PRNUMBER = "10690329013589";//短息发送提供商 private String smsID = ""; //短信观察者 收到一条短信时 onchange方法会执行两次,所以比较短信id,如果一致则不处理 public SMSContentObserver(Activity activity, Handler handler, EditText verifyText) { super(handler); this.activity = activity; this.verifyText = verifyText; } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Cursor cursor = null;// 光标 // 读取收件箱中指定号码的短信 cursor = activity.getContentResolver().query(Uri.parse(SMS_URI_INBOX), new String[]{"_id", "address", "body", "read"}, //要读取的属性 "address=? and read=?", //查询条件是什么 new String[]{SMS_ADDRESS_PRNUMBER, "0"},//查询条件赋值 "date desc");//排序 if (cursor != null) { cursor.moveToFirst(); if (cursor.moveToFirst()) { //比较和上次接收到短信的ID是否相等 if (!smsID.equals(cursor.getString(cursor.getColumnIndex("_id")))) { String smsbody = cursor.getString(cursor.getColumnIndex("body")); //用正则表达式匹配验证码 Pattern pattern = Pattern.compile("[0-9]{6}"); Matcher matcher = pattern.matcher(smsbody); if (matcher.find()) {//匹配到6位的验证码 smsContent = matcher.group(); if (verifyText != null && null != smsContent && !"".equals(smsContent)) { verifyText.requestFocus();//获取焦点 verifyText.setText(smsContent);//设置文本 verifyText.setSelection(smsContent.length());//设置光标位置 } } smsID = cursor.getString(cursor.getColumnIndex("_id")); } } } } } 2.在登录页面事件化监听类 Java代码 //实例化短信** SMSContentObserver mObserver = new SMSContentObserver(getActivity(), new Handler(), mEt_auth_code); // 注册短信变化监听 mContext.getContentResolver().registerContentObserver(Uri.parse("content://sms/"), true, mObserver); > 3.声明读取短信权限 <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.WRITE_SMS" /> 4.为了防止内存泄漏,记得注销监听 Java代码 @Override public void onDestroy() { super.onDestroy(); //注销短信监听 mContext.getContentResolver().unregisterContentObserver(mObserver); } 小结: 去短信库获取短信比较不容易被拦截
这些内容是每一种编程语言的基础。我们大部分人从C语言开始编程,如果你还记得通过值调用与引用调用的函数,你也许就清楚它们的区别到底是什么。让我们看看苹果是怎么回答的吧 就像标题所说,swift中一个类型可以归入到下面两个分类中 值类型 引用类型 最基本的定义: 值类型-每个实例都拥有其数据的一份副本。当被赋值给一个变量或常量,或传递给一个函数时候,它会建立一份新的副本。 有需要关于iOS学习的视频与资料的扫码关注下列公众号: 让我们看一些编码 考虑下面的代码 引用类型 上面的类Home并没有进行任何初始化。存储的特性roomCount有一个默认值2。现在,看看第一个叫peterVilla的实例,他会有一个值为2的roomCount。 现在建立一个新的叫johnVilla的对象,并把这个对象按照上面的代码赋值。你觉得johnVilla的roomCount值会是多少呢?他会和peterVilla的roomCount值一样吗?是的,它是2。 现在把johnVilla的roomCount值变为5,之后打出它们的roomCount值,两者都给出了5。 原因: 类(class)是一个引用类型,复制一个引用,即表示建立一个共享的实例。复制之后,两个变量都使用数据中同一份实例。所以修改第二个变量中的数据同样会影响第一个。 注意:类是一个引用类型,意味着类中的变量不会存储实例,而是一个向内存(堆)中存储该实例位置的引用。 问题:如果我们把上面代码中的var变成let会怎么样? 回答:什么都不会变。像下面这样运行的话: let peterVilla = Home() let johnVilla = peterVilla 对输出结果不会有任何影响。roomCount还会是5,为什么?? 因为类是引用对象,let与var唯一的区别就是再赋值给同样类型下不同类的能力。let与var并不会影响同一个类中改变一个变量的能力。 但是,看看下面的代码 上面的代码就不言自明了。 简单考虑:一旦我们创建一个HOME,然后给它一个let常数,我们就只能改变roomCount。所以这里因为它是不可变的(let),John无法升级他的房子。我们不能创建一个新的HOME或改变它,let会对我们生气的。你现在应该明白了! 如果johnVilla是一个var呢? 如果如果johnVilla是一个var,那么它是可变的,他就可以改变他的房屋,无论多少次都可以。看看下面的代码吧: 如果Home是一个结构(struct)呢? 考虑下面的代码,其中Home是一个struct 这里Home是一个结构,johnVilla成为一个let常数,我们无法改变roomCount,就像上部分我们在类中一样。 这是因为结构是一个值类型,在结构中用let会让这个对象变为常数。它将不可改变或再赋值。一个用var创建的结构可以改变它的变量。 所以对johnVilla再赋值也会失败。 let peterVilla = Home() let johnVilla = peterVilla johnVilla = Home() //error: cannot assign to value: ‘johnVilla’ is a ‘let’ constant 注意:所以,对于值类型,如果我们想对对象再赋值或改变对象中的变量,它需要被声明为可变的(‘var’)。 上面的代码很简单,它涵盖了各方面比如再赋值或者改变成员变量。尽管我们在第44行把peterVilla赋值给johnVilla,johnVilla是一个独立的实例,拥有独自的peterVilla的数据副本。 这就是说,struct并不是swift中唯一的值类型,class也不是唯一的引用类型。下面是一些例子: Swift把一个引用类型看成一个类,这和Objective-C中很像。Objective-C中一切继承于NSObject都被按照引用类型存储。 我们什么时候选择值类型而不用引用类型? 如果你想建立一个新类型,你要怎么决定选哪一种呢?当你用Cocoa的时候,很多APIs期望NSObject的子类,所以你不得不采用类。对于其他情况,这里有一些参考: 以下时候使用值类型: 想要用==比较实例数据。一个双等号(==)用于比较值。 你想复制来建立独立数据。 数据要在多线程的代码中使用,那么你就不用担心数据会被其他线程改变。 以下时候使用引用类型(比如一个类): 想要用===比较实例一致性。===会检查两个对象是否完全一致,包括存储数据的内存地址。 你想要创建用于共享,可改变的数据。 引用类型和值类型在内存中怎么存储? 值类型-在栈内存中存储 引用类型-在托管堆内存中存储 栈与堆的不同! 像前面说的,引用类型实例存在堆中,值类型实例比如结构存在于一个称为栈的内存区域中。如果值类型实例是一个类的一部分,值会和类一起存在堆中。 栈被用于静态存储分配,栈用于动态存储分配,它们都存在计算机的RAM中。 栈被CPU紧密管理并优化,当一个函数创建一个变量,栈会存储这个变量,并在函数退出时候被毁掉。被分配到栈的变量直接存储在内存上,访问这段内存非常快。当一个函数或者方法调用另一个函数,另一个函数再依次调用其他函数等等,直到最后一个函数返回它的值之前,其他所有函数都会保持暂停执行。 栈总是按照LIFO顺序保留,最新保留的区块总是会下一个释放。这使得跟踪记录栈非常简单,释放一个栈上的区块不过是调整一个指针。因为栈非常组织有序,所以它快捷高效。 系统使用堆存储被其他对象引用的数据,堆是一大片内存,系统可以从中请求并动态分配内存区块。堆并不会像栈一样自动毁掉它的对象,需要外部工作来处理这些。在苹果设备中ARC就做这个工作。引用数量会被ARC追踪,当它变为0时对象会被释放。因此整个过程(分配,追踪引用,释放)会比栈要慢。所以值类型要快于引用类型。 这些就是全部内容,希望你喜欢! 本文转自: http://www.cocoachina.com/ios/20180503/23270.html
由于最近开发新版本,就避免不了在开发和调试过程中引起崩溃,以及诱发一些之前的bug导致的崩溃。而且项目比较大也很不好排查,正好想起之前研究过的`Method Swizzling`,考虑是否能用这个苹果的“黑魔法”解决问题,当然用好这个黑魔法并不局限于解决这些问题.... 小编自己有一个学习交流群,里面都是有多年开发经验的iOS大牛在里面交流,有需要的伙伴可以进来一起交流群号(681503716)(验证编码:大鲨),不定时也会分享ARKit技术,移动架构,支付宝,底层,高级进阶学习不等的视频教程资料 开发需求 如果产品经理突然说:”在所有页面添加统计功能,也就是用户进入这个页面就统计一次”。我们会想到下面的一些方法: - 手动添加 直接简单粗暴的在每个控制器中加入统计,复制、粘贴、复制、粘贴… 上面这种方法太Low了,消耗时间而且以后非常难以维护,会让后面的开发人员骂死的。 - 继承 我们可以使用继承的方式来解决这个问题。创建一个基类,在这个基类中添加统计方法,其他类都继承自这个基类。 然而,这种方式修改还是很大,而且定制性很差。以后有新人加入之后,都要嘱咐其继承自这个基类,所以这种方式并不可取。 - Category 我们可以为UIViewController建一个Category,然后在所有控制器中引入这个Category。当然我们也可以添加一个PCH文件,然后将这个Category添加到PCH文件中。 - Method Swizzling 我们可以使用苹果的“黑色魔法”Method Swizzling,Method Swizzling本质上就是对IMP和SEL进行交换。 Method Swizzling 原理 Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。 使用注意 类簇设计模式 在iOS中NSNumber、NSArray、NSDictionary等这些类都是类簇(Class Clusters),一个NSArray的实现可能由多个类组成。 所以如果想对NSArray进行Swizzling,必须获取到其“真身”进行Swizzling,直接对NSArray进行操作是无效的。 下面列举了NSArray和NSDictionary本类的类名,可以通过Runtime函数取出本类。 注意要点 Swizzling应该总在+load中执行 Swizzling应该总是在dispatch_once中执行 Swizzling在+load中执行时,不要调用[super load]。如果多次调用了[super load],可能会出现“Swizzle无效”的假象,原理见下图: 封装 在项目中我们肯定会在很多地方用到Method Swizzling,而且在使用这个特性时有很多需要注意的地方。我们可以将Method Swizzling封装起来,也可以使用一些比较成熟的第三方。 里面核心就两个类,代码看起来非常清爽。 #import <Foundation/Foundation.h> @interface NSObject (JRSwizzle) + (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; @end // MethodSwizzle类 #import <objc/objc.h> BOOL ClassMethodSwizzle(Class klass, SEL origSel, SEL altSel); BOOL MethodSwizzle(Class klass, SEL origSel, SEL altSel); 错误剖析 在上面的例子中,如果只是单独对NSArray或NSMutableArray中的单个类进行Method Swizzling,是可以正常使用并且不会发生异常的。如果进行Method Swizzling的类中,有两个类有继承关系的,并且Swizzling了同一个方法。例如同时对NSArray和NSMutableArray中的objectAtIndex:方法都进行了Swizzling,这样可能会导致父类Swizzling失效的问题。 对于这种问题主要是两个原因导致的,首先是不要在+ (void)load方法中调用[super load]方法,这会导致父类的Swizzling被重复执行两次,这样父类的Swizzling就会失效。例如下面的两张图片,你会发现由于NSMutableArray调用了[super load]导致父类NSArray的Swizzling代码被执行了两次。 #import "NSMutableArray+LXZArrayM.h" @implementation NSMutableArray (LXZArrayM) + (void)load { // 这里不应该调用super,会导致父类被重复Swizzling [super load]; Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndex:)); Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(lxz_objectAtIndexM:)); method_exchangeImplementations(fromMethod, toMethod); } 这里由于在子类中调用了super,导致NSMutableArray执行时,父类NSArray也被执行了一次。 父类NSArray执行了第二次Swizzling,这时候就会出现问题,后面会讲具体原因。 这样就会导致程序运行过程中,子类调用Swizzling的方法是没有问题的,父类调用同一个方法就会发现Swizzling失效了…..具体原因我们后面讲! 还有一个原因就是因为代码逻辑导致Swizzling代码被执行了多次,这也会导致Swizzling失效,其实原理和上面的问题是一样的,我们下面讲讲为什么会出现这个问题。 问题原因 我们上面提到过Method Swizzling的实现原理就是对类的Dispatch Table进行操作,每进行一次Swizzling就交换一次SEL和IMP(可以理解为函数指针),如果Swizzling被执行了多次,就相当于SEL和IMP被交换了多次。这就会导致第一次执行成功交换了、第二次执行又换回去了、第三次执行…..这样换来换去的结果,能不能成功就看运气了,这也是好多人说Method Swizzling不好用的原因之一。 从这张图中我们也可以看出问题产生的原因了,就是Swizzling的代码被重复执行,为了避免这样的原因出现,我们可以通过GCD的dispatch_once函数来解决,利用dispatch_once函数内代码只会执行一次的特性。 在每个Method Swizzling的地方,加上dispatch_once函数保证代码只被执行一次。当然在实际使用中也可以对下面代码进行封装,这里只是给一个示例代码。 #import "NSMutableArray+LXZArrayM.h" @implementation NSMutableArray (LXZArrayM) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndex:)); Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(lxz_objectAtIndexM:)); method_exchangeImplementations(fromMethod, toMethod); }); } 这里还要告诉大家一个调试小技巧,已经知道的可以略过。我们之前说过IMP本质上就是函数指针,所以我们可以通过打印函数指针的方式,查看SEL和IMP的交换流程。 先来一段测试代码: Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:)); Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:)); NSLog(@"%p", method_getImplementation(fromMethod)); NSLog(@"%p", method_getImplementation(toMethod)); method_exchangeImplementations(fromMethod, toMethod); NSLog(@"%p", method_getImplementation(fromMethod)); NSLog(@"%p", method_getImplementation(toMethod)); method_exchangeImplementations(fromMethod, toMethod); NSLog(@"%p", method_getImplementation(fromMethod)); NSLog(@"%p", method_getImplementation(toMethod)); method_exchangeImplementations(fromMethod, toMethod); NSLog(@"%p", method_getImplementation(fromMethod)); NSLog(@"%p", method_getImplementation(toMethod)); 看到这个打印结果,大家应该明白什么问题了吧: 2016-04-13 14:16:33.477 [16314:4979302] 0x1851b7020 2016-04-13 14:16:33.479 [16314:4979302] 0x1000fb3c8 2016-04-13 14:16:33.479 [16314:4979302] 0x1000fb3c8 2016-04-13 14:16:33.480 [16314:4979302] 0x1851b7020 2016-04-13 14:16:33.480 [16314:4979302] 0x1851b7020 2016-04-13 14:16:33.480 [16314:4979302] 0x1000fb3c8 2016-04-13 14:16:33.481 [16314:4979302] 0x1000fb3c8 2016-04-13 14:16:33.481 [16314:4979302] 0x1851b7020 Method Swizzling危险吗? 既然Method Swizzling可以对这个类的Dispatch Table进行操作,操作后的结果对所有当前类及子类都会产生影响,所以有人认为Method Swizzling是一种危险的技术,用不好很容易导致一些不可预见的bug,这些bug一般都是非常难发现和调试的。 这个问题可以引用念茜大神的一句话:使用 Method Swizzling 编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全。
前言 如果你有志于将iOS开发作为职业,或者已经是一位iOS开发者,那么你应该听说过唐巧的名字。唐巧,2012年从网易有道离开参与创业, 目前是猿题库iOS高级研发工程师。难得的是,他本身是一线的iOS开发者,同时又对iOS开发在国内的布道做了不少的工作,身影活跃于博客、微博、微信公众账号等地。从2011年以来,唐巧开始在博客分享自己写的iOS开发技术文章,CocoaChina 也曾多次转载过他的博客文章,后来他成为InfoQ兼职编辑,除了发表技术文章之外,还负责整理《iOS移动开发周报》,至今已有35期,他的微博和微信公众账号粉丝都超过1万,可以说是iOS开发领域的“大V”。并且他的新书《iOS开发进阶》已经和大家见面,也引得他的粉丝的一份热潮 iOS开发的更新换代 移动开发真正火起来其实就是最近这三年,iOS 开发技术因为发展也就才这么几年,所以值得做的事情还有很多,这就造成了每年苹果的 WWDC 都会推出一堆新的特性和 API。整体上来说,这对业界是好事情。在技术更新过程中产生一些坑,是在所难免的事情。所以希望大家都能用一个比较平和的心态去搞定这些坑。 我之前写过一篇《iOS 开发如何提高》的文章,简单总结来说,就是多读、多写、多思考、多讨论。多读主要包括:阅读博客、图书、WWDC 视频、官方文档、开源项目。多写就没什么好说了,没有写过几十万行代码是不能算熟悉一门语言的。多思考和讨论这个需要个人主动一些,遇到问题喜欢多问为什么,在多次重构和思考的过程中,我们就会慢慢积累出一类问题的 “最佳实践” 方式,成为自己宝贵的经验。 Swift 最终肯定会替代Objective-C 有一点不容怀疑:Swift 最终肯定会替代 Objective-C,成为主要的 iOS 开发语言。单从语言特性上来看,Swift 吸收了众多现代编程语言的优秀特性,例如类型推断(Type inference)、范型(Generic)、闭包(Closure)、命名空间(namespace),元组(tuple)等,整体语法上也更加简洁。所有 iOS 开发者都应该花时间学习这门全新的语言。 但是,客观地说,Swift 当前的生态环境还不够成熟,这包括第三方开源库,文档和教程,甚至是苹果自己的编译器。我前不久才和一些同行们聊过这个话题,朋友晒出了 Github 上整理出来的会造成 Swift 编译 Crash 的列表,从上面可以看出,Swift 语言本身还有较长的路要走。 用 Swift 来完成 App 还有一个不大不小的问题就是体积会比较大,因为 Swift 相关的库会直接打包进 App 中。我们曾经拿猿题库的 App 测试过,采用 Swift 和Objective-C 混合编写的方案后,应用体积会增加 10M,这对于公司来说还是比较大的困扰。我个人对于 Swift 的预期是:在 1、2 年内能够全面替代 Objective-C。但在近期,我个人还是会使用 Objective-C 来完成公司的项目。 iOS 开发者应该如何自我提升? 对于技术实力的提升,我比较推荐最近 tinyfool 写了一篇文章,叫《不要轻易在简历上写我热爱编程,我热爱学习》。我觉得你提的这个问题的根本还是在于:iOS 开发者到底热不热爱编程。在我看来,任何一件事情,如果你做到了热爱它,把它当作乐趣,那么在同行中做到出类拔萃应该是理所当然的。如果不热爱,我感觉做到会比较难。 对于其它方面,比如管理及其它软能力的提升,我其实也没有很多经验。如果从我自己的经验出发,我自认为多写作对于提高表达沟通能力是有帮助的。 做一个负责任的开发者 那么现在说正题,如何成为负责任的开发者?首先要负责,对自己的项目负责。如果是自己新开的项目,要保证随时都能清晰的想到项目当中每个地方是怎么实现的,测试或者用户反馈了问题以后,能立马想到可能的错误原因。如果是接手的项目,就要尽快去了解主要的界面和功能是如何实现的。你只有先做好自己分内的事,才有机会去顾暇别人的事。 1.保持一个良好的代码规范以及文件架构。 2.每天要给自己做一个TodoList 和一个BugList,时刻保持自己是在有效率的工作,严重的需要时间修复的bug汇报上去,小bug自己记下来偷偷修复。 3.有空时将排行榜上的应用下载排名靠前的应用,去欣赏并分析主流app的界面,功能实现,在拿到设计图时,去考虑界面的合理性,功能怎么实现最符合用户的操作习惯。 4.要有一定的协调能力,交流能力,稍微了解一点后台知识以及前端知识。 5.信念,一个不做初级iOS开发的信念。多去了解,不会被别人当小白,学多少都是自己的,至于在你去学习的时候,有人会说风言风语,这就是区别,他们活该初级,自己不会的东西,也看不惯别人去学习。所以,一定要有一个规划,按照自己正确的规划去学习,去成长,别原地踏步。 深度学习API,了解iOS UI控件 Objc阶段的学习只是带你入门,如果你想进阶,API和UI控件是必须需要学的。一门语言可能会有成千上百的API和UI控件。你不需要把他们全都记住,只需要记住一些常用的即可。 例如:Objc中的UITableView的dataSouce和delegate;NSDictionary、NSArray相关的增删查改方法;JSON数组转换方法;文件操作方法等。 UI控件的话,常用的有UIView、UITableView、UIScrollView、UIButton、UILable等。你要熟悉这些控件的属性和相关的操作方法。 学习ios开发应由浅入深,循序渐进。 掌握了初级框架就可以编写简单的ios程序了,包括简单的本地数据、图片、照片、音视频、地址簿、邮件、日程等的读写和显示。中级框架涉及面最广,包括所有类型框架,这里FOUNDATION、DATA、NET和TEST类又是基本的,涉及复杂数据类型、文件、数据库、各种网络连接、单元测试等,应尽量全面掌握,其它类型的框架可以根据兴趣或工作任务逐渐去学习或掌握。高级框架以图形、音视频内容为主,主要是提供了更低层和更灵活的硬件操作接口。仅在必要时再去深入学习即可。由此可见,一个初级ios开发者应掌握至少15个框架,一个中级ios开发者应掌握40~50个框架,一个高级ios开发者应掌握50~60个框架(至少包含10个高级框架)。 总结 简单总结来说,就是多读、多写、多思考、多讨论。多读主要包括:阅读博客、图书、WWDC 视频、官方文档、开源项目。多写就没什么好说了,没有写过几十万行代码是不能算熟悉一门语言的。多思考和讨论这个需要个人主动一些,遇到问题喜欢多问为什么,在多次重构和思考的过程中,我们就会慢慢积累出一类问题的 “最佳实践” 方式,成为自己宝贵的经验。 在这里我还是要推荐下我自己建的iOS开发学习群:681503716(验证编号:大鲨),群里都是学ios开发的,如果你正在学习ios ,小编欢迎你加入(闲聊,广告,培训勿扰~),今天分享的这个案例已经上传到群文件,大家都是软件开发党,不定期分享干货(只有iOS软件开发相关的),包括我自己整理的一份2018最新的iOS进阶资料和高级开发教程
小编是一名7年iOS开发人员,在这里诚挚邀请各位还在坚持iOS的人程序员,不管你是十年大牛还是三年渣硕,肯学肯交流就加入我私人的一个交流群681503716,验证编码:大鲨,里面都有关于iOS的学习资料一起努力进步,我们为iOS付出那么多,不应该随便放弃吧 什么样的代码才是好代码?衡量代码的好坏的指标或者维度有很多,比如性能好、架构好、高内聚等,这些指标的侧重点各不相同,不同的开发人员的关注的重点也各不相同。我个人更喜欢简单的可读性高的代码,我主要从以下几个维度衡量代码是否良好: 一、代码是可工作的写代码的目的是要为了解决特定问题的,因此无论如何,代码首先是可工作的,能解决特定的问题。可工作的包含有两层含义:1、从功能角度来说能满足用户的需求2、从性能角度来说要满足当前基本的性能需求。所以可工作是衡量代码好坏的前置条件,只考虑代码本身不考虑可工作性是舍本取末。 二、代码是可读性高的代码是开发人员来开发和维护的,而且在软件漫长的生命周期中,通常会由不同的开发人员来维护的,如果代码的可读性很差将来的维护就将是一个噩梦。我们写的代码是给开发人员看的,绝对不是给机器看的(编译后的代码是给机器看的,编译器会帮我们去掉无意义的空行等),因此代码必须首先是可读性高的。 那什么是可读性高的代码呢?从 coding style 角度来说,有意义的命名、添加必要的文档和注释、类和方法不要太长、每一行也不要太长、添加必要的空行以及必要的缩进等,具体可以参考《C++编程规范》和《重构改善既有代码的设计》。另外一方面就是从代码的结构来定义,例如代码是高内聚、低耦合的,代码是简单的,这三个方面下面会有详细描述。 三、代码是简单的 我们先来看一下什么是复杂的代码,比如说美其名曰为了代码的扩展性,使用了好多设计模式和软件开发原则,结果就是明明可以用很简单几行代码搞定的事情,结果用了几十行代码甚至更多,而且代码用了各种酷炫的技术,但是事实上大部分的扩展性可能一辈子也没有发生过,从敏捷开发角度来说,这是非常典型的过度设计。敏捷开发不是不考虑设计,只是不推崇过度设计,比如考虑 10 年后系统的扩展性是没有任何意义的,另外一种场景是只是做一个简单的后台管理系统,但是却花大量的精力考虑高并发也是没有意义的,过度设计的代码通常是复杂的。 所以在适度考虑代码的可扩展性的基础上,能不用设计模式就不要用设计模式,能不用新的、复杂的技术就不用新的、复杂的技术,技术够用就好,代码越简单越好,有人说代码太简单是不是有点 low,其实写出高质量的简单代码远比写出复杂的代码难度高,尤其是系统比较复杂时,保持代码的简单性难度是非常大的。 所以说简单的代码就是:代码所有人都看得懂,尤其是新人,但是又具备一定的扩展性和维护性,简单的讲就是简约而不简单。复杂的代码首先对读代码的人要求就很高,最终导致代码很难维护。代码是简单的是代码可读性高的一个方面。 四、代码是高内聚的 内聚是从功能角度来度量模块内的联系,代码关联性比较强的代码应该放在内聚在一起,形成一个独立的功能模块,可以是一个独立的类,也可以是一个微服务。其实判断代码是否内聚一个比较简单的方法就是看你能否给代码或者服务给一个贴切的名字,如果代码功能不内聚,我们是很难用一个简短的名字来表示它的含义的。 五、代码是低耦合的 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决与模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。耦合比较高的代码危害比较大,最常见的表现就是改一个模块的代码会影响许多其它模块,最终必然导致大家不敢修改旧的代码,只能不停的添加新的接口,系统的可维护性非常差。本文只是描述我心中的好代码,并不打算说明如何编写好代码,那需要太多的篇幅和太多的争议。所以,至此为止。 小编是一名7年iOS开发人员,在这里诚挚邀请各位还在坚持iOS的人程序员,不管你是十年大牛还是三年渣硕,肯学肯交流就加入我私人的一个交流群681503716,验证编码:大鲨,里面都有关于iOS的学习资料一起努力进步,我们为iOS付出那么多,不应该随便放弃吧
本篇文章是对bang神的文章移动App网络优化概述进行的总结,文章中也加了的一些自己的理解与扩展。 我们每次在做业务做网络请求的时候,想必每个人都思考过如何进一步优化网络请求吧,比如这三点包括: 速度:网络请求的速度怎样能进一步提升? 弱网:移动端网络环境随时变化,经常出现网络连接很不稳定可用性差的情况,怎样在这种情况下最大限度最快地成功请求? 安全:怎样防止被第三方窃听/篡改或冒充,防止运营商劫持,同时又不影响性能? 对基于浏览器的前端开发来说,网络这块能做的事情很少,但对于客户端 APP 来说,整个网络请求过程是自由控制的,可以做很多事情,很多大型 APP 都针对这三个问题做了很多网络层的优化,一些新的网络层协议像 HTTP2 / QUIC 也是在这些方面进行了不少优化,在这里边学习边整理,大致列举一下常见的做法。 一 速度 正常一条网络请求需要经过的流程是这样: 1、DNS 解析,请求DNS服务器,获取域名对应的 IP 地址。 2、与服务端建立连接,包括 tcp 三次握手,安全协议同步流程。 3、连接建立完成,发送和接收数据,解码数据。 这里有明显的三个优化点: 1、直接使用 IP 地址,去除 DNS 解析步骤。 2、不要每次请求都重新建立连接,复用连接或一直使用同一条连接(长连接)。 3、压缩数据,减小传输的数据大小。 依次来分析每个优化点我们能做什么 1 DNS DNS 完整的解析流程很长,会先从本地系统缓存取,若没有就到最近的 DNS 服务器取,若没有再到主域名服务器取,每一层都有缓存,但为了域名解析的实时性,每一层缓存都有过期时间,这种 DNS 解析机制有几个缺点: 1、缓存时间设置得长,域名更新不及时,设置得短,大量 DNS 解析请求影响请求速度。 2、域名劫持,容易被中间人攻击,或被运营商劫持,把域名解析到第三方 IP 地址,据统计劫持率会达到7%。 3、DNS 解析过程不受控制,无法保证解析到最快的IP 4、一次请求只能解析一个域名。 为了解决这些问题,就有了 HTTPDNS,这里有iOS版本接入文档HTTPDNS iOS客户端接入文档 ,原理很简单,就是自己做域名解析的工作,通过 HTTP 请求后台去拿到域名对应的 IP 地址,直接解决上述所有问题: 1、域名解析与请求分离,所有请求都直接用IP地址,无需 DNS 解析,APP 定时请求 HTTPDNS 服务器更新IP地址即可。 2、通过签名等方式,保证 HTTPDNS 请求的安全,避免被劫持。 3、DNS 解析由自己控制,可以确保根据用户所在地返回就近的 IP 地址,或根据客户端测速结果使用速度最快的 IP。 4、一次请求可以解析多个域名。 其余细节就不多说了,HTTPDNS 优点这么多,几乎成为中大型 APP 的标配。至此解决了第一个问题 — DNS 解析耗时的问题,顺便把一部分安全问题 — DNS 劫持也解决了。 2 连接 第二个问题,连接建立耗时的问题,这里主要的优化思路是复用连接,不用每次请求都重新建立连接,如何更有效率地复用连接,可以说是网络请求速度优化里最主要的点了,并且这里的优化仍在演进过程中,值得了解下。 keep-alive HTTP 协议里有个 keep-alive,HTTP1.1默认开启,一定程度上缓解了每次请求都要进行TCP三次握手建立连接的耗时。原理是请求完成后不立即释放连接,而是放入连接池中,若这时有另一个请求要发出,请求的域名和端口是一样的,就直接拿出连接池中的连接进行发送和接收数据,少了建立连接的耗时。 **实际上现在无论是客户端还是浏览器都默认开启了keep-alive,对同个域名不会再有每发一个请求就进行一次建连的情况,纯短连接已经不存在了。**但有个问题,就是这个 keep-alive 的连接一次只能发送接收一个请求,在上一个请求处理完成之前,无法接受新的请求。若同时发起多个请求,就有两种情况: 1、若串行发送请求,可以一直复用一个连接,但速度很慢,每个请求都要等待上个请求完成再进行发送。 2、若并行发送这些请求,那么首次每个请求都要进行tcp三次握手建立新的连接,虽然第二次可以复用连接池里这堆连接,但若连接池里保持的连接过多,对服务端资源产生较大浪费,若限制了保持的连接数,并行请求里超出的连接仍每次要建连。 对这个问题,新一代协议 HTTP2 提出了多路复用去解决。 多路复用 HTTP2 的多路复用机制一样是复用连接,但它复用的这条连接支持同时处理多条请求,所有请求都可以并发在这条连接上进行,也就解决了上面说的并发请求需要建立多条连接带来的问题,网络上有张图可以较形象地表现这个过程: HTTP1.1的协议里,在一个连接里传送数据都是串行顺序传送的,必须等上一个请求全部处理完后,下一个请求才能进行处理,导致这些请求期间这条连接并不是满带宽传输的,即使是HTTP1.1的pipelining可以同时发送多个request,但response仍是按请求的顺序串行返回,只要其中一个请求的response稍微大一点或发生错误,就会阻塞住后面的请求。 HTTP2 这里的多路复用协议解决了这些问题,它把在连接里传输的数据都封装成一个个stream,每个stream都有标识,stream的发送和接收可以是乱序的,不依赖顺序,也就不会有阻塞的问题,接收端可以根据stream的标识去区分属于哪个请求,再进行数据拼接,得到最终数据。 解释下多路复用这个词,多路可以认为是多个连接,多个操作,复用就是字面上的意思,复用一条连接或一个线程。HTTP2这里是连接的多路复用,网络相关的还有一个I/O的多路复用(select/epoll),指通过事件驱动的方式让多个网络请求返回的数据在同一条线程里完成读写。 客户端来说,iOS9 以上 NSURLSession 原生支持 HTTP2,只要服务端也支持就可以直接使用,Android 的 okhttp3 以上也支持了 HTTP2,国内一些大型 APP 会自建网络层,支持 HTTP2 的多路复用,避免系统的限制以及根据自身业务需要增加一些特性,例如微信的开源网络库 mars,做到一条长连接处理微信上的大部分请求,多路复用的特性上基本跟 HTTP2 一致。 TCP队头阻塞 HTTP2 的多路复用看起来是完美的解决方案,但还有个问题,就是队头阻塞,这是受限于 TCP 协议,TCP 协议为了保证数据的可靠性,若传输过程中一个 TCP 包丢失,会等待这个包重传后,才会处理后续的包。HTTP2的多路复用让所有请求都在同一条连接进行,中间有一个包丢失,就会阻塞等待重传,所有请求也就被阻塞了。 对于这个问题不改变 TCP 协议就无法优化,但 TCP 协议依赖操作系统实现以及部分硬件的定制,改进缓慢,于是GOOGLE 提出 QUIC 协议,相当于在 UDP 协议之上再定义一套可靠传输协议,解决 TCP 的一些缺陷,包括队头阻塞。QUIC协议虽然是基于UDP,但它不但具有TCP的可靠性、拥塞控制、流量控制等,QUIC 协议相对于 HTTP2 最大的优势是对TCP队头阻塞的解决,另外,QUIC协议具有TLS的安全传输特性,实现了TLS的保密功能,同时又使用更少的RTT建立安全的会话。 3 数据 第三个问题,传输数据大小的问题。数据对请求速度的影响分两方面,一是压缩率,二是解压序列化反序列化的速度。目前最流行的两种数据格式是 json 和 protobuf,json 是字符串,protobuf 是二进制,即使用各种压缩算法压缩后,protobuf 仍会比 json 小,数据量上 protobuf 有优势,序列化速度 protobuf 也有一些优势,这两者的对比就不细说了。可以看此文章protobuf 在iOS上的实践来进一步了解protobuf 压缩算法多种多样,也在不断演进,最新出的 Brotli 和Z-standard实现了更高的压缩率,Z-standard 可以根据业务数据样本训练出适合的字典,进一步提高压缩率,目前压缩率表现最好的算法。 除了传输的 body 数据,每个请求 HTTP 协议头的数据也是不可忽视,HTTP2 里对 HTTP 协议头也进行了压缩,HTTP 头大多是重复数据,固定的字段如 method 可以用静态字典,不固定但多个请求重复的字段例如 cookie 用动态字典,可以达到非常高的压缩率,这里有详细介绍。 通过 HTTPDNS,连接多路复用,更好的数据压缩算法,可以把网络请求的速度优化到较不错的程度了,接下来再看看弱网和安全上可以做的事情。 二 弱网 手机无线网络环境不稳定,针对弱网的优化,微信有较多实践和分享,包括: 1、 提升连接成功率复合连接,建立连接时,阶梯式并发连接,其中一条连通后其他连接都 关闭。这个方案结合串行和并发的优势,提高弱网下的连接成功率,同时又不会增加服务器资源消耗: 2、制定最合适的超时时间对总读写超时(从请求到响应的超时)、首包超时、包包超时(两个数据段之间的超时)时间制定不同的计算方案,加快对超时的判断,减少等待时间,尽早重试。这里的超时时间还可以根据网络状态动态设定。 3、调优TCP参数,使用TCP优化算法。对服务端的TCP协议参数进行调优,以及开启各种优化算法,使得适合业务特性和移动端网络环境,包括RTO初始值,混合慢启动,TLP,F-RTO等 针对弱网的这些细致优化未成为标准,系统网络库没有内置,不过前两个客户端优化微信的开源网络库 mars 有实现,若有需要可以使用。 三 安全 标准协议 TLS 保证了网络传输的安全,前身是 SSL,不断在演进,目前最新是 TLS1.3。常见的 HTTPS 就是 HTTP 协议加上 TLS 安全协议。 安全协议概括性地说解决两个问题:1.保证安全 2. 降低加密成本 在保证安全上: 1、使用加密算法组合对传输数据加密,避免被窃听和篡改。 2、认证对方身份,避免被第三方冒充。 3、加密算法保持灵活可更新,防止定死算法被破解后无法更换,禁用已被破解的算法。 降低加密成本上: 1、用对称加密算法加密传输数据,解决非对称加密算法的性能低以及长度限制问题。 2、缓存安全协议握手后的密钥等数据,加快第二次建连的速度。 3、加快握手过程:2RTT-> 0RTT。加快握手的思路,就是原本客户端和服务端需要协商使用什么算法后才能加密发送数据,变成通过内置的公钥和默认的算法,在握手的同时就把数据发出去,也就是不需要等待握手就开始发送数据,达到0RTT。 这些点涉及的细节非常多,对 TLS 的介绍有一篇雄文,说得很详细,在此推荐。 目前基本主流都支持 TLS1.2,iOS 网络库默认使用 TLS1.2,Android4.4 以上支持 1.2。TLS1.3 iOS 还处于测试阶段,Android 未查到消息。对于普通 APP,只要正确配置证书,TLS1.2 已经能保证传输安全,只是在建连速度上会有所损耗,有一些大型 APP 像微信就自行实现了 TLS1.3 的部分协议,早一步全平台支持。
引言 由于笔者是做 iOS 开发的,因此本文也仅对做 iOS 的同行们有针对性,其他方向仅供参考。 如果你: 1~3年左右工作经验,本科,非计算机相关科班出生,学校又比较一般。 实习企业不理想没有签,校招单位跟想象的有很大差距。 每天都为自己的前途感到焦虑,非常羡慕大厂同学的发展机会。 觉得自己是个有上进心,有热情,有执行力的人,相信自己能独当一面。 如果你跟上面的情况类似,想去大厂?那么现在就接着看下去吧。 光看这篇文章是没有用的,你要证明你符合最后一条要求,有执行力,最重要。 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要这是一个我的iOS交流群:681503716,验证编号:大鲨 不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长! 几点建议 你的情况并不特殊,市场上有大把像你这样的人,在供求关系上是很尴尬的。因此合适的工作机会会非常难得,大厂招1-3年人的情况是不多的,且竞争会非常激烈,可能会有十几甚至几十个人跟你抢一个岗位,对于这点要有心理准备。 以下几点建议是以笔者亲身经历出发,对情况类似的同学会有帮助。 正确的心态 如上面所说,竞争是很激烈的,没电话或者面试挂掉的情况也很正常。跳槽是场拉锯战,经常会持续数月。这不是你的问题,笔者自己就花了三个多月之久。因此这不是因为你水平不行,这是大家都会经历的过程。过程中还会有各种念头爆发出来,比如裸辞了专心找工作,又或者是想各种转方向转行,甚至跑去考研。 对于只有一年左右经验的同学来说,由于还没有得到过行业的认可,因此会有更多负面的情绪出来。例如面试面一次挂一次,原岗位水深火热的呆不下去,好不容易有面试机会,面一次挂一次,花了大把经历和金钱在路费和请假上,老大同事起疑心,请假借口都用完了还没找到,一次次失望而归,心态崩了,觉得自己完全不行。 这是必须要挺过去的一段时期,这时候你可能要找你女朋友聊聊人生让她鼓励鼓励你了。什么?没女朋友怎么办? 写好简历很重要 简历真的非常重要。不合适的简历可能会造成完全没有面试机会,或者面试过程中被虐出血最后挂掉。 关于简历,我的建议是: 两页以内,简洁大方就好,别花花绿绿的,也别贴照片,别写对方完全不在乎的东西。 才一年工作经验,如果不是天才,内容肯定不会多到两页放不下的。提炼一下语句,缩短篇幅。照片的话成倍增加简历容量不说,虽说长得好看的人,人生如同开了挂,如果长得没有开挂效果,还是别放了。跟岗位无关的东西别写了,招的是 iOS 开发,对方完全不会在意你会弹吉他还是会弹钢琴的。 推荐用 Markdown 写,其他也可以,最后一定要导成 PDF。 你一个Word文稿甩过去,你不知道 Mac 上的 office 打开很慢吗?你不知道格式会乱掉吗? 简历里的每一条内容都要做好被问的准备。 相当一部分面试官可能平时比较忙,或者是临时被喊过来面试的根本没有任何准备,因此就会照着简历一条一条问下来。因此简历上每一条内容都要保证有东西可以说。有些内容可能是面试官不擅长的,他会让你来说说这东西是什么,用来做什么的,这时候一定要说得出来东西。 针对简历做的准备是很容易做到的,一定要做,不要心存侥幸,想到时候糊弄过去。一旦发现你简历里写的内容你不会,你就完了。 项目经历要有亮点,少写没营养的东西。 如果每个项目都写了一大堆调用了xxx框架xxx库,可以说这些内容是没意义的。会用xxx库只能说明看得懂README会调用接口,这些是没有门槛的,提炼成一句话就可以了。尽量写一些有门槛的内容,如修改了xxx框架,做了特别的优化,这些才会引起面试官的兴趣。 用词尽量谦虚一点。 这点能避免简历写得太过了导致面试被血虐。精通肯定是红线,谁写谁死。熟练掌握出现一两处就可以了,那种从大学甚至中学就开始用的技能,熟练掌握是可以的。其他的了解、简单了解就可以了。那些写过一个小东西就敢说熟练的人面试会很危险。 准备一个杀手锏。 一年左右经验的岗位竞争是非常激烈的,大厂的一个岗位可能有数十人跟你竞争,你必须是最强的那个才行。因此从现在开始,准备一个细分领域做一点点深入的研究,比如优化,动画,安全等,写上。如果面试官有兴趣,你又能说得出内容来,胜出的概率就很大了。 保持与同行的交流 与同行交流是学习的一条捷径。通过跟别人的对比,才能知道差距在哪,对知识面的深度和广度都是很有帮助的。一个人单打独斗就如同井底之蛙,完全不知道外面的世界长什么样。当然真正单打独斗的人是绝对看不到我的文章的,如果你能看到我的文章,说明还不算太糟。 想进大厂,肯定是要向大厂的同行们取取经的。他们最近在做什么,会什么,看过什么书,啥时候招人,能不能内推等等。这些信息能像灯塔一般给你指出一条明路。 自己补补课 从跟同行接触开始,就会逐步发现差距。剩下的就很简单了,业余时间自己学习吧。起初一个人战斗时,学习资料来源是很匮乏的,但跟人交流多了以后,来源就很丰富了。还记得我在文章开头提到过的上进心和执行力吗?到这里你可能会有一个书单或者博客列表,大量的文章。消化需要一定的时间,能否坚持下来就很关键了。 我同事当初给我的两个建议: 当你觉得这玩意真tm难,打死我也学不会的时候,去学别的,过一段时间回来再试试。 抱着怀疑的态度去看技术文章,如果是可以验证的东西,自己写 Demo 验证一下,加深印象。 大家都是经历过高考的人了,学习这种事,相信大家都懂的。 充分的面试准备 大厂的面试是有套路的。 这些套路你可以从博客等地方获得,也可以亲自去面几次来获得。知识点重在平时积累,面试前复习。面试最终也是看运气和缘分的,算法和基础题能考的东西非常多,即使复习时需要砸足够的时间下去也未必能命中考点,自己尽力即可。 每一次面试的公司、岗位不同,他们的侧重点肯定也是不同的。了解对方的业务和岗位职责以后去有针对性地准备会更有效,切勿盲目准备。例如面直播公司的开发岗位,各种视频流和直播协议的东西最好准备一下。 自信,不要怂。面试时候就觉得自己是大神,最坏的结果不就是没过嘛。 如果面试没有过,摆正心态。这不是谁的错,只是缘分未到。塞翁失马焉知非福,或许马上会有更好的机会出现呢? 总结 最后也祝愿有上进心有执行力的同学们能如愿进入想去的公司。
模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程序中。在iOS开发中MVC的机制被使用的淋漓尽致,充分理解iOS的MVC模式,有助于我们程序的组织合理性。 模型对象 模型对象封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算。例如,模型对象可能是表示游戏中的角色或地址簿中的联系人。用户在视图层中所进行的创建或修改数据的操作,通过控制器对象传达出去,最终会创建或更新模型对象。模型对象更改时(例如通过网络连接接收到新数据),它通知控制器对象,控制器对象更新相应的视图对象。视图对象 视图对象是应用程序中用户可以看见的对象。视图对象知道如何将自己绘制出来,并可能对用户的操作作出响应。视图对象的主要目的,就是显示来自应用程序模型对象的数据,并使该数据可被编辑。尽管如此,在 MVC 应用程序中,视图对象通常与模型对象分离。 在iOS应用程序开发中,所有的控件、窗口等都继承自 UIView,对应MVC中的V。UIView及其子类主要负责UI的实现,而UIView所产生的事件都可以采用委托的方式,交给UIViewController实现。控制器对象 在应用程序的一个或多个视图对象和一个或多个模型对象之间,控制器对象充当媒介。控制器对象因此是同步管道程序,通过它,视图对象了解模型对象的更改,反之亦然。控制器对象还可以为应用程序执行设置和协调任务,并管理其他对象的生命周期。 控制器对象解释在视图对象中进行的用户操作,并将新的或更改过的数据传达给模型对象。模型对象更改时,一个控制器对象会将新的模型数据传达给视图对象,以便视图对象可以显示它。 对于不同的UIView,有相应的UIViewController,对应MVC中的C。例如在iOS上常用的UITableView,它所对应的Controller就是UITableViewController。 Model和View永远不能相互通信,只能通过Controller传递。 Controller可以直接与Model对话(读写调用Model),Model通过Notification和KVO机制与Controller间接通信。 Controller可以直接与View对话,通过outlet,直接操作View,outlet直接对应到View中的控件,View通过action向Controller报告事件的发生(如用户Touch我了)。Controller是View的直接数据源(数据很可能是Controller从Model中取得并经过加工了)。Controller是View的代理(delegate),以同步View与Controller。
虽然你有可能在一周内学习到iOS开发的本质,但想要精通iOS开发需则要花费更长的时间。问题是你如何从新手变成专家?在这篇文章中,我们主要就这个主题给出一些建议: 1.练习,练习还是练习 iOS开发没有捷径可走。这是在讨论这个话题之前需要强调的东西。如果你仅仅在周末的4PM和5PM之间学习/练习编程,那么你不会成为一个熟练的iOS开发者。这么做可能会很有趣,但是它很难让你成为一个老练的专业的iOS开发者。除了极少数的人,大部分人都需要不断的练习和实践。如果你还没有准备好投入大量时间,这时候最好重新审视你的目标和理想。 2.向其他人学习 提升开发技能和采用最佳实践的最好策略之一是学习别人的代码。这不仅仅意味着浏览Stack Overflow,但更重要的是其他开发者的各种开源类库和代码片段。 不管何时你一头扎进各种类库的学习,比如AFNetworking或者Magical Record,很关键的一点是不要被各种代码淹没。有可能你不理解各种库中的代码,但这不是真正的关键,重点是从一个更高的层次来看源码和尽可能多地学习,比如命名规范、最佳实践以及设计模式等。 除了学习别人的代码外,创建你自己的类库是一个最好的学习方法。不久前,我用Core Data作为数据层开发了一款app,我没有使用Magical Record,我决定通过学习Magical Record创建自己的库,并重新创建了我需要的功能。这种方法不仅帮我创建了一个更加灵活的库,也让我学习到很多Magical Record内部的工作原理。 3. 不要仅仅复制和粘贴 这一点让我看到了正确学习方式的另一个关键面:不要不动脑子简单地复制和粘贴。我们常常会使用我们在Stack Overflow或者苹果开发者论坛找到的各种代码片段,但是不要为了偷懒简单地复制粘贴你从网络上找到的代码,这样你很难彻底理解它。最大的危险在于这可能会导致意外的行为,以致于你的代码以后都很难调试和修复。 有时候,复制别人的代码片段看着可能已经解决了你遇到的问题,但是我强烈建议不要这么做。阅读代码,理解你往代码库添加的东西,可能的话定制你需要的解决方案。不用说,这并不适用于开发者积极维护的库或框架。 4. 模式 Cocoa和Objective-C在很多方面都不同于其他编程语言和环境。这意味着他们有自己独特的模式和最佳实践。我确信你已经熟悉了一些常见的模式,比如代理和通知。但还有很多模式可以开发过程中帮助你,比如单例模式、观察者模式以及通用模式等。Cocoa Fundamentals Guide很好地概述了Cocoa中大部分常用模式。 5.了解你的工具 作为一个开发者并不仅仅是理解语言和框架,理解你每天使用的工具同样重要。对于iOS开发来说,这意味着你需要很好地理解Xcode和其他可能用到的工具,比如PonyDebugger和Charles。 6.与时俱进 即使你不能参加苹果一年一度的开发者大会--WWDC,但浏览大量的会议视频是个很好的学习方法,你可以观看你感兴趣的专题视频,它们都是由在某方面擅长的工程师演讲,他们可以告诉你一些细节信息以及相关使用说明。另外,你还可以多关注优秀的开发者,他们会定期写一些技术文章,都是很实用的文章 7.学习其他语言 学习新语言或者使用新框架可以对软件开发的整体理解有明显提高。这种方法的优点在于不会限制你对最熟悉语言的认知和它潜在可能性的理解。对于软件开发来说,没有最好的语言,每种语言都有自己的优势和劣势。但是好的一面在于或多或少他们都有所不同,这使得学习新语言变得更加有趣和更有启发性。 总结 如果你不想花费时间成为一个更好的程序员,那么你可能要重新考虑下为什么你把成为一个程序员摆在首位。但如果你会因为一个可以帮你的新类库或者开发工具而兴奋,那么随着时间的流逝,你在提高自身技能方面可能不会有什么问题。你真的必须热爱你所做的事情,并在这面擅长,这一点尤其适用于编程。不管什么人告诉你,你不会在一夜之间成为一个老练的开发者,但如果你保持不间断的学习,并细心打磨自己的“手艺”,那么你终有一天会成为一名开发专家。