求助如何在think上用友加盟盟的推送sdk

后端要推送消息给前端的话一般要做定时任务或者轮询,不然就只能等前端请求才能给回应。
这里用的第三方软件:友盟推送
友盟里的集成文档已经说的很清楚了。这里只谈sdk的集成
前面的步骤跟着友盟推送的文档做就可以了,(先注册友盟账号)友盟里设置应用获取appkey和app master secret
这里用 app示范
appkey和secret都是自动生成的,下面的服务器IP地址就是你自己的ip地址或者你项目挂的ip地址,也就是白名单
代码的话主要是demo.java文件,里面有各种形式的推送,需要说的是友盟中有两种情景,一个是推送,一个是正式推送,
测试推送需要在友盟端添加测试设备
device Token是IOS端获取的,具体的我不清楚,搞IOS的获取很简单。(相应的也有这一段)
添加好ip地址和token就可以试试测试推送了。
正式推送环境的话IOS需要上传生产证书,android不用证书,需要上传包名(这个在需要的时候就知道用在哪里了,不用刻意)。
使用过程中可能会碰到一个问题,广播推送和单播推送都OK,然而部分推送的时候友盟现有的标签可能不符合我们的要求,我们需要自己给用户打标签
java sdk中并没有集成这一接口,这让我好找了一会,最后在友盟论坛里找到了接口
具体的写法(签名,url拼凑)可以参考sdk中PushClient.java中的public String uploadContents方法
相对的还有api/tag/delete?sign=签名&&&&&&&&&& api/tag/clear?sign=签名&&&&&&&&&&& api/tag/list?sign=签名等方法
给用户打上标签之后在友盟上需要几分钟时间才能显示。然后就可以用自己的标签给相应的用户推送消息。
另外推送策略也没有在代码中集成,我顺便加进去了IOSNotification.java
自己码的菜鸟代码,有兴趣的可以看看也可以相互交流(QQ:)& &密码:08p0
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3002次
排名:千里之外
原创:25篇
转载:14篇
(1)(9)(9)(1)(5)(4)(7)(3)发表了一篇文章
我们假设你已经成功的安装 OpenCV 。
在你的项目中使用 OpenCV 的最简单方式是用 . 优点是 (来自官方 Wiki):
支持 Windows 和 Linux,无需任何改动
和轻松和其他支持 CMake 的工具一起使用( 例如 Qt, ITK 和 VTK )
如果你对 CMake 不熟悉,请参考
使用 OpenCV 创建一个简单应用
DisplayImage.cpp 如下
#include &stdio.h&
#include &opencv2/opencv.hpp&
using namespace
int main(int argc, char** argv )
if ( argc != 2 )
printf("usage: DisplayImage.out &Image_Path&\n");
return -1;
Mat image;
image = imread( argv[1], 1 );
if ( !image.data )
printf("No image data \n");
return -1;
namedWindow("Display Image", WINDOW_AUTOSIZE );
imshow("Display Image", image);
waitKey(0);
创建 CMake 文件
现在你需要创建一个 CMakeLists.txt 文件,内容如下:
cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
find_package( OpenCV REQUIRED )
add_executable( DisplayImage DisplayImage.cpp )
target_link_libraries( DisplayImage ${OpenCV_LIBS} )
生成可执行文件
这部分很简单,使用如下命令构建即可:
cd &DisplayImage_directory&
现在你已经有一个可执行程序(名为 DisplayImage ). 你只需要传递一个图片文件即可运行:
./DisplayImage lena.jpg
运行结果:
使用 gcc 和 CMake 编译简单的 OpenCV 程序
我们假设你已经成功的安装 OpenCV 。
在你的项目中使用 …
发表了一篇文章
In this tutorial you will learn how to:
to detect objects in a video stream. Particularly, we will use the functions:
load a .xml classifier file. It can be either a Haar or a LBP classifer
perform the detection.
This tutorial code’s is shown lines below. You can also download it from
The second version (using LBP for face detection) can be
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include &iostream&
#include &stdio.h&
using namespace std;
using namespace
void detectAndDisplay( Mat frame );
String face_cascade_name = "haarcascade_frontalface_alt.xml";
String eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_
CascadeClassifier eyes_
string window_name = "Capture - Face detection";
RNG rng(12345);
int main( int argc, const char** argv )
CvCapture*
if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };
if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };
capture = cvCaptureFromCAM( -1 );
if( capture )
while( true )
frame = cvQueryFrame( capture );
if( !frame.empty() )
{ detectAndDisplay( frame ); }
{ printf(" --(!) No captured frame -- Break!"); break; }
int c = waitKey(10);
if( (char)c == 'c' ) { break; }
void detectAndDisplay( Mat frame )
std::vector&Rect&
Mat frame_
cvtColor( frame, frame_gray, CV_BGR2GRAY );
equalizeHist( frame_gray, frame_gray );
face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );
for( size_t i = 0; i & faces.size(); i++ )
Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar( 255, 0, 255 ), 4, 8, 0 );
Mat faceROI = frame_gray( faces[i] );
std::vector&Rect&
eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) );
for( size_t j = 0; j & eyes.size(); j++ )
Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5, faces[i].y + eyes[j].y + eyes[j].height*0.5 );
int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
circle( frame, center, radius, Scalar( 255, 0, 0 ), 4, 8, 0 );
imshow( window_name, frame );
Here is the result of running the code above and using as input the video stream of a build-in webcam:
Remember to copy the files haarcascade_frontalface_alt.xml and haarcascade_eye_tree_eyeglasses.xml in your current directory. They are located in opencv/data/haarcascades
This is the result of using the file lbpcascade_frontalface.xml (LBP trained) for the face detection. For the eyes we keep using the file used in the tutorial.
OpenCV中的级联分类器Cascade Classifier
In this tutorial you will le…
发表了一篇文章
##一点背景知识
OpenCV 是一个开源的计算机视觉和机器学习库。它包含成千上万优化过的算法,为各种计算机视觉应用提供了一个通用工具包。根据这个项目的关于页面,Open…
发表了一篇文章
OpenCV的变更日志
2015年10月
这是在3.x系列第一稳定的更新。应当提醒的是,因为OpenCV的3.0,我们已经改变了版本…
发表了一篇文章
软件目录路径:/Applications字体路径:System\Library\Fonts\CacheFIT皮肤:/Library/FIT文件夹:FIT漫画文件存放于:/p…
发表了一篇文章
1.应用代码混淆,可参考国外开发者写的方案/Polidea/ios-class-guard,这个还有点bug,需要懂一些shell脚本。2.链接一定…
发表了一篇文章
IMP orginIMP;
NSString * MyUppercaseString(id SELF, SEL _cmd)
NSLog(@"begin uppercaseString");
NSString *str = orginIMP (SELF, _cmd);(3)
NSLog(@"end uppercaseString");
-(void)testReplaceMethod
Class strcls = [NSString class];
oriUppercaseString = @selector(uppercaseString);
orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString];
IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2)
NSString *s = "hello world";
NSLog(@"%@",[s uppercaseString]];
执行结果为:
begin uppercaseString
end uppercaseString
HELLO WORLD
这段代码的作用就是
(1)得到uppercaseString这个函数的函数指针存到变量orginIMP中
(2)将NSString类中的uppercaseString函数的实现替换为自己定义的MyUppercaseString
(3)在MyUppercaseString中,先执行了自己的log代码,然后再调用之前保存的uppercaseString的系统实现,这样就在系统函数执行之前加入自己的东西,后面每次对NSString调用uppercaseString的时候,都会打印出log来
与class_replaceMethod相仿,class_addMethod可以在运行时为类增加一个函数。
(2)当某个对象不能接受某个selector时,将对该selector的
调用转发给另一个对象:
- (id)forwardingTargetForSelector:(SEL)aSelector
forwardingTargetForSelector是NSObject的函数,用户可以在派生类中对其重载,从而将无法处理的selector转发给另一个对象。还是以上面的uppercaseString为例,如果用户自己定义的CA类的对象a,没有uppercaseString这样一个实例函数,那么在不调用respondSelector的情况下,直接执行[a performSelector:@selector"uppercaseString"],那么执行时一定会crash,此时,如果CA实现了forwardingTargetForSelector函数,并返回一个NSString对象,那么就相对于对该NSString对象执行了uppercaseString函数,此时就不会crash了。当然实现这个函数的目的并不仅仅是为了程序不crash那么简单,在实现装饰者模式时,也可以使用该函数进行消息转发。
示例代码:
1 @interface CA : NSObject
3 -(void)f;
7 @implementation CA
9 - (id)forwardingTargetForSelector:(SEL)aSelector
if (aSelector == @selector(uppercaseString))
return@"hello world";
测试代码:
CA *a = [CA new];
NSString * s = [a performSelector:@selector(uppercaseString)];
NSLog(@"%@",s);
测试代码的输出为:HELLO WORLD
ps:这里有个问题,CA类的对象不能接收@selector(uppercaseString),那么如果我在forwardingTargetForSelector函数中用class_addMethod给CA类增加一个uppercaseString函数,然后返回self,可行吗?经过试验,这样会crash,此时CA类其实已经有了uppercaseString函数,但是不知道为什么不能调用,如果此时new一个CA类的对象,并返回,是可以成功的。
(3)当某个对象不能接受某个selector时,向对象所属的类动态
添加所需的selector:
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
这个函数与forwardingTargetForSelector类似,都会在对象不能接受某个selector时触发,执行起来略有差别。前者的目的主要在于给客户一个机会来向该对象添加所需的selector,后者的目的在于允许用户将selector转发给另一个对象。另外触发时机也不完全一样,该函数是个类函数,在程序刚启动,界面尚未显示出时,就会被调用。
在类不能处理某个selector的情况下,如果类重载了该函数,并使用class_addMethod添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用forwardingTargetForSelector,如果在forwardingTargetForSelector并未返回能接受该selector的对象,那么resolveInstanceMethod会再次被触发,这一次,如果仍然不添加selector,程序就会报异常
代码示例一:
1 @implementation CA
3 void dynamicMethodIMP(id self, SEL _cmd)
printf("SEL
%s did not exist\n",sel_getName(_cmd));
resolveInstanceMethod:(SEL)aSEL
== @selector(t))
class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
return [superresolveInstanceMethod:aSEL];
测试代码:
* ca = [CA new]
performSelector:@selector(t)];
SEL t did not exist
示例代码二:
@implementation CA
void dynamicMethodIMP(id self, SEL _cmd)
printf("SEL
%s did not exist\n",sel_getName(_cmd));
resolveInstanceMethod:(SEL)aSEL
(id)forwardingTargetForSelector:(SEL)aSelector
if (aSelector
== @selector(uppercaseString))
return @"hello
= [[CA alloc]init];
NSLog(@"%@",[a
performSelector:@selector(uppercaseString)];
该测试代码的输出为:HELLO WORLD
对于该测试代码,由于a没有uppercaseString函数,因此会触发resolveInstanceMethod,但是由于该函数并没有添加selector,因此运行时发现找不到该函数,会触发
forwardingTargetForSelector函数,在forwardingTargetForSelector函数中,返回了一个NSString "hello world",因此会由该string来执行uppercaseString函数,最终返回大写的hello world。
示例代码三:
@implementation CA
resolveInstanceMethod:(SEL)aSEL
(id)forwardingTargetForSelector:(SEL)aSelector
return nil;
 测试代码:
a = [[CA alloc]init];
NSLog(@"%@",[a
performSelector:@selector(uppercaseString)];
这段代码的执行顺序为:
(1):首先在程序刚执行,AppDelegate都还没有出来时,resolveInstanceMethod就被触发,
(2)等测试代码执行时,forwardingTargetForSelector被调用
(3)由于forwardingTargetForSelector返回了nil,因此运行时还是找不到uppercaseString selector,这时又会触发resolveInstanceMethod,由于还是没有加入selector,于是会crash。
使用class_copyPropertyList及property_getName
获取类的属性列表及每个属性的名称
objc_property_t*
properties= class_copyPropertyList([UIView class],
for (int i
= 0; i & i++)
const char*
propertyName = property_getName(properties[i]);
NSString *strName
= [NSString
stringWithCString:propertyName
encoding:NSUTF8StringEncoding];
NSLog(@"%@",strName);
 以上代码获取了UIView的所有属性并打印属性名称, 输出结果为:
skipsSubviewEnumeration
viewTraversalMark
viewDelegate
monitorsSubtree
backgroundColorSystemColorName
gesturesEnabled
deliversTouchesForGesturesToSuperview
userInteractionEnabled
_boundsWidthVariable
_boundsHeightVariable
_minXVariable
_minYVariable
_internalConstraints
_dependentConstraints
_constraintsExceptingSubviewAutoresizingConstraints
_shouldArchiveUIAppearanceTags
(5) 使用class_copyMethodList获取类的所有方法列表
获取到的数据是一个Method数组,Method数据结构中包含了函数的名称、参数、返回值等信息,以下代码以获取名称为例:
methods= class_copyMethodList([UIView class],
for (int i
= 0; i & i++)
= method_getName(methods[i]);
NSString *strName
= [NSString
stringWithCString:sel_getName(name)
encoding:NSUTF8StringEncoding];
NSLog(@"%@",strName);
  代码执行后将输出UIView所有函数的名称,具体结果略。
其他一些相关函数:
1.SEL method_getName(Method m) 由Method得到SEL
2.IMP method_getImplementation(Method m)
由Method得到IMP函数指针
3.const char *method_getTypeEncoding(Method m)
由Method得到类型编码信息
4.unsigned int method_getNumberOfArguments(Method m)获取参数个数
5.char *method_copyReturnType(Method m)
得到返回值类型名称
6.IMP method_setImplementation(Method m, IMP imp)
为该方法设置一个新的实现
总而言之,使用runtime技术能做些什么事情呢?
可以在运行时,在不继承也不category的情况下,为各种类(包括系统的类)做很多操作,具体包括:
增加函数:class_addMethod
增加实例变量:class_addIvar
增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成
增加Protocol:class_addProtocol
(说实话我真不知道动态增加一个protocol有什么用,-_-!!)
获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod
method_getName ...
获取属性列表及每个属性的信息:class_copyPropertyList
property_getName
获取类本身的信息,如类名等:class_getName
class_getInstanceSize
获取变量列表及变量信息:class_copyIvarList
获取变量的值
将实例替换成另一个类:object_setClass
将函数替换成一个函数实现:class_replaceMethod
直接通过char *格式的名称来修改变量的值,而不是通过变量
后面主要介绍oc类的运行时行为。这里面包括运行时方法的更换,消息的转发,以及动态属性。这些对于面向方面编程AOP的热爱者还是很有用的,当然还有很多其他的作用,例如可配置编程之类的。但是按照我之前在java和dotnet的编程经验,这些都是使用性能为代价的。所以尽量在程序开始部分完成操作,而不是用于程序行为的代码。
第一段代码是方法交换。下面的例子将使用自己的代码替换[NSString stringByAppendingPathComponent]方法的实现。
这里是替换代码:
* NSStringstringByAppendingPathComponent(id SELF, SEL _cmd, NSString * path){
NSLog(@”this is a
fake imp for method
%@”, NSStringFromSelctor(_cmd));
NSLog(@”I
won’t do anything!
but I will return a
virus!”);
return [NSString
stringWithCString: “virus!!!” encoding:NSUTF8StringEncoding];
下面是方法交换的代码:
Class strcls = [NSString class];
SEL oriStringByAppendingPathComponent = @selector(stringByAppendingPathComponent:);
class_replaceMethod(strcls,
  oriStringByAppendingPathComponent,
  (IMP)NSStringstringByAppendingPathComponent,
  NULL);
//后面的type参数可以是NULL。如果要替换的方法不存在,那么class_addMethod会被调用,type参数将被用来设置被添加的方法
Apple development reference 的描述如下:
type参数:An array of characters that describe the types of the arguments to the method. For possible values, see
& . Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
If the method identified by name does not yet exist, it is added as if
called. The type encoding specified by types is used as given.
If the method identified by name does exist, its IMP is replaced as if
called. The type encoding specified by types is ignored.
objective-c 2.0中增加了一个新的关键字@dynamic, 用于定义动态属性。所谓动态属性相对于@synthesis,不是由编译器自动生成setter或者getter,也不是由开发者自己写的setter或getter,而是在运行时动态添加的setter和getter。
一般我们定义一个属性都是类似以下方法:
@interface
(retain) NSString*
@implement
@synthesize
这种情况下,@synthesize关键字告诉编译器自动实现setter和getter。另外,如果不使用@synthesize,也可以自己实现getter或者setter
@implement
(NSString*)
setName:(NSString*) n{
现在通过@dynamic,还可以通过第三种方法来实现name的setter和getter。实现动态属性,需要在代码中覆盖resolveInstanceMethod来动态的添加name的setter和getter。这个方法在每次找不到方法实现的时候都会被调用。事实上,NSObject的默认实现就是抛出异常。
参考以下代码:
下面是定义动态属性和实现动态属性的代码:
@interface
Car:NSObject
(retain) NSString*
dynamicSetName(id SELF, SEL _cmd, NSString * n){
NSLog(@"the
new name is going to send in:%@!",
@implement
resolveInstanceMethod:(SEL) sel{
* method = NSStringFromSelector(sel);
if([method
isEqualToString:@"setName:"]){
class_addMethod([selfclass],
sel, (IMP)dynamicSetName, "v@:@");
return YES:
return [super
resolveInstanceMethod:sel];
上面的代码动态实现了name的setter,当然这个时候如果想要调用car.name,就会抛出错误,因为我并没有实现它的getter。
第七篇中讲动态属性时,提到了resolveInstanceMethod,这个方法不仅在这里用,还用来实现消息的转发。
消息的转发就是向对象发送一个它本身并没有实现的消息,在运行时确定它实际产生的行为。
举个例子来说,一个Person对象,在运行时根据实际情况,决定是否响应fly这样的方法。如果条件具备,则fly被响应。否则,则不具备这样的方法。类似于AoP的做法。
要实现消息转发,需要覆盖三个方法:
1, resolveInstanceMethod(可选),这个方法为你提供了一个机会,在消息被发现本身没有在类中定义时你可以通过class_addMethod将它添加进去。如果你不这样做,不管你最终返回YES还是NO,程序都会jmp到下一个方法。
2, –(NSMethodSignature*)methodSignatureForSelector: (SEL)通过覆盖这个方法,可以将你要转发的消息的签名创建并返回。如果在这里你返回nil,那么就会直接抛出异常。如果返回一个签名,则程序会jmp到第三个方法。这里返回的方法签名,必须满足两个条件之一(方法名相同||输入参数相同).
3, –(void)forwardInvocation:(NSInvocation * )anI在这里进行实际的方法调用。参考以下代码:
#import &Foundation/Foundation.h&
#import "Human.h"
@interface Plane : NSObject
-(void)fly:(Human*)p;
-(int)fly1;
-(void)fly2:(Human*)p withBird:(NSString*)birdN
#import "Plane.h"
@implementation Plane
-(void)fly:(Human*)p{
NSLog(@"Fly with a guy whose information is \"%@\"", [p description]);
NSLog(@"fly......");
-(int)fly1{
NSLog(@"I can fly in Plane for fly1");
-(void)fly2:(Human*)p withBird:(NSString*)birdName{
NSLog(@"Fly with a guy whose information is \"%@\"", [p description]);
NSLog(@"fly......bird:'%@'.", birdName);
#import &Foundation/Foundation.h&void dynamicMethod(id self, SEL _cmd, float height);
@interface Human : NSObject{
@property(retain, nonatomic) NSString *
@property(readonly) float
@property float
-(id) initWithWeight:(float)
-(NSString*)
#import &objc/runtime.h&
#import "Human.h"
#import "Plane.h"void dynamicMethod(id self, SEL _cmd, float height){
NSLog(@"dynamicMethod:%@", NSStringFromSelector(_cmd));
// here is a question. how to implement the statements to assign the property of id(human.height);
// looks like the dynamic property is just for linkage.
// 当赋值发生时,由于动态函数的全局性,可以访问其他全局变量,对象。所以更像一种链接机制}
@implementation Human
-(id)initWithWeight:(float)w{
if(self=[super init]){
-(NSString*)description{
return [NSString stringWithFormat:@"The human whose name is \"%@\". weight is [%f]. height is [%f]",
self.name, self.weight, self.height];
-(float)height{
-(void)fly2{
NSLog(@"yes I can fly in 2!");
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString* method = NSStringFromSelector(sel);
if([method isEqualToString:@"setHeight:"]){
class_addMethod([self class], sel, (IMP)dynamicMethod, "v@:f");
return [super resolveInstanceMethod:sel];
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector{
NSMethodSignature * signature = [super methodSignatureForSelector:selector];
if(!signature&&[NSStringFromSelector(selector) isEqualToString:@"fly"]){
//signature = [[self class] instanceMethodSignatureForSelector:@selector(fly2)];
SEL newSel = NSSelectorFromString(@"fly1");
//will ok.
// SEL newSel = NSSelectorFromString(@"fly:");//will ok.
// SEL newSel = NSSelectorFromString(@"fly2:withBird");//will wrong.
//这里返回的消息,必须符合以下条件之一:
//方法名相同
//输入参数相同
if([Plane instancesRespondToSelector:newSel]){
signature = [Plane instanceMethodSignatureForSelector:newSel];
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSString * selName = NSStringFromSelector([anInvocation selector]);
NSMethodSignature * sig = [anInvocation methodSignature];
NSLog(@"the signature %@", [NSString stringWithCString:[sig methodReturnType] encoding:NSUTF8StringEncoding]);
if([selName isEqualToString:@"fly"]){
Plane * p = [[Plane alloc] init];
SEL newSel = NSSelectorFromString(@"fly:");
IMP imp = [p methodForSelector:newSel];
imp(p, newSel, self);
[p release];
-(void)dealloc{
[self setName:nil];
[super dealloc];
#import &Foundation/Foundation.h&
#import &objc/runtime.h&
#import "Human.h"
#import "Plane.h"int main (int argc, const char * argv[])
@autoreleasepool {
// insert code here...
Human * h = [[Human alloc] initWithWeight:17.3];
h.name=@"Chris";
h.height = 18.0;
NSLog(@"%@", [h description]);
[h release];
学习到目前为止,我看到oc实现的序列化方式有两种:NSKeyedArchiver,NSPropertyListSerialization。
在这两种序列化方式中,NSData都是序列化的目标。两种方式的不同点在于NSPropertyListSerialization只是针对字典类型的,而NSKeyedArchiver是针对对象的。(补充一下,在Mac OS环境下,还可以使用NSArchiver获得更加精简的二进制序列化内容,但是NSArchiver在iOS环境下不支持)。
首先讲NSPropertyListSerialization,这个相对简单,只要创建一个NSDictionary,然后调用NSPropertyListSerialization dataFromPropertyList, 将内容写入NSData中就可以了。如果要读入,就调用propertyListFromData.
* filepath = @”…”;
NSDictionary
* props = [NSDictionary dictionaryWithObjectsAndKey:@”Lucy”, @"name”,
@"Beijing,
China”, @"city”,
@"supervior”,
@"position”,
@"Qitiandasheng”,
@"company”,
* data = [NSPropertyListSerialization dataFromPropertyList:props
format:NSPropertyListXMLFormat_v1_0
errorDescription:&err];
writeToFile:filePath atomically:YES];
NSLog(@"error
然后再来看NSKeyedArchiver。从基本的代码示例来看也很简单:
= [[Person alloc] initWithName:@"lucy"];
lucy.address
= @"Beijing,
* data = [NSKeyedArchiver archiveDataWithRootObject:lucy];
writeToFile:filePath];
这里要求Person类必须事先了NSCoding协议。NSCoding协议中包含两个必须事先的方法initWithCoder和encodeWithCoder. 参考下面这两个方法的实现:
-(void)encodeWithCoder:(NSCoder
encodingObject:[NSNumber numberWithInt:self.age] forKey @"age"];
-(id)initWithCoder:(NSCoder*)
if(self=[super
= (int)[(NSNumber*)[aDecoder
decodeObjectForKey:@"age"]
intValue];
这里之所以用int age作为序列化属性的示例,是为了引出另一个话题,就是在序列化时对基础类型的处理。NSCoder中是不能存储基础类型的,包括int,float,char *什么的。要将这些进行序列化,必须要进行封装。
一般封装有两种方式。对于数值基本类型,使用Cocoa提供的封装,NSNumber就可以了。对于定长的struct,要分情况,如果struct的所有类型都是定长,并且成员中没有struct类型,那么可以直接使用NSValue来封装;否则,需要自己想办法进行key-value编写。char * 应该直接使用NSString进行封装。
Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增…
发表了一篇文章
在入门级别的ObjC 教程中,我们常对从C++或Java 或其他面向对象语言转过来的程序员说,ObjC 中的方法调用(ObjC中的术语为消息)跟其他语言中的方法调用差不多,只是…
发表了一篇文章
中文车牌识别系统 EasyPR
EasyPR 是一个开源的中文车牌识别系统。 EasyPR是一个中文的开源车牌识别系统,其目标是成为一个简单、灵活、准确…
发表了一篇文章
机器视觉开源处理库汇总
从cvchina搞到的机器视觉开源处理库汇总,转来了,很给力,还在不断更新。。。
通用库/General Library
发表了一篇文章
1.机器学习开源软件网(收录了各种机器学习的各种编程语言学术与商业的开源软件)
2偶尔找到的机器学习资源网:(也非常全,1和2基本收录了所有ML的经典开源软件了)
3libsvm(支持向量机界最牛的,不用多说了,台湾大学的林教授的杰作)
4WEKA(基于java的机器学习算法最全面最易用的开源软件)
5scikit(本人最喜欢的一个基于python的机器学习软件,代码写得非常好,而且官方的文档非常全,所有都有例子,算法也齐全,开发也活跃
,强烈推荐给大家用)
6OpenCv(最牛的开源计算机视觉库了,前途无可限量,做图像处理与模式识别的一定要用,总不能整天抱着matlab做实验和工业界脱节吧,但是有一定难度)
7Orange(基于c++和python接口的机器学习软件,界面漂亮,调用方便,可以同时学习C++和python,还有可视化的功能,)
8Mallet(基于JAVA实现的机器学习库,主要用于自然语言处理方面,特色是马尔可夫模型和随机域做得好,可和WEKA互补)
9NLTK(PYTHON的自然处理开源库,非常易用,也强大,还有几本orelly的经典教程)
10lucene(基于java的包括nutch,solr,hadoop,mahout等全套,是做信息检索和搜索引擎的同志们必学的开源软件了,学JAVA的必学)
MG4J可以让你通过内插编码(interpolativecoding)技术,为大量的文档集合构建一个被压缩的全文本索引。与Lucene主要区别是,它提供了cluster功能,具有更OO的设计方式。虽然MG4J不是一个像Lucene、Egothor和Xapian那样的信息检索库,但是相信每一位搜索工程师都应该知道它,因为它对构建Java信息检索库提供了低水平的支持。MG4J是在一本很流行的书问世之后被命名的,这本书是由H.Witten,AlistairMoffat和Timothy所写,名字是《管理十亿字节:压缩并且索引文档和图片》。在使用他们的分布式、可容错的网页爬虫UbiCrawler收集到大量的网页数据后,它的作者需要一个软件来解析那些收集来的数据,由于这个需求,MG4J诞生了。MG4J的库提供了优化的类来处理I/O,转化索引文件的压缩等等。2.
Terrier是一个高度灵活,高效的开源搜索引擎,易于部署在大型的文件集合。Terrier实现非常优秀的索引和搜索功能,为开发大型检索应用程序提供了一个理想的平台。它支持多索引策略比如:multi-pass、single-pass和大型MapReduce索引。3.
Lemur(狐猴)系统是CMU和UMass联合推出的一个用于自然语言模型和信息检索研究的系统。在这个系统上可以实现基于自然语言模型和传统的向量空间模型以及Okapi的adhoc或者分布式检索,可以使用结构化查询、跨语言检索、过滤、聚类等。Lemur可以在Windows或者Unix环境下使用,因此我们可以直接在Windows下使用Lemur。但是Lemur提供了shellscript文件来演示完整的使用lemur进行检索的过程,所以在Windows下需要安装cygwin来模拟Unix环境。Lemur还提供了一个GUI程序以及用户交互的界面的CGI,Java程序可以直接看到检索的结果,需要安装Java虚拟机,CGI程序需要Perl的解释器。4.
Xapian由C++编写,但可以绑定到Perl、Python、PHP、Java、Tcl、C#和Ruby甚至更多的语言。Xapian可以说是STL编程的典范,在这里您可以找到熟悉的引用计数型智能指针、容器和迭代器,甚至连命名也跟STL相似,相信一定能引起喜好C++和STL的你的共鸣(实际上,很少C++程序员完全不使用STL)。由于Xapian使用的是STL和C运行时库,因此具有高度可移值性,官方说法是可以运行在Linux、MacOSX、FreeBSD、NetBSD、OpenBSD、Solaris、HP-UX、Tru64和IRIX,甚至其它的Unix平台,在Windows上也跑得很好。当然,并不能像Java那样“一次编译,到处可以运行”,当移植到其它平台时,一般来说是需要重新编译的。
揭秘 TensorFlow:Google 开源到底开的是什么?
这两天发现朋友圈被Google开源深度学习系统TensorFlow的新闻刷屏了。这当然是一个很好的消息,尤其对我们这种用机器学习来解决实际问题的工程师来说更是如此。但同时很多人并不清楚听起来神乎其神的“TensorFlow”到底是什么,有什么意义。
我是人工智能科技公司“出门问问”的工程师,对人工智能“深度学习”理论有一定的了解,在这里就为大家“破雾”,聊一聊Google的开源系统TensorFlow。
什么是深度学习?
在机器学习流行之前,都是基于规则的系统,因此做语音的需要了解语音学,做NLP的需要很多语言学知识,做深蓝需要很多国际象棋大师。
而到后来统计方法成为主流之后,领域知识就不再那么重要,但是我们还是需要一些领域知识或者经验来提取合适的feature特征,feature的好坏往往决定了机器学习算法的成败。对于NLP来说,feature还相对比较好提取,因为语言本身就是高度的抽象;而对于Speech或者Image来说,我们人类自己也很难描述我们是怎么提取feature的。比如我们识别一只猫,我们隐隐约约觉得猫有两个眼睛一个鼻子有个长尾巴,而且它们之间有一定的空间约束关系,比如两只眼睛到鼻子的距离可能差不多。但怎么用像素来定义”眼睛“呢?如果仔细想一下就会发现很难。当然我们有很多特征提取的方法,比如提取边缘轮廓等等。
但是人类学习似乎不需要这么复杂,我们只要给几张猫的照片给人看,他就能学习到什么是猫。人似乎能自动“学习”出feature来,你给他看了几张猫的照片,然后问猫有什么特征,他可能会隐隐预约地告诉你猫有什么特征,甚至是猫特有的特征,这些特征豹子或者老虎没有。
深度学习为什么最近这么火,其中一个重要的原因就是不需要(太多)提取feature。
从机器学习的使用者来说,我们以前做的大部分事情是feature engineering,然后调一些参数,一般是为了防止过拟合。而有了深度学习之后,如果我们不需要实现一个CNN或者LSTM,那么我们似乎什么也不用干。(机器让工人失业,机器学习让搞机器学习的人失业!人工智能最终的目的是让人类失业?)
但是深度学习也不是万能的,至少现在的一个问题是它需要更强的计算能力才能训练出一个比较好的模型。它还不能像人类那样通过很少的训练样本就能学习很好的效果。
其实神经网络提出好几十年了,为什么最近才火呢?其中一个原因就是之前它的效果并不比非深度学习算法好,比如SVM。
到了2006年之后,随着计算能力的增强(尤其是GPU的出现),深度神经网络在很多传统的机器学习数据集上体现出优势来之后,后来用到Image和Speech,因为它自动学出的feature不需要人工提取feature,效果提升更加明显。这是否也说明,我们之前提取的图像feature都不够好,或者是根据人脑的经验提取的feature不适合机器的模型?
很多人对深度学习颇有微词的一个理由就是它没有太多理论依据,更多的像蛮力的搜索——非常深的层数,几千万甚至上亿参数,然后调整参数拟合输入与输出。其实目前所有的机器学习都是这样,人类的大脑的学习有所不同吗,不是神经元连接的调整吗?
但不管怎么说,从深度神经网络的使用者(我们这样的工程师)的角度来说,如果我们选定了一种网络结构,比如CNN,那么我们要做的就是根据经验,选择合适的层数,每层的神经元数量,激活函数,损失函数,正则化的参数,然后使用validation数据来判定这次训练的效果。从效果来说,一般层次越多效果越好(至少相对一两层的网络来说),但是层次越多参数也越多,训练就越慢。单机训练一个网络花几天甚至几周的时间非常常见。因此用多个节点的计算机集群来训练就是深度学习的核心竞争力——尤其对于用户数据瞬息万变的互联网应用来说更是如此。
常见深度神经网络的训练和问题
对于机器学习来说,训练是最关键也最困难的部分,一般的机器学习模型都是参数化的模型,我们可以把它看成一个函数y=f(w;x)。
比如拿识别图像来说,输入x是这张图片的每个像素值,比如MNIST的数据是28*28的图片,每个点是RGB的颜色值,那么x就是一个28*28*3的向量。而一个模型有很多参数,用w表示。输出y是一个向量,比如MNIST的数据是0-9的10个数字,所以我们可以让y输出一个10维的向量,分别代表识别成0-9的置信度(概率),选择最大的那个作为我们的识别结果(当然更常见的是最后一层是softmax而不是普通的激活函数,这样这个10维向量加起来等于1,可以认为是分类的概率)。
而训练就是给这个模型很多(x,y),然后训练的过程就是不断的调整参数w,然后使得分类错误尽可能少(由于分类错误相对w不连续因而不可求导,所以一般使用一个连续的Loss Function)。
对于神经网络来说,标准的训练算法就是反向传播算法BackPropagation。从数学上来说就是使用(随机)梯度下降算法,求Loss Function对每个参数的梯度。另外我们也可以从另外一个角度来看反向传播算法,比如最后一层的神经网络参数,直接造成错误Loss;而倒数第二层的神经网络参数,通过这一次的激活函数影响最后一层,然后间接影响最终的输出。反向传播算法也可以看成错误不断往前传播的过程。
由于深度神经网络的参数非常多,比如GoogleNet, 2014年ILSVRC挑战赛冠军,将Top5 的错误率降低到6.67%,它是一个22层的CNN,有5百多万个参数。所以需要强大的计算资源来训练这么深的神经网络。
其中,CNN是Image领域常见的一种深度神经网络。由Yann LeCun提出,通过卷积来发现位置无关的feature,而且这些feature的参数是相同的,从而与全连接的神经网络相比大大减少了参数的数量。
cnn深度神经网络
最开始的改进是使用GPU来加速训练,GPU可以看成一种SIMT的架构,和SIMD有些类似,但是执行相同指令的warp里的32个core可以有不同的代码路径。对于反向传播算法来说,基本计算就是矩阵向量乘法,对一个向量应用激活函数这样的向量化指令,而不像在传统的代码里会有很多if-else这样的逻辑判断,所以使用GPU加速非常有用。
但即使这样,单机的计算能力还是相对有限的。
深度学习开源工具
从数学上来讲,深度神经网络其实不复杂,我们定义不同的网络结构,比如层次之间怎么连接,每层有多少神经元,每层的激活函数是什么。前向算法非常简单,根据网络的定义计算就好了。
而反向传播算法就比较复杂了,所以现在有很多深度学习的开源框架来帮助我们把深度学习用到实际的系统中。
我们可以从以下几个不同的角度来分类这些开源的深度学习框架。
通用vs专用
深度学习抽象到最后都是一个数学模型,相对于传统的机器学习方法来说少了很多特征抽取的工作,但是要把它用到实际的系统中还有很多事情要做。而且对于很多系统来说,深度学习只是其中的一个模块。
拿语音识别来说,语音识别包含很多模块,比如声学模型和语言模型,现在的声学模型可以用LSTMs(一种RNN,也是一种深度学习网络)来做,但是我们需要把它融入整个系统,这就有很多工作需要做。而且目前大部分的机器学习方法包括深度学习,都必须假设训练数据和测试数据是相同(或者类似)的分布的。所以在实际的应用中,我们需要做很多数据相关的预处理工作。
比如Kaldi,它是一个语音识别的工具,实现了语音识别的所有模块,也包括一些语音识别常用的深度神经网络模型,比如DNN和LSTM。
而Caffe更多的是用在图像识别,它实现了CNN,因为这个模型在图像识别上效果非常好。
大部分开源的深度学习工具把整个模型都封装好了,我们只需要指定一些参数就行了。比如我们使用Caffe的CNN。
但是还有一些工具只是提供一些基础库,比如Theano,它提供了自动求梯度的工具。
我们可以自己定义网络的结构,我们不需要自己求梯度。使用Theano的好处是如果我们“创造”一个新的网络结构或者是很新的深度神经网络,那么其它框架很可能还没有实现,所以Theano在学术界很流行。当然坏处就是因为它不可能针对特定的模型做优化,所以可能性能不如特定的实现那么好。
单机vs集群
目前大部分的开源工具都是单机版的,有些支持在一个节点的多个GPU训练,但是支持GPU cluster比较少,目前支持多机训练的有GraphLab和Deeplearning4j。
Tensor Flow到底是什么?
Tensor张量意味着N维数组,Flow流意味着基于数据流图的计算,TensorFlow即为张量从图的一端流动到另一端。
TensorFlow 表达了高层次的机器学习计算,大幅简化了第一代系统,并且具备更好的灵活性和可延展性。TensorFlow一大亮点是支持异构设备分布式计算,它能够在各个平台上自动运行模型,从电话、单个CPU / GPU到成百上千GPU卡组成的分布式系统。
从目前的文档看,TensorFlow支持CNN、RNN和LSTM算法,这都是目前在Image,Speech和NLP最流行的深度神经网络模型。
而且从Jeff Dean的论文来看,它肯定是支持集群上的训练的。
在论文里的例子来看,这个架构有点像Spark或者Dryad等图计算模型。就像写Map-reduce代码一样,我们从高层的角度来定义我们的业务逻辑,然后这个架构帮我们调度和分配计算资源(甚至容错,比如某个计算节点挂了或者太慢)。目前开源的实现分布式Deep learning的GraphLab就是GAS的架构,我们必须按照它的抽象来编写Deep Learing代码(或者其它分布式代码,如果PageRank),而Deeplearning4j直接使用了Spark。
Map-Reduce的思想非常简单,但是要写出一个稳定可用的工业级产品来就不容易了。而支持分布式机器学习尤其是深度学习的产品就更难了,Google的TensorFlow应该是一种抽象方式,可惜现在开源的部分并没有这些内容。有点像Google开源了一个单机版的Hadoop,可以用这种抽象(Map-reduce)来简化大数据编程,但是实际应用肯定就大大受限制了。
深度学习能解决所有问题吗?
至少目前来看,深度学习只是在Speech和Image这种比较“浅层”的智能问题上效果是比较明显的,而对于语言理解和推理这些问题效果就不那么好了,也许未来的深度神经网络能解决更“智能”的问题,但只是目前还不行。
Google开源TensorFlow的意义
这一次的Google开源深度学习系统TensorFlow在很多地方可以应用,如语音识别,自然语言理解,计算机视觉,广告等等。但是,基于以上论点,我们也不能过分夸大TensorFlow这种通用深度学习框架在一个工业界机器学习系统里的作用。在一个完整的工业界语音识别系统里, 除了深度学习算法外,还有很多工作是专业领域相关的算法,以及海量数据收集和工程系统架构的搭建。
不过总的来说,这次谷歌的开源很有意义,尤其是对于中国的很多创业公司来说,他们大都没有能力理解并开发一个与国际同步的深度学习系统,所以TensorFlow会大大降低深度学习在各个行业中的应用难度。
编程语言:搞实验个人认为当然matlab最灵活了(但是正版很贵),但是更为前途的是python(numpy+scipy+matplotlib)和C/C++,这样组合既可搞研究,…
发表了一篇文章
(2) Theos的用法介绍:
1,更改工作目录
更改工作目录至常用的 iOS 工程目录(如 “ /Users/zhangdasen/Code/”)注:为何要做这一步呢,因为使用Theos创建的工程,是你在哪个目录执行的nic.pl启动的,就会在创建完成后在这个目录生成创建的Theos项目。
2,创建工程
终端输入“/opt/theos/bin/nic.pl”,启动 NIC(New Instance Creator),下面进行简单演示:
3,简单说明
上面创建工程输入了一大堆东西,下面用一张图来说明:
4,工程文件说明
创建好工程就会生成四个文件:
Makefile、Tweak.xm、control、iOSREProject.plist 下面进行简单说明。
(1) Makefile
Makefile 文件指定工程用到的文件、框架、库等信息,将整个过程自动化,下图为里面内容说明。下图是我自己修改过后的Makefile文件。
1,导入 private framework
iOSREProject_PRIVATE_FRAMEWORKS = private framework name
2,链接 Mach-O 对象(Mach-O object)
iOSREProject_LDFLAGS = -lz –lsqlite3.0 –dylib1.o
(2) Tweak.xm
用 Theos 创建 tweak 工程, 认生成的源文件是 Tweak.xm。
如果后缀名是单独一个“ x”,说明源文件支持 Logos 和 C 语法;
如果后缀名是“ xm ”,说明源文件支持 Logos 和 C/C++ 语法,与“ m ”和“ mm ”的区别类似。
Tweak.xm 语法众多,在这里简单介绍一些,详细语法大家可以去看书籍。
指定需要 hook 的 class, 必须以 %end 结尾
%hook SpringBoard
- (void)_menuButtonDown:(id)down {
NSLog(@"You've pressed home button.");
● %orig该指 在 %hook 内部使用,执行被 (hook)的 数的原始代码,如下:
%hook SpringBoard
- (void)_menuButtonDown:(id)down {
NSLog(@"You've pressed home button.");
还可以利用 %orig 更改原始 数的参数,例如:
%hook SBLockScreenDateViewController
- (void)setCustomSubtitleText:(id)arg1 withColor:(id)arg2 {
%orig(@"iOS 8 App Reverse Engineering", arg2); }
这样一来, 锁屏界面原本显示日期的地方就变成了如图的样子。
该指 用于将 %hook 分组,便于代码管理及按条件初始化分组(含义 后有 细解 ),必须以 %end 结 ;一个 %group可以包含多个 %hook,所有不 于某个自定义 group 的 %hook会被隐式归类到 %group _ungrouped 中。
%group iOS7Hook
%hook iOS7Class
- (id)iOS7Method {
id result = %
NSLog(@"This class & method only exist in iOS 7.");
%end // iOS7Hook
%group iOS8Hook
%hook iOS8Class
- (id)iOS8Method {
id result = %
NSLog(@"This class & method only exist in iOS 8."); return
%end // iOS8Hook
该指令用于初始化某个 %group,必须在 %hook 或 %ctor 内调用;如果带参数,则初始化指定的 group,如果不带参数,则初始化 _ungrouped,如下:
%hook SpringBoard
- (void)applicationDidFinishLaunching:(id)application {
% // Equals to %init(_ungrouped)
if (kCFCoreFoundationVersionNumber &= kCFCoreFoundationVersionNumber_iOS_7_0 && kCFCoreFoundationVersionNumber & kCFCoreFoundationVersionNumber_iOS_8_0)
%init(iOS7Hook);
if (kCFCoreFoundationVersionNumber &= kCFCoreFoundationVersionNumber_iOS_8_0)
%init(iOS8Hook);
只有调用了 %init,对应的 %group 才能起作用,切 切 !
在 %hook 内部使用,给一个现有 class 加新函数,功能与 class_addMethod 相同。它的用法如下:
%hook SpringBoard %new
- (void)namespaceNewMethod {
NSLog(@"We've added a new method to SpringBoard.");
tweak的constructor,完成初始化工作;如果不显式定义,Theos会自动生成一个%ctor,并在其中调用 %init(_ungrouped)。因此,
%hook SpringBoard - (void)reboot {
NSLog(@"If rebooting doesn't work then I'm screwed.");
可以成功生效,因为 Theos 隐式定义了如下内容:%ctor
%init(_ungrouped);
%hook SpringBoard
- (void)reboot{
NSLog(@"If rebooting doesn't work then I'm screwed.");
里的 %hook无法生效,因为这里显式定义了%ctor,却没有显式调用 %init,
%group(_ungrouped) 不起作用。
%ctor 一般可以用来初始化 %group,以及进行 MSHookFunction 等操作,如下:
#ifndef kCFCoreFoundationVersionNumber_iOS_8_0
#define kCFCoreFoundationVersionNumber_iOS_8_0 1140.10 #endif
if (kCFCoreFoundationVersionNumber &= kCFCoreFoundationVersionNumber_iOS_7_0 && kCFCoreFoundationVersionNumber & kCFCoreFoundationVersionNumber_iOS_8_0)
%init(iOS7Hook);
if (kCFCoreFoundationVersionNumber &= kCFCoreFoundationVersionNumber_iOS_8_0)
%init(iOS8Hook);
MSHookFunction((void *)&AudioServicesPlaySystemSound, (void *)&replaced_AudioServicesPlaySystemSound, (void **)&original_AudioServicesPlaySystemSound);
注意,%ctor 不需要以 %end 结 。
该指 的作用等同于 objc_getClass 或 NSClassFromString,即动态获 一个类的定义,在 %hook 或 %ctor 内使用。
(3) control文件
control文件 录了deb包管理系统所需的基本信息,会被打包进deb包里。
iOSREProject 里 control 文件的内容如下:
control 文件中可以自定义的字段还有很多,但上面这些信息就已经足够了。更全面的
说明可以参阅 debian 的官方网站()
(3)iOSREProject.plist文件
简单来说就是里面描述了tweak 的作用范围,也就是需要作用的APP的bundle identifier。
也就是在创建项目的时候填写的这个地方:
待续。。。。。。
下个文章继续更新内容 ---- 手动HOOK一个自己的APP,和剩余越狱开发工具简单介绍。
由于本书笔者还没看完呢,所以更新会比较慢,或者有段时间不更新,再次说明,整理这些内容只是为了自己方便查阅笔记,顺便分享给大家,没有它意,希望大家支持原版书籍。
iOS逆向知识,千变万化,无穷无尽,需要学习的太多了,国内文章知识还是比较少,逆向的书籍也是比较少,逆向开发方面的进步也需要我们国人也要努力。
参考书籍: 沙梓社 吴航 * 著
感谢作者。
一、class-dump
简介:顾名思义,就是用来导出目标对象的class信息的工具,私有方法声明也能导出来。
原理:利用 Objective-C语言的 runtime 特…
发表了一篇文章
cycript 详细使用
http://iphonedevwiki.net/index.php/Cycript_Tricks
发表了一篇文章
从Mac DownLoad拷贝到手机 usr/bin
Liam:~ apple$ scp ~/Downloads/Clutch.2.0.2.DEBUG/Clutch-Debug ro…
发表了一篇文章
@interface
Person : NSObject {
NSString * _
@property (strong, nonatomic, readonly) NSString *
@property (nonatomic, readonly)
- (instancetype)initWithName:(NSString *)name age:(int)
@implementation Person
@synthesize name = _
@synthesize age = _
- (instancetype)initWithName:(NSString *)name age:(int)age{
self = [self init];
if (self) {
- (void)setName:(NSString *)name {
if (name != _name) {
- (void)setAge:(int)age {
- (NSString *)name {
- (int)age {
现在我们需要保护这个类的数据,虽然我们在@property里声明了这两个都是readonly,但是因为Objective-C的runtime特性,这个属性说了基本等于没说(对于破解者而言)。 那么我们要怎么做才能保护呢?
防止choose(类名)
我们知道,在Cycript中可以很方便的使用choose(类名)来获取到App中该类所有的实例变量(图1),那么我们就先从这里下手吧!
解决方案: 重载- (NSString *)description方法。效果如图2所示。
- (NSString *)description {
return [NSString stringWithFormat:@"This person is named %@, aged %d.", self.name, self.age];
禁忌,二重存在
上面虽然在cycript中用choose函数拿不到了,但是如果一开始就被Hook了init方法怎么办呢?
解决方案:memcpy一份。
首先确定Person类实例的大小:(类指针大小+所有成员变量大小)
ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
然后就可以愉快的memcpy了:
Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
void * superman = malloc(object_size);
memcpy(superman, (__bridge void *)normal_man, object_size);
在用的时候,通过__bridge转换:
[(__bridge Person *)superman setName:@"Superman"];
代码片段:
Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
void * superman = malloc(object_size);
memcpy(superman, (__bridge void *)normal_man, object_size);
[(__bridge Person *)superman setName:@"Superman"];
[(__bridge Person *)superman setAge:20];
while (1) {
NSLog(@"Normal:
%p %@",normal_man, [normal_man name]);
NSLog(@"Superman: %p %@",superman, [(__bridge Person *)superman name]);
那么为了模拟实际情况(即init方法被Hook,拿到了normal_man的地址),我们直接在NSLog里输出。
使用Cycript攻击的实际效果如图3、图4:
通过Hook init方法,拿到了normal_man的地址0x7fbffbe06b00。
在Cycript中使用choose,只能看见两个字符串。现在直接调用[#0x7fbffbe06b00 setName:@"Cracker"];更改name属性。
可以看到normal_man的name的确被更改了。而我们memcpy的superman表示无压力。
那么superman的地址也被找到了的话,怎么办呢?如图5
P.S 事实上,它也的确被找到了,cycript会检索所有malloc的内存,图4、图5里,choose执行后的两句NSString就是证明,只不过因为我们重载了description方法,才没有直接看到地址。
自己的内存块
那么我们把这个normal_man复制到自己的一个内存区块如何呢?正好借用之前写的MemoryRegion。试试看吧!
代码片段:(其余部分与上面的相同)
ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
MemoryRegion mmgr = MemoryRegion(1024);
void * superman = mmgr.malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size);
实际效果(图6):
可以看到,现在choose找不到处于MemoryRegion中的superman。
不过就算找不到,Cracker还可以Hook这个类的setter和getter呀!我们又要如何应对呢?
虚伪的setter/getter
让我们把setter和getter改成这个样子:
- (void)setName:(NSString *)name {
_name = @"Naive";
- (void)setAge:(int)age {
_age = INT32_MAX;
- (NSString *)name {
return @"233";
- (int)age {
return INT32_MIN;
这样Cracker们通过setter方法就改不了了,也不能通过getter来获取,只能HookIvar了。当然我们也是,那么我们自己要怎么修改呢?添加两个C函数吧!
__attribute__((always_inline)) void setName(void * obj, NSString * newName) {
void * ptr = (void *)((long)(long *)(obj) + sizeof(Person *));
memcpy(ptr, (void*) &newName, sizeof(char) * newName.length);
__attribute__((always_inline)) void setAge(void * obj, int newAge) {
void * ptr = (void *)((long)(long *)obj + sizeof(Person *) + sizeof(NSString *));
memcpy(ptr, &newAge, sizeof(int));
在修改的时候使用:
setName(superman, @"Superman");
setAge (superman, 20);
在获取的时候:
NSLog(@"This person is named %@, aged %d", *((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *))));
加密内存区块
在我们把Person类改成上面那个样子之后,已经能阻止大部分只用cycript就想调戏我们的App的人了。
然而,如果Cracker们搜索内存的话,还是有可能找到一些数据的,比如这里superman的年龄,
superman的内存地址是0x,_age在(0x + sizeof(Person *) + sizeof(NSString *)),也就是0x,如图7。
那么我们不用的时候加密这块内存,用的时候再解密,演示用的加密、解密函数如下,
__attribute__((always_inline)) void encryptSuperman(void ** data_ptr, ssize_t length) {
char * data = (char *) * data_
for (ssize_t i = 0; i & i++) {
data[i] ^= 0xBBC -
__attribute__((always_inline)) void decryptSuperman(void ** data_ptr, ssize_t length) {
char * data = (char *) * data_
for (ssize_t i = 0; i & i++) {
data[i] ^= 0xBBC -
使用代码:
Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
MemoryRegion mmgr = MemoryRegion(1024);
void * superman = mmgr.malloc(object_size);
memcpy(superman, (__bridge void *)normal_man, object_size);
setName(superman, @"Superman");
setAge (superman, 20);
encryptSuperman(&superman, object_size);
while (1) {
NSLog(@"Normal:
%p %@",normal_man,[normal_man name]);
NSLog(@"Superman: %p",superman);
decryptSuperman(&superman, object_size);
NSLog(@"This person is named %@, aged %d",*((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *))));
encryptSuperman(&superman, object_size);
现在再来看看内存里的数据(图8):
嗯,似乎是没问题了呢~
完整示例代码,
/ios/01.html
这一篇文章着重于保护重要数据不被攻击者使用Cycript或者Runtime修改,概要内容如下:
防止choose(类名)
禁忌,二重存在
发表了一篇文章
1.AutoLayout相关的几个易混淆的方法
setNeedsLayout
layoutIfNeeded
layoutSubViews
setNeedsUpdateCon…
发表了一篇文章
一、IPV6-Only支持是啥?
首先IPV6,是对IPV4地址空间的扩充。目前当我们用iOS设备连接上Wifi、4G、3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和企业逐渐部署IPV6 DNS64/NAT64网络之后,设备被分配的地址会变成IPV6的地址,而这些网络就是所谓的IPV6-Only网络,并且仍然可以通过此网络去获取IPV4地址提供的内容。客户端向服务器端请求域名解析,首先通过DNS64 Server查询IPv6的地址,如果查询不到,再向DNS Server查询IPv4地址,通过DNS64 Server合成一个IPV6的地址,最终将一个IPV6的地址返回给客户端。如图所示:
NAT64-DNS64-ResolutionOfIPv4_2x.png
在Mac OS 10.11+的双网卡的Mac机器(以太网口+无线网卡),我们可以通过模拟构建这么一个local IPv6 DNS64/NAT64 的网络环境去测试应用是否支持IPV6-Only网络,大概原理如下:
local_ipv6_dns64_nat64_network_2x.png
参考资料:
二、Apple如何审核支持IPV6-Only?
首先第一点:这里说的支持IPV6-Only网络,其实就是说让应用在 IPv6 DNS64/NAT64 网络环境下仍然能够正常运行。但是考虑到我们目前的实际网络环境仍然是IPV4网络,所以应用需要能够同时保证IPV4和IPV6环境下的可用性。从这点来说,苹果不会去扫描IPV4的专有API来拒绝审核通过,因为IPV4的API和IPV6的API调用都会同时存在于代码中(不过为了减小审核被拒风险,建议将IPV4专有API通过IPV6的兼容API来替换)。
其次第二点:Apple官方声明iOS9开始向IPV6支持过渡,在iOS9.2+支持通过getaddrInfo方法将IPV4地址合成IPV6地址(The ability to synthesize IPv6 addresses was added to getaddrinfo in iOS 9.2 and OS X 10.11.2)。其提供的Reachability库在iOS8系统下,当从IPV4切换到IPV6网络,或者从IPV6网络切换到IPV4,是无法监控到网络状态的变化。也有一些开发者针对这些Bug询问Apple的审核部门,给予的答复是只需要在苹果最新的系统上保证IPV6的兼容性即可。
最后第三点:只要应用的主流程支持IPV6,通过苹果审核即可。对于不支持IPV6的模块,考虑到我们现实IPV6网络的部署还需要一段时间,短时间内不会影响我们用户的使用。但随着4G网络IPV6的部署,这部分模块还是需要逐渐安排人力进行支持。
追加第四点:如果应用一直直接使用IPV4地址通过NSURLConenction或者NSURLSession进行网络请求(一般需要服务器允许,且客户端需要在header中伪装host);经测试,IPV6网络环境下,直接使用IPV4地址在iOS9及以上的系统仍然能够正常访问;在iOS8.4及以下不能正常访问;这一点苹果的解释和建议是这样的:
Note: In iOS 9 and OS X 10.11 and later, NSURLSession and CFNetwork automatically synthesize IPv6 addresses from IPv4 literals locally on devices operating on DNS64/NAT64 networks. However, you should still work to rid your code of IP address literals.
三、应用如何支持IPV6-Only?
对于如何支持IPV6-Only,官方给出了如下几点标准:(这里就不对其进行解释了,大家看上面的参考链接即可)
1. Use High-Level Networking F
2. Don’t Use IP Address L
3. Check Source Code for IPv6 DNS64/NAT64 I
4. Use System APIs to Synthesize IPv6 A
3.1 NSURLConnection是否支持IPV6?
官方的这句话让我们疑惑顿生:using high-level networking APIs such as NSURLSession and the CFNetwork frameworks and you connect by name, you should not need to change anything for your app to work with IPv6 addresses
只说了NSURLSession和CFNetwork的API不需要改变,但是并没有提及到NSURLConnection。 从上文的参考资料中,我们看到NSURLSession、NSURLConnection同属于Cocoa的url loading system,可以猜测出NSURLConnection在ios9上是支持IPV6的。
应用里面的API网络请求,大家一般都会选择AFNetworking进行请求发送,由于历史原因,应用的代码基本上都深度引用了AFHTTPRequestOperation类,所以目前API网络请求均需要通过NSURLConnection发送出去,所以必须确认NSURLConnection是否支持IPV6. 经过测试,NSURLConnection在最新的iOS9系统上是支持IPV6的。
3.2 Cocoa的URL Loading System从iOS哪个版本开始支持IPV6?
目前我们的应用最低版本还需要支持iOS7,虽然苹果只要求最新版本支持IPV6-Only,但是出于对用户负责的态度,我们仍然需要搞清楚在低版本上URL Loading System的API是否支持IPV6.
(to fix me, make some experiments)待续~~~
3.3 Reachability是否需要修改支持IPV6?
我们可以查到应用中大量使用了Reachability进行网络状态判断,但是在里面却使用了IPV4的专用API。
在Pods:Reachability中
Files:Reachability.m
struct sockaddr_in
Files:Reachability.h , Reachability.m
那Reachability应该如何支持IPV6呢?
(1)目前Github的开源库Reachability的最新版本是3.2,苹果也出了一个Support IPV6 的Reachability的官方样例,我们比较了一下源码,跟Github上的Reachability没有什么差异。
(2)我们通常都是通过一个0.0.0.0 (ZeroAddress)去开启网络状态监控,经过我们测试,在iOS9以上的系统上IPV4和IPV6网络环境均能够正常使用;但是在iOS8上IPV4和IPV6相互切换的时候无法监控到网络状态的变化,可能是因为苹果在iOS8上还并没有对IPV6进行相关支持相关。(但是这仍然满足苹果要求在最新系统版本上支持IPV6的网络)。
(3)当大家都在要求Reachability添加对于IPV6的支持,其实苹果在iOS9以上对Zero Address进行了特别处理,是这样的:
reachabilityForInternetConnection: This monitors the address 0.0.0.0,
which reachability treats as a special token that causes it to actually
monitor the general routing status of the device, both IPv4 and IPv6.
+ (instancetype)reachabilityForInternetConnection {
struct sockaddr_in zeroA
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress];
综上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPV6和IPV4,但是在iOS9以下会存在bug,但是苹果审核并不关心。
四、底层的socket API如何同时支持IPV4和IPV6?
由于在应用中使用了网络诊断的组件,大量使用了底层的 socket API,所以对于IPV6支持,这块是个重头戏。如果你的应用中使用了长连接,其必然会使用底层socket API,这一块也是需要支持IPV6的。 对于Socket如何同时支持IPV4和IPV6,可以参考谷歌的开源库.
下面我针对我们的开源 , 说一下是如何同时支持IPV4和IPV6的。
开源地址:
这个网络诊断组件的主要功能如下:
本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);
通过TCP Connect监测到域名的连通性;
通过Ping 监测到目标主机的连通耗时;
通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;
4.1 IP地址从二进制到符号的转化
之前我们都是通过inet_ntoa()进行二进制到符号,这个API只能转化IPV4地址。而inet_ntop()能够兼容转化IPV4和IPV6地址。 写了一个公用的in6_addr的转化方法如下:
+(NSString *)formatIPV6Address:(struct in6_addr)ipv6Addr{
NSString *address = nil;
char dstStr[INET6_ADDRSTRLEN];
char srcStr[INET6_ADDRSTRLEN];
memcpy(srcStr, &ipv6Addr, sizeof(struct in6_addr));
if(inet_ntop(AF_INET6, srcStr, dstStr, INET6_ADDRSTRLEN) != NULL){
address = [NSString stringWithUTF8String:dstStr];
+(NSString *)formatIPV4Address:(struct in_addr)ipv4Addr{
NSString *address = nil;
char dstStr[INET_ADDRSTRLEN];
char srcStr[INET_ADDRSTRLEN];
memcpy(srcStr, &ipv4Addr, sizeof(struct in_addr));
if(inet_ntop(AF_INET, srcStr, dstStr, INET_ADDRSTRLEN) != NULL){
address = [NSString stringWithUTF8String:dstStr];
4.2 本机IP获取支持IPV6
相当于我们在终端中输入ifconfig命令获取字符串,然后对ifconfig结果字符串进行解析,获取其中en0(Wifi)、pdp_ip0(移动网络)的ip地址。
(1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。
(2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。
+ (NSString *)deviceIPAdress
while (temp_addr != NULL) {
NSLog(@"ifa_name===%@",[NSString stringWithUTF8String:temp_addr-&ifa_name]);
if ([[NSString stringWithUTF8String:temp_addr-&ifa_name] isEqualToString:@"en0"] || [[NSString stringWithUTF8String:temp_addr-&ifa_name] isEqualToString:@"pdp_ip0"])
if (temp_addr-&ifa_addr-&sa_family == AF_INET){
address = [self formatIPV4Address:((struct sockaddr_in *)temp_addr-&ifa_addr)-&sin_addr];
else if (temp_addr-&ifa_addr-&sa_family == AF_INET6){
address = [self formatIPV6Address:((struct sockaddr_in6 *)temp_addr-&ifa_addr)-&sin6_addr];
if (address && ![address isEqualToString:@""] && ![address.uppercaseString hasPrefix:@"FE80"]) break;
temp_addr = temp_addr-&ifa_
4.3 设备网关地址获取获取支持IPV6
其实是在IPV4获取网关地址的源码的基础上进行了修改,初开把AF_INET-&AF_INET6, sockaddr -& sockaddr_in6之外,还需要注意如下修改,就是拷贝的地址字节数。去掉了ROUNDUP的处理。 (解析出来的地址老是少了4个字节,结果是偏移量搞错了,纠结了半天),具体参考源码库。
int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, RTF_GATEWAY};
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &l, 0, 0) & 0) {
address = @"192.168.0.1";
for (i = 0; i & RTAX_MAX; i++) {
if (rt-&rtm_addrs & (1 && i)) {
sa_tab[i] =
sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa-&sa_len));
sa_tab[i] = NULL;
for (i = 0; i & RTAX_MAX; i++) {
if (rt-&rtm_addrs & (1 && i)) {
sa_tab[i] =
sa = (struct sockaddr_in6 *)((char *)sa + sa-&sin6_len);
sa_tab[i] = NULL;
4.4 设备DNS地址获取支持IPV6
IPV4时只需要通过res_ninit进行初始化就可以获取,但是在IPV6环境下需要通过res_getservers()接口才能获取。
+(NSArray *)outPutDNSServers{
res_state res = malloc(sizeof(struct __res_state));
int result = res_ninit(res);
NSMutableArray *servers = [[NSMutableArray alloc] init];
if (result == 0) {
union res_9_sockaddr_union *addr_union = malloc(res-&nscount * sizeof(union res_9_sockaddr_union));
res_getservers(res, addr_union, res-&nscount);
for (int i = 0; i & res-& i++) {
if (addr_union[i].sin.sin_family == AF_INET) {
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN);
NSString *dnsIP = [NSString stringWithUTF8String:ip];
[servers addObject:dnsIP];
NSLog(@"IPv4 DNS IP: %@", dnsIP);
} else if (addr_union[i].sin6.sin6_family == AF_INET6) {
char ip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN);
NSString *dnsIP = [NSString stringWithUTF8String:ip];
[servers addObject:dnsIP];
NSLog(@"IPv6 DNS IP: %@", dnsIP);
NSLog(@"Undefined family.");
res_nclose(res);
free(res);
return [NSArray arrayWithArray:servers];
4.4 域名DNS地址获取支持IPV6
在IPV4网络下我们通过gethostname获取,而在IPV6环境下,通过新的gethostbyname2函数获取。
phot = gethostbyname(hostN)
phot = gethostbyname2(hostN, AF_INET6)
4.5 ping方案支持IPV6
Apple的官方提供了最新的支持IPV6的ping方案,参考地址如下:
只是需要注意的是:
(1)返回的packet去掉了IPHeader部分,IPV6的header部分也不返回TTL(Time to Live)字段;
(2)IPV6的ICMP报文不进行checkSum的处理;
4.6 traceRoute方案支持IPV6
其实是通过创建socket套接字模拟ICMP报文的发送,以计算耗时;
两个关键的地方需要注意:
(1)IPV6中去掉IP_TTL字段,改用跳数IPV6_UNICAST_HOPS来表示;
(2)sendto方法可以兼容支持IPV4和IPV6,但是需要最后一个参数,制定目标IP地址的大小;因为前一个参数只是指明了IP地址的开始地址。千万不要用统一的sizeof(struct sockaddr), 因为sockaddr_in 和 sockaddr都是16个字节,两者可以通用,但是sockaddr_in6的数据结构是28个字节,如果不显式指定,sendto方法就会一直返回-1,erroNo报22 Invalid argument的错误。
关键代码如下:(完整代码参考开源组件)
NSString *ipAddr0 = [serverDNSs objectAtIndex:0];
NSData *addrData = nil;
BOOL isIPV6 = NO;
if ([ipAddr0 rangeOfString:@":"].location == NSNotFound) {
isIPV6 = NO;
struct sockaddr_in nativeAddr4;
memset(&nativeAddr4, 0, sizeof(nativeAddr4));
nativeAddr4.sin_len = sizeof(nativeAddr4);
nativeAddr4.sin_family = AF_INET;
nativeAddr4.sin_port = htons(udpPort);
inet_pton(AF_INET, ipAddr0.UTF8String, &nativeAddr4.sin_addr.s_addr);
addrData = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
isIPV6 = YES;
struct sockaddr_in6 nativeAddr6;
memset(&nativeAddr6, 0, sizeof(nativeAddr6));
nativeAddr6.sin6_len = sizeof(nativeAddr6);
nativeAddr6.sin6_family = AF_INET6;
nativeAddr6.sin6_port = htons(udpPort);
inet_pton(AF_INET6, ipAddr0.UTF8String, &nativeAddr6.sin6_addr);
addrData = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
struct sockaddr *
destination = (struct sockaddr *)[addrData bytes];
if ((recv_sock = socket(destination-&sa_family, SOCK_DGRAM, isIPV6?IPPROTO_ICMPV6:IPPROTO_ICMP)) & 0)
if ((send_sock = socket(destination-&sa_family, SOCK_DGRAM, 0)) & 0)
if ((isIPV6?
setsockopt(send_sock,IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)):
setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))) & 0)
ssize_t sentLen = sendto(send_sock, cmsg, sizeof(cmsg), 0,
(struct sockaddr *)destination,
isIPV6?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in));
原文链接: /p/a6bab07c4062
iOS应用支持IPV6,就那点事儿
果然是苹果打个哈欠,iOS行业内就得起一次风暴呀。自从5月初Apple明文规定所有开发者在6月1号以后提交新版本需要支持IPV6-Only…
发表了一篇文章
iOS在某些特定时刻需要把内容重一个app拷贝到另一个app 这时候我们就可以使用剪切板UIPasteboard
2、iOS自带剪切板操作的原生UI控件

发表了一篇文章
1:应用启动时间 应用启动时,只加载启动相关的资源和必须在启动时加载的资源。
2:本地图片加载方式 本地图片加载常用方法有两种: (1),[UIImage imageName…
发表了一篇文章
import sys
import time
import hashlib
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib
第一步 , 声明一些变量
project_path = "/Users/xx/project"
app_path = "/Users/xx/project/build/Build/Products/Release-iphoneos/xxx.app"
build_path = "build"
targerIPA_parth = "/Users/xx/Desktop"
第二步,clean,和创建一个文件夹,这里的示例是针对有使用cocoaPods的项目 , 如果没有使用 不用创建文件夹 ,命令自行简化
def clean_project_mkdir_build():
os.system('cd %s;xcodebuild clean' % project_path)
os.system('cd %s;mkdir build' % project_path)
%s 是py的占位符,字符串类型。后面是真正的填充。
第三步编译项目
def build_project():
print("build release start")
os.system ('cd %s;xcodebuild -list')
os.system ('cd %s;xcodebuild -workspace xxx.xcworkspace
-scheme xxx -configuration release -derivedDataPath %s ONLY_ACTIVE_ARCH=NO || exit' % (project_path,build_path))
不知道scheme是啥的xcodebuild
-list 自己查
第四步 打包
def build_ipa():
global ipa_filename
ipa_filename = time.strftime('yourproject_%Y-%m-%d-%H-%M-%S.ipa',time.localtime(time.time()))
os.system ('xcrun -sdk iphoneos PackageApplication -v %s -o %s/%s' % (app_path,targerIPA_parth,ipa_filename))
然后你现在再编写个方法,按顺序调用就可以编译打包了 ,执行完会看到桌面的ipa
def main():
clean_project_mkdir_build()
build_project()
build_ipa()
执行就在最下面直接调用就行了 main()
我们是把代码上传到fir测试的,如果你们用的蒲公英或者其他,请自行搜索。
通过 gem install fir-cli 如果你没有ruby环境,自行搜索
安装完成后,在命令行输入fir 回车 。会有fir的命令提示。我们上传fir需要fir的API_TOKEN , 去fir官网登录找好就能找到。
拿到那一串串字符,在变量区加上
fir_api_token = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
然后命令传入ipa目录和token就可以上传了
def upload_fir():
if os.path.exists("%s/%s" % (targerIPA_parth,ipa_filename)):
print('watting...')
ret = os.system("/usr/local/bin/fir p '%s/%s' -T '%s'" % (targerIPA_parth,ipa_filename,fir_api_token))
print("没有找到ipa文件")
这里也有遇到一个=坑,就是在终端直接fir 带后面的就可以执行 ,但是在这里识别不了命令,必须制定全路径,怎么找命令的全路径呢?终端输入 which fir
具体发邮件功能看代码,这里有几个变量。我使用的是新浪邮箱发的,smtp服务器 , 如果你是 pop3 请更换,还要在邮箱里开启相应的服务
from_addr = ""
password = "*****"
smtp_server = ""
to_addr = ','
然后发邮件的方法
我们的fir路径是固定的
def send_mail():
msg = MIMEText('xxx iOS测试项目已经打包完毕,请前往 http://fir.im/xxxxx 下载测试!', 'plain', 'utf-8')
msg['From'] = _format_addr('自动打包系统 &%s&' % from_addr)
msg['To'] = _format_addr('xxx测试人员 &%s&' % to_addr)
msg['Subject'] = Header('xxx iOS客户端打包程序', 'utf-8').encode()
server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
然后执行顺序是这样的
def main():
clean_project_mkdir_build()
build_project()
build_ipa()
upload_fir()
send_mail()
本文重点在自动打包命令上,python代码感兴趣的可以去 学习。
本文代码已经托管在github上:
原文 http://stonedu.site//iOS-%E6%9C%AC%E5%9C%B0%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7/
1、为什么要自动打包工具?
每修改一个问题,测试都让你打包一个上传fir , 你要clean -& 编译打包 -& 上传fir -& 通知测试。而且打包速度好慢,太浪…
362504人浏览
232207人浏览
216645人浏览
178820人浏览
124971人浏览

我要回帖

更多关于 用友加盟 的文章

 

随机推荐