天气不错哦
天气不错哦 给你的AppDelegate减减肥我们在开发过程中,总是不可避免的需要加入第三方的SDK,比如第三方的推送、分享、统计、IM等功能。这些SDK大多数需要在AppDelegate的回调中设置响应方法。特别是以下几个方法最为常用: //这个回调中通常需要设置第三方SDK的AppKey等初始化信息
//这两个回调中通常需要回传给SDK,供SDK处理回传参数
//如果使用第三方推送,这两个回调中通常需要传给第三方SDK
还有其他许多一些回调,也是第三方SDK需要关心的,全部写在AppDelegate里,那么AppDelegate会显得十分臃肿杂乱并且与各个SDK耦合特别严重。(如果集成了多个SDK,简直不能忍) 解决方法实际上部分回调是有通知的比如上边的
对应会有一个名为
的通知,那么针对这样的有通知的回调就可以解耦并且剥离到另外一个类中。 新建一个类名为 SDKLaunch的类,为了解耦在+load 方法中加入Observer,
这样只需要在项目中加入SDKLaunch 就可以自动设置好了,这样那些只需要在有通知的回调中设置的SDK(如crash统计类的SDK), 在AppDelegate 一行代码都没有并且已经解耦,SDKLaunch加入项目就可以配置完成。 ##进一步(需要SEL、 IMP等知识) 但是很多SDK还是需要在没有通知的回调中设置的(比如上面说的第三方推送),难道就没有办法了么?不是的,还有大杀器 runtime。我们可以用runtime中方法去hook AppDelegate中的回调方法来实现。 那就在+load方法的时候(此时AppDelegate还实例未创建)把需要的回调替换成SDKLaunch中的方法。这样当AppDelegate创建并调用回调方法的时候后自然就跑到SDKLaunch的方法中去了 以第三方推送举例需要的回调为例:
我们需要在SDKLaunch拿到回调,首先要
获取 AppDelegate的Class targetClass Class targetClass = objc_getClass("AppDelegate"); 然后我们需要3个SEL: //获取新的方法SEL SEL newSEL =@selector(newApplication:didRegisterForRemoteNotificationsWithDeviceToken:); //获取原始的需要Hook的方法SEL //获取一个默认的SEL 以及2个IMP: IMP newIMP = class_getMethodImplementation([SDKLaunch class], newSEL); IMP defaultIMP = class_getMethodImplementation([SDKLaunch class], defaultSEL); 在SDKLaunch实现这个2个IMP //添加默认方法和新的方法 然后把对应的SEL 和 IMP加入到AppDelegate中 class_addMethod(targetClass, newSEL, newIMP ,nil); class_addMethod(targetClass, originalSEL, defaultIMP ,nil); 再交换originalSEL、newSEL Method oldMethod = class_getInstanceMethod(targetClass, originalSEL); Method newMethod = class_getInstanceMethod(targetClass, newSEL); method_exchangeImplementations(oldMethod, newMethod); 需要注意的是 这里的 defaultSEL、defaultIMP 很重要,当AppDelegate没有实现回调时,可以让hook中的方法来代替 AppDelegate 实现,不至于找不到方法而Crash。 这样无论是用到什么样回调的SDK 都可以不在AppDelegate加一行代码,实现 解耦并且自动初始化。 甚至可以分模块如分成:ShareLaunch、ChatLaunch等到不同的类中实现不同的SDK初始化、回调等工作。 另外要做的就是把hook的方法封装封装下用起来更方便。 用枚举类型优雅的控制View布局在iOS 开发过程中经常会遇到类似这样的页面: 这3个按钮(按钮宽度不同,会受到按钮文字数量不同的影响)在不同的场景下,会有一个或者几个隐藏(如:只有“咨询”和“留言”,为了页面的美观一般都是居左对齐,也就是“留言”会到“反馈”的位置) 常见的做法:(三颗按钮对应成 button0、button1、button2)先定义2个常量
把按钮按设置好宽度高度放入一个数组中,再设置之前全部Hidden,然后把需要显示的按钮进行布局,代码类似如下:
这种方式的缺点是: 1、需要把按钮暴露在外面。很多时候仅仅是展示用,并不想把按钮暴露出去。 2、按钮的从左到右的排列顺序完全依赖外部传入。(如果需要内部控制要加额外代码,虽然可以实现但是显得不干净) 优化下,调用显示按钮的时候只传入需要显示按钮的index数组:
这样虽然不需要把按钮暴露出去了并且按钮的排序顺序也是由内部决定的。但是导致了接口不够清晰,需要传什么不明确,正真调用的时候还要进到内部看下按钮的顺序T_T,还是不优雅。 2.使用枚举类型定义一个枚举类型如下:
然后定义一个变量,开放给外部
调用如下,非常简单明确:
内部在重写的setter中做处理
是不是感觉好了许多呢?:) 我在这个公司的最后一天,迎来新的开始。 使用TodayExtension 导致意外登出的问题一、问题最近项目上使用了TodayExtension后发现,原本已经登录的用户会在很“诡异”的情况下登出。(开始并未找到重现规则所以这么认为) 但是调试状态下却从未出现。 二、分析下结合业务逻辑仔细整理了下,登出的三种情况: 1、用户手动登出 (排除) 2、Token过期并且RefreshToken也过期(这个也排除,因为我们 RefreshToken过期时间得好几个月,却有上午登录下午就意外登出了的情况) 3、从KeyChain读取或者存入失败。 看来只有第三种情况导致的,但是调试状态下却抓不到,只能把KeyChain读取与写入时的错误信息存成Log文件,出现问题时查看Log。 出现后抓到一条不寻常的错误
Apple的注解
用户操作不允许。。看来是被系统阻止了, 为什么被阻止了呢,翻了半天Security framewoeks的头文件终于在 SecItem.h发现了这么样一段:
大意是说 这几个value 是给 kSecAttrAccessible 这个key 使用的,如果不符合value权限规定会返回 errSecInteractionNotAllowed。官方对这个key的说明大致是
这是KeyChain另外一种更加细致的权限分配。 看看它的几个值
其实大致含义从他的命名上能看出来,而Apple的注释中写的更为详细 * kSecAttrAccessibleWhenUnlocked只有设备解锁的情况下有效,并且当迁移到其他设备时会加密备份到别的设备。 * kSecAttrAccessibleAfterFirstUnlock只有设备在重启后的一次解锁后的情况下有效,并且当迁移到其他设备时会加密备份到别的设备。 * kSecAttrAccessibleAlways任何情况下都能访问无论设备是否解锁,并且当迁移到其他设备时会加密备份到别的设备。除了给系统使用不推荐使用 * kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly这个是iOS8、OSX10.10后新增的属性,只有设备设置了密码并在解锁状态才有效,如果关闭设备密码在这里存储的数据会被删除。数据只保留在本机,如果迁移到其他设备这里的数据不会被迁移。这个权限显然比较严格,推荐给非常重要的数据使用。 * kSecAttrAccessibleWhenUnlockedThisDeviceOnly只有设备解锁的情况下有效,数据只保留在本机,如果迁移到其他设备这里的数据不会被迁移。 * kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly只有设备在重启后的一次解锁后的情况下有效,如果迁移到其他设备这里的数据不会被迁移。 * kSecAttrAccessibleAlwaysThisDeviceOnly任何情况下都能访问无论设备是否解锁,如果迁移到其他设备这里的数据不会被迁移。 三、解决了解了这些就可以回过头来看看 ,之前的问题就可以大致猜测了,应该是在锁定状态打开了TodayExtension,访问了KeyChain,导致发生errSecInteractionNotAllowed 错误,用户登出。而调试状态下都是没有锁定设备的,所以一直没有出现。根据猜测尝试操作后就重现了这问题。只需要在设置成 kSecAttrAccessibleAlways就可以了。但是需要注意,因为之前的数据已经是 WhenUnlocked 有效的,所以当有权限的时候要把原来的数据重新存成Always 这样才能正常访问。 四、总结下这些权限实际上是这样几种权限的组合 1、是否单台设备2、是否解锁3、是否第一次解锁4、是否有密码(iOS8)官方并没有说明kSecAttrAccessible默认值,但是大致可以从现象上猜测下:是需要解锁中的其中一种。但一定不需要是第一次解锁,所有默认值只有三者之一: kSecAttrAccessibleWhenUnlocked kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly kSecAttrAccessibleWhenUnlockedThisDeviceOnly 具体是哪个没有具体考证,但是从他家讨论的来看应该是kSecAttrAccessibleWhenUnlocked,这也符合苹果一贯把第一个作为默认值的风格。 ——-2014.12.30 补充权衡了下,这个方式只能说从技术上解决了问题,但其实仔细从产品或者用户角度考虑当用户的设备被锁定时,其实并不应该去访问用户的登录信息的,这样可以起到保护用户信息的作用(因为TodayExtension是可以在锁定状态下打开的)。我觉得更好的方式是从产品上提示用户解锁设备后才能执行一些需要获取用户登录信息才能继续的操作。这应该也是 Apple设计这样的权限的初衷吧。 Extension下的单例生命周期最近项目中用到了 ShareExtension ,发现了个的现象,如果在Extension 中使用了单例,其生命周期是跟随Host app的,也就是说如果在Safari下打开ShareExtension,那么只要Safari不被Kill掉,无论如何关闭打开Extension,其使用的单例永远是同一个。TodayExtension 经过测试也是一样(这样的话就不知道TodayExtension下的单例什么时候会生成新的,重启iOS?!)。 一开始感到挺惊讶的,但是细想下来,确实应该这样,Extension 对Host app来说只是一个插件(页面)而已。只是之前把Extension想象的太过独立。 测试过程很简单:
看下项目目录
是一个单例里面只有1个类方法,返回一个单例
然后新建Extension的Target,并且把 InstanceObject 加入到Extension的编译中 然后在Extension的ViewController的 -(void)viewDidLoad 中加入打印
然后在不关闭Safari的前提下打开、关闭Extension,可以看到InstanceObj Address 是同一个,而ExtensionViewController一直在变化
初识iOS8 Today ExtensioniOS8正式版发布已经有段时间了,今天看了下Extension 相关的新功能接口的使用。 Apple 提供了6个位置(类型)的Extension:
其中最强大实用的应该就是 Custom Keyboard(自定义键盘)和 Today Extension(通知中心的“Today”Tab扩展) 对TodayExtension比较感兴趣就从它下手了 一、 创建带有Extension的项目首先使用Xcode6新建一个项目叫“MyApplication”的项目,为了简单起见就使用 Single View Application。然后新增一个Target (File->New->Target),选择Application Extension 的 Today Extension 类型。 填上Product Name,就可以了这样就一个带有TodayExtension的项目建好了。 二、Hello,Extension!项目建立后看到的目录是这样的 上下2个目录结构是不是很像呢~TodayExtension下的MainInterface.storyboard 就是会展示在通知中心的“Today”Tab下的视图。 Target选择TodayExtension Run!通知中心中已经呈现刚才的视图了: 如果这个默认的视图不够高不能显示下全部内容可以通过设置TodayViewController的preferredContentSize来调整。 三、研究下1、首先刚才注意到“MyApplication”和“TodayExtension”目录很像,都有个info.plist 文件,看看TodayExtension下的info.plist: 它拥有独立的一个APP必要的基本信息 和 特有的NSExtension信息。从这点上看出Extension其实就是一个高度独立APP。 为什么说是高度而不是完全独立呢? 第一、是因为它虽然拥有独立的Bundle identifier,但必须要以它的主程序的Bundle identifier为前缀,如果尝试修改掉这个前缀就不能Run起来了(猜测iOS是通过Bundle identifier判断从属关系的)。 第二、是因为他们使用的都是同一个沙盒,即主程序的沙盒,因此Extension可以随意读写主程序沙盒内文件以及KeyChain。 2、看看TodayExtension生命周期,在以下2个方法中加入打印
有两个特性: 1、 当通知中心的Tab是选中Today的时候: 拉出通知中心时,通知中心拉到底才会生成这个TodayExtension实例 收起通知中心时,只要一移动通知中心就会把TodayExtension实例释放 如下图: 所以当下拉通知中心的时候其实显示的是通知中心上一次的截图,而当向上划收起的时候显示的是滑动之前的截图。这也是动画效果的常用做法。 2、 只要通知中心的Today Tab不显示就不会生成这个TodayExtension实例或已经生成的就会释放。 也就是说Extension 的生命周期和主程序是无关的,是和Extension的宿主程序(这里是通知中心的Today Tab)相关。 结合这次发现的Extension特点,对Extension 、主程序、宿主程序之间的关系打个比喻 一个城市只有一个市公安局(主程序),但是会在不同的地方(宿主程序)设置不同的派出所(Extension),没有市公安局就不会设立派出所(从属关系),派出所可以访问市公安局的数据库(沙盒),公安局下班了派出所还是可以加班的(高度的独立性),但是如果公安局没有设立派出所工作效率就不高、不方便(插件的扩展功能) 先这么点吧。 iOS APP 重签名的问题之前有朋友想实现ipa包内的资源文件修改,并且根据不同的资源生成各自新的ipa包。 想想有2种方式: 1、修改完资源文件后编译打包2、直接对ipa包内的修改第一种显然不是最优方案有2个缺点 1、需要在服务端存放客户端代码2、消耗性能过程时间长所以第一种PASS 那就只剩下第二种了。 一、为什么要重签名为了校验资源文件的完整性,在编译打包成ipa后,会/Payload/xxx.app/_CodeSignature/目录下生成一个CodeResources文件
内容类似:
key是文件的名称,data 是文件的hash值应该使用的是SHA1+BASE64的方式加密(猜测)。 所以如果只修改资源文件 不修改这个CodeResources的话,安装ipa的时候就会验证失败导致安装不了。 二、如何重签名使用 Xcode Command Line Tools 有2种方式: 1、使用xcrun命令这种方式是最方便的只要对xxx.app文件执行,就能输出一个重新签名好的.ipa文件。 命令如下:
2、使用codesign命令这种方式也是网上流传最广的方式:
不过这种方式输出的还是个.app文件,需要把这个YourAppName.app放到 名为Payload的文件夹,然后用zip压缩Payload文件夹,把生成的Payload.zip后缀改成.ipa,才能使用。 三、有什么问题使用xcrun命令来重签名打包完美,没有任何问题。 使用codesign签名的包实际测试中,可以安装成功运行也没有问题, 但是使用NSKeyedUnarchiver、NSKeyedArchiver时 读取写入信息都会失败报错误号为-34018的错误。 四、怎么办为什么用xcrun 就可以 用codesign就不行呢? 尝试使用:
把xcrun的日志输出看看它时怎么做的。 其实xcrun内部也是使用codesign来签名的,不同的是它多了个参数
所以如果要用codesign来签名应该是这样的:
经测试已经解决KeyChain不能用的问题。 五、如果你的需求也是这样(重新签名->打成ipa包),那还是使用xcrun吧!方便快捷。几种可能引起[UIImage imageNamed:@“imageName”] == null 的情况几种可能引起[UIImage imageNamed:@“imageName”] == null 的情况 资源为
代码:
造成模拟器中正常显示真机中返回null。
代码:
资源后缀是png,其实真实格式并不是png,造成模拟器中正常显示真机中返回null。 使用CocoPods&Git Branch方便构建多渠道的IPA包(二)在前面一篇《使用CocoPods&Git Branch方便构建多渠道的IPA包(一)》 中介绍了思路及实现了更换颜色的简单效果,当然如果在配置的项目中放入不同的图片、Icon资源,就可以换肤了。 这次再来点稍微高级点的。实现不同的包不同的 APP DisplayName 以及 Bundle Identifier 其实原理上相同 , 不同的是 这次建的文件是个Xcode 能读懂的配置文件,这个文件就是 看看里面的信息 应有尽有: 好了 我们只要拷贝一分,分别放到配置项目Configuration 不同分支下,然后修改成 不同的值。 然后 额外需要做的就是把Xcode 读取配置文件的路径改成Configuration 项目下的 plist 文件,这个设置值位于 Targets->Demo->Build Setting 下: 把它修改为:
并删除项目Supporting Files 下的 这样引入不同分支的 Demo项目 就有了不同的 APP DisplayName 以及 Bundle Identifier,总之只要是 接下来就是我们的终极目的批量打包。当需要3个以上不同IPA包的时候 ,Xcode打包的工作就显得很痛苦了,根本无法忍受 Xcode 提供 Command Tools 有打包功能这样我们需要的批量打包功能就可以用bash 来实现了。 思路:根据设定的 branch 列表 做循环,单个循环过程是 修改Podfile 中引入配置项目的 branch 并 执行编译打包。 为了批量打包 先定义一些必要的参数: //branch 的列表
//证书的列表
//配置文件的列表 profiles=(“branchA_appStore” “branchB_appStore”) //需要Archive的scheme scheme=“Demo” //项目workspace的名称 workspaceName=“Demo” //打包输出的目录 outPutDir=“build/Release-iphoneos/” //配置项目的名称(Podfile 中的那个配置项目的名字) configurationProjectName=“Configuration” 这个几个参数不多说了 和 用Xcode 打包是一样的。 首先使用 xcodebuild 命令 Archive 项目生成 .xcarchive :
然后使用 xcarchive 文件 打包成 .ipa 文件:
完整脚本 点击 这里下载 打包时只要执行 然后就可以去泡咖啡啦,即使时20个包也就喝一杯咖啡的时间! |
|