Index

iOS中CocoaPods的使用整理

CocoaPods的使用总结与整理,来自至于巧叔这一篇blog和自己的使用经验总结。

CocoaPods使用来做iOS项目工程所使用的三方库的依赖管理和维护。它的项目在git上的源码地址请点击这里。而我们做实际项目时,不可避免的要使用三方库,这时CocoaPods绝对是个利器,CocoaPods会自动对三方库做一些submodule的处理、配置(如禁用ARC(-fno-objc-arc),关闭三方库的警告等) 以及更新。

CocoaPods的安装与使用

安装方法

CocoaPods的安装需要Ruby,而Mac都默认自带有Ruby环境,直接使用安装CocoaPods即可。默认的下载源是 https://rubygems.org/,天朝上国的墙内是用不了的。所以我们要先重置ruby源:
// 建议先升级一下
sudo gem update --system

// 替换源
gem source -l    // 查看源,如果已经是taobao的源,跳过以下2
gem sources --remove https://rubygems.org/
gem sources -a http://ruby.taobao.org/   // 替换源

// 开始安装
sudo gem install cocoapods
pod setup

使用

如果是自己新建工程,那么使用的第一步是新建一个Podfile文件。 样例如下:

platform :ios, '7.0' pod 'SDiPhoneVersion', '~>1.1.2' pod 'AFNetworking', '~>2.0' pod 'JSONKit-NoWarning', '~> 1.2'

Podfile文件的语法见这里,简单易学。

Podfile最好放在工程目录下面,当然也可以是其他位置(不过使用不方便)。以放在工程目录下为例:

cd "your project home" pod install

以后如果Podfile有更新,在工程目录下执行pod update

注意:在install或者update过程中,要退出workspace或者Xcode,若不退出,这个过程中不要做其他的操作 等待结束,不然可能会出现:工程打不开的情况 Can’t open project from workspace 这是血泪的教训啊

添加新的三方库时,使用pod search,能够搜索到的就是能够使用Cocoapods维护的,上样例:

`$ pod search AFNetworking

-> AFNetworking (2.5.4) A delightful iOS and OS X networking framework.
pod ‘AFNetworking’, ‘~> 2.5.4’()

  • Homepage: https://github.com/AFNetworking/AFNetworking
  • Source: https://github.com/AFNetworking/AFNetworking.git
  • Versions: 2.5.4, 2.5.3, 2.5.2, 2.5.1, 2.5.0, 2.4.1, 2.4.0, 2.3.1, 2.3.0, 2.2.4, 2.2.3, 2.2.2, 2.2.1, 2.2.0, 2.1.0, 2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-RC3, 2.0.0-RC2, 2.0.0-RC1, 1.3.4, 1.3.3, 1.3.2, 1.3.1, 1.3.0, 1.2.1, 1.2.0, 1.1.0, 1.0.1, 1.0, 1.0RC3, 1.0RC2, 1.0RC1, 0.10.1, 0.10.0, 0.9.2, 0.9.1, 0.9.0, 0.7.0, 0.5.1 [master repo]
  • Subspecs:
    • AFNetworking/Serialization (2.5.4)
    • AFNetworking/Security (2.5.4)
    • AFNetworking/Reachability (2.5.4)
    • AFNetworking/NSURLConnection (2.5.4)
    • AFNetworking/NSURLSession (2.5.4)
    • AFNetworking/UIKit (2.5.4)

-> AFNetworking+AutoRetry (0.0.5)
Auto Retries for AFNetworking requests
pod ‘AFNetworking+AutoRetry’, ‘~> 0.0.5’

  • Homepage: https://github.com/shaioz/AFNetworking-AutoRetry
  • Source: https://github.com/shaioz/AFNetworking-AutoRetry.git
  • Versions: 0.0.5, 0.0.4, 0.0.3, 0.0.2, 0.0.1 [master repo]

// 此处省略若干行`

最简单的写更新Podfile的方法,就是复制其中的加粗斜体部分到Podfile中即可。


关于.gitignore

当你执行pod install之后,除了Podfile外,CocoaPods还会生成一个名为Podfile.lock的文件,你不应该把这个文件加入到.gitignore中。因为Podfile.lock会锁定当前各依赖库的版本,之后如果多次执行pod install 不会更改版本,要pod update才会改Podfile.lock了。这样多人协作的时候,可以防止第三方库升级时造成大家各自的第三方库版本不一致。
CocoaPods的这篇官方文档也在What is a Podfile.lock一节中介绍了Podfile.lock的作用,并且指出:

This file should always be kept under version control.


使用私有的pods

我们可以直接指定某一个依赖的podspec,这样就可以使用公司内部的私有库。该方案有利于使企业内部的公共项目支持CocoaPods。如下是一个示例:

pod 'MyCommon', :podspec => 'https://yuantiku.com/common/myCommon.podspec'


不更新podspec

CocoaPods在执行pod install和pod update时,会默认先更新一次podspec索引。使用–no-repo-update参数可以禁止其做索引更新操作。如下所示:

pod install --no-repo-update pod update --no-repo-update


生成第三方库的帮助文档

如果你想让CococaPods帮你生成第三方库的帮助文档,并集成到Xcode中,那么用brew安装appledoc即可:

brew install appledoc

appledoc的介绍和使用,google之。


给自己的开源项目创建podspec

我们可以给自己的开源项目创建podspec文件,命令如下:

pod spec create custom_pod_spec_name

之后会得到一个custom_pod_spec_name.podspec文件。下面的工作就是修改.podspec文件中相关内容。
Pod::Spec.new do |s|

  s.name         = "test"
  s.version      = "0.0.1"
  s.summary      = "A short description of test."

  s.description  = <<-DESC
                   A longer description of test in Markdown format.

  s.homepage     = "http://EXAMPLE/test"
  # s.screenshots  = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"


  s.license      = "MIT"


  s.author       = { "***" => "***@**" }
  
  s.source       = { :git => "http://EXAMPLE/test.git", :tag => "0.0.1" }


  s.source_files  = "Classes", "Classes/**/*.{h,m}"
  s.exclude_files = "Classes/Exclude"

end
好多内容是自解释的注释,按照注释修改完成后,删掉注释,大概就是上面的代码的样子。

以下是文件修改的方法


s.source s.source_files

这里的 source 我们看出是一个git 的地址,这里我们调试的时候,可以先暂时设置成本地git,调试完毕之后就可以发布 增加tag。想要最新的代码只需要这样设置就好

{ :git => "https://github.com/studentdeng/ShareCenterExample.git"}

s.source_files表示源文件的路径,注意这个路径是 相对podspec文件而言 的。
我们的git项目中,并不是所有的代码都需要被引用到我们的代码中,通常project还会包括一些example,test cases等,这里的 source_files 就是用来指定一些文件夹或文件。

s.frameworks s.library

这里配置的就是我们的framework 和 library,这里注意一下library的名字规则就好,需要用到的frameworks,不需要加.frameworks后缀

vendored_libraries

用来指定外部的静态库。

s.prefix_header_contents

用来指定预编译的配置。

部署我们的配置到cocoapods中

cocoapods的代码配置文件是在这里Specs
这里最好是去fork一个自己的project,然后保存一个自己或是团队的配置,这样不会在更新cocoapods的时候,丢掉自己的配置。当然,如果觉得自己搞的还不错,也可以去pull requests。
在之前提到的目录~/.cocoapods/repo/master/ 下面,我们可以看到已经有超级多的项目了,我们可以也可以通过 $ pod search XXX 来查找项目,或是直接在这个文件夹下面找。

LICENSE文件

CocoaPods强制要求所有的Pods依赖库都必须有license文件,否则验证不会通过。license的类型有很多种,详情可以参考网站

注册Truck

如果已经注册过了,可以跳过这一步。 CocoPods从0.33加入了Trunk,这样方便了发布自己的Pod。注册很简单,命令如下: pod trunk register your-email "your nickname"

然后就可以使用如下命令来发布和升级自己的Pod: pod trunk push NAME.podspec

在使用Trunk发布前,要讲自己的代码提交到github上,并打上tag。 Podfile内书写这种自定义的Pod的格式如下:

pod 'name', :git => 'custom source url', :tag => 'tag version'

发布成功后就可以用 pod search命令搜索到自定义的 pod 就说明成功了。
如果希望更多的了解cocoapods,还是需要去Github上面。

参考资料:

CocoaPods

发布自己的pods到CocoaPods trunk

CocoaPods详解之—-制作篇

CocoaPods 手把手五分钟教你制作自己的podspec文件

项目管理:CocoaPods建立私有仓库

使用CocoaPods开发并打包静态库

在Swift中使用CocoaPods

使用Cocoapods创建私有podspec

为自己的库添加CocoaPods支持

iOS中如何hook消息

转载自一片枫叶的blog

在windows下可以通过API轻松的hook很多消息,IOS里面貌似还没有现成的API(可能是我还没发现吧),前段时间碰巧看到Objective-C运行时的一些东西,于是心想着是不是可以尝试一下实现hook的功能。

为什么要hook消息呢,因为有些时候我们可能无法直接去继承一个类,却又想先截获某些消息做一些处理,然后再接着进行正常的处理流程。今天使用运行时的一些API实现了基本的hook功能。

下面先直接上源码:

//
//  TestHookObject.m
//  TestHookMessage
//
//  Created by mapleCao on 13-2-28.
//  Copyright (c) 2013年 mapleCao. All rights reserved.
//

#import "TestHookObject.h"
#import <objc/objc.h>
#import <objc/runtime.h>

@implementation TestHookObject

// this method will just excute once
+ (void)initialize
{
    // 获取到UIWindow中sendEvent对应的method
    Method sendEvent = class_getInstanceMethod([UIWindow class], @selector(sendEvent:));
    Method sendEventMySelf = class_getInstanceMethod([self class], @selector(sendEventHooked:));
    
    // 将目标函数的原实现绑定到sendEventOriginalImplemention方法上
    IMP sendEventImp = method_getImplementation(sendEvent);
    class_addMethod([UIWindow class], @selector(sendEventOriginal:), sendEventImp, method_getTypeEncoding(sendEvent));
    
    // 然后用我们自己的函数的实现,替换目标函数对应的实现
    IMP sendEventMySelfImp = method_getImplementation(sendEventMySelf);
    class_replaceMethod([UIWindow class], @selector(sendEvent:), sendEventMySelfImp, method_getTypeEncoding(sendEvent));
}

/*
 * 截获到window的sendEvent
 * 我们可以先处理完以后,再继续调用正常处理流程
 */
- (void)sendEventHooked:(UIEvent *)event
{
    // do something what ever you want
    NSLog(@"haha, this is my self sendEventMethod!!!!!!!");
    
    // invoke original implemention
    [self performSelector:@selector(sendEventOriginal:) withObject:event];
}

@end

下面我们来逐行分析一下上面的代码:

首先我们来看19行,这一行主要目的是获取到UIWindow原生的sendEvent的Method(一个结构体,用来对方法进行描述),接着第20行是获取到我们自己定义的类中的sendEvent的Method(这两个方法的签名必须一样,否则运行时报错)。第23行我们通过UIWindow原生的sendEvent的Method获取到对应的IMP(一个函数指针),第24行使用运行时API Class_addMethod给UIWindow类添加了一个叫sendEventOriginal的方法,该方法使用UIWindow原生的sendEvent的实现,并且有着相同的方法签名(必须相同,否则运行时报错)。27行是获取我们自定义类中的sendEventMySelf的IMP,28行是关键的一行,这一行的主要目的是为UIWindow原生的sendEvent指定一个新的实现,我们看到我们将该实现指定到了我们自己定义的sendEventMySelf上。到了这儿我们就完成了偷梁换柱,大功告成。

执行上面这些行以后,我们就成功的将UIWindow的sendEvent重定向到了我们自己的写的sendEventMySelf的实现,然后将其原本的实现重定向到了我们给它新添加的方法sendEventOriginal中。而sendEventMySelf中,我们首先可以对这个消息进行我们想要的处理,然后再通过41行调用sendEventOriginal方法转到正常的执行流程。

这块儿你可能有个困惑 “我们自定义类中明明是没有sendEventOriginal方法的啊?”

为什么执行起来不报错,而且还会正常执行?因为sendEventMySelf是UIWindow的sendEvent重定向过来的,所以在运行时该方法中的self代表的就是UIWindow的实例,而不再是TestHookObject的实例了。加上sendEventOriginal是我们通过运行时添加到UIWindow的实例方法,所以可以正常调用。当然如果直接通过下面这种方式调用也是可以的,只不过编译器会提示警告(编译器没那么智能),因此我们采用了performSelector的调用方式。

[self sendEventOriginal:event];

以上就是Hook的实现,使用时我们只需要让TestHookObject类执行一次初始话操作就可以了,执行完以后。UIWindow的sendEvent消息就会会hook到我们的sendEventMySelf中了。

下面是调用代码:

//  Install Hook

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.viewController = [[[TestHookViewController alloc] initWithNibName:@"TestHookViewController" bundle:nil] autorelease];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    
    
    //hook UIWindow‘s SendEvent method
    TestHookObject *hookSendEvent = [[TestHookObject alloc] init];
    [hookSendEvent release];
    
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    btn.center = CGPointMake(160, 240);
    btn.backgroundColor = [UIColor redColor];
    [btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventAllEvents];
    [self.window addSubview:btn];
    [btn release];
    
    return YES;
}

代码中我们还专门添加了一个button来验证,hook完以后消息是否正常传递。经验证消息流转完全正常。

参考资料:

Objective-C运行时源代码

Objective-C Runtime Reference

使用JSPatch给iOS APP打补丁

  在我们的iOS应用中就算是已经上线了,也会存在一些bug,我们想修复这些小bug,而又不想重新经历 发包 这个繁复的过程。怎么办呢,最简单的方法就是在线打补丁。

  苹果在iOS7中提供了JavaScriptCore.framework框架。这让OC和JavaScript可以相互调用。虽然苹果官方是禁止下载在线代码运行,但是通过JavaScriptCore动态改变app的行为还是可行的。 关于JavaScriptCore的入门介绍,这里有一篇blog,可以读一下。cocoachina上还找到了一篇。而我们要介绍的JSPatch也是基于JavaScriptCore框架和runtime做的。

  这样我们就可以在Server端放置patch,等app启动的时候先去下载补丁,之后JSPatch会在runtime替换目标方法的实现,即可实现在线打补丁的目的。

  下面举个栗子project示例一下。在我们的项目中引入JSPatch,我使用的是cocoapods管理三方库的,如图:

   引入JSPatch如

  为了方便自己的使用,我们可以再对JSPatch做一层封装,因为我们要从Server端下载patch,我们可以把下载patch的操作和JSPatch的处理封装在一起,留作一个接口方便使用。JSPatch的用法,点进去看github就了解了。这样我们应用的调用逻辑只需要简单的调用这一个接口就可以了。我封装后的接口如下图:

接口图

  这个接口留了三个参数,patch在Server端的下载地址;补丁下载成功时的parser(这主要看传输数据的协议格式了);下载补丁失败时的handler。 大家可以根据自己的需求封装借口。

  下面就是在应用中的调用了。我们在应用启动的delegate方法:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中调用。

调用图

  假如我们的补丁是要修改- (void)applicationDidEnterBackground:(UIApplication *)application方法。该方法原来的实现为空,即什么都没有,现在我们要用补丁的方式让它弹出一个UIAlterView,那么js的补丁代码可以这么写:

defineClass('AppDelegate', {
    applicationWillEnterForeground: function(application) {
	    var alertView = require('UIAlertView').alloc().init()
    	    alertView.setTitle('提示')
    	    alertView.setMessage('当前为debug版本,JSPatch测试')
    	    alertView.addButtonWithTitle('OK')
    	    alertView.show()
    }
})

  效果图就不上了,静态图也不能演示这个过程,哪位有好的截取屏幕GIF图片的分享一下。

  把补丁存在沙盒内,以后再启动应用,封装的接口会先运行本地补丁,就不用下载了。补丁的运行和下载更新策略自己想吧,哈哈。


PS: 由于启动下载patch的过程是异步的,假如需要打的补丁在首页(也可能是快速操作进入了其他需要打补丁的页面),那么还没等patch下载运行,应用也已经进入了该页了,这种情况下,我们只能忍着,毕竟是处理小bug的嘛,可以忍,等再次进入该页面的时候,补丁就会运行,效果就会出来。如果逻辑设计成同步下载完成后在进入应用,在弱网环境下应用就挂掉了,得不偿失。这是我的策略,有好的想法的可以分享一下。

引用参考

JSPatch – 动态更新iOS APP

向Jekyll添加Google Analytics

转载请注明出处: http://elijahdou.github.io/

  由于要用Jekyll + github page搭建blog,想要添加analytics的功能,便于管理。便从网上找了方法,记录总结下来,与大家分享。

  背景知识不多说,直接上干货:

####   修改_config.yml文件,可以直接把统计跟踪的信息写在这里,也可以使用自定义的方式。使用自定义的方式可以方便以后升级,否则文件可能会被覆盖,推荐!

 analytics:       
 provider: custom      

  然后再_include目录下,建立一个custom目录,再在这个目录下新建一个文件analytics,如下:

 cd _includes       
 mkdir custom     
 cd custom     
 touch analytics          

  在新建的analytics文件内填写Google Analytics的跟踪代码:

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', '这是跟踪ID', 'auto');
  ga('send', 'pageview');
</script>

  如果需要关闭对某个单独页面的跟踪统计的话,关闭Analytics的方法,上图先:

提示图

###   markdown文件头添加 analytics: false即可关闭对该blog文件的跟踪统计,图中为打开状态。

PS:可以在sublime里建议blog的markdown模板,以后直接填写内容就可以了,很简单吧。


参考资料: Blog Configuration

Package Control:There are no packages available for installation的解决方法

  原谅我不想学vim,我感觉它变态。现在好的编辑器也挺多,我主要用sublime。

  在安装完Package Control后,安装其他插件包的时候,遇到了错误:Package Control:There are no packages available for installation。

报错图

  Holy shit,这是什么鬼,Google之,StackOverFlow上面解答的是IPv6的问题,我们配置一下hosts文件可以解决之。

  具体方法:

1. 在终端中输入命令 sudo sublime /etc/hosts  (编辑器任选)        
2. 在hosts文件最后添加:  
   # to fix sublime Package Control IPV6 issue                  
   50.116.34.243 sublime.wbond.net      
   #end         
3. 重启sublime,再try一下试试。

  这里有一篇中文文章,还介绍了其他两种sublime常见问题的解决方法。

mark一下。