JOBridge之四使用方法(可用代替JSPatch)

本博客为原创为 吸血鬼de晚餐,转载随意,但请标明出处。
博客原文 JOBridge之四使用方法(可用代替JSPatch)

CSDN真恶心🤢,把我之前的一些博客爬取下来,然后挂在一些僵尸账号就成了其原创博文,甚至连发布时间都不改的!

JSPatch被苹果爸爸封了以后hotfix怎么办?业务需求需要尽快上线怎么办?可以尝试使用JOBridge。其使用了和JSPatch不一样的实现方案,全部由OC实现,比JSPatch功能更强,性能也更好,语法上也基本保持一致(之后我会给出一些示例),当然最关键的是苹果爸爸还不知道!

原理方案

请移步本人博客
JOBridge之一任意方法的Swizzle(可用代替JSPatch) https://www.jianshu.com/p/905e06eeda7b
JOBridge之二JS注册类和访问所有Native方法(可用代替JSPatch) https://www.jianshu.com/p/f457528fedeb
JOBridge之三C函数OC化和语法增加以及优化(可用代替JSPatch) https://www.jianshu.com/p/c1161f61ed96

使用方法

开始之前,先说明一下通用调用桥。

*JOBridge通用调用桥:

在js的Object.prototype中添加了一个_oc_的方法,其负责转换处理js传递过来的参数,然后调用OC的对应的方法,最后将返回值回传给js。这种方式被我称为JOBridge通用调用桥。通过该桥,JOBridge将OC几乎所有的方法开放给了js,C方法也可以通过其开放给js(需要预先挖坑,C函数入口地址需要硬编译)。

OC端:

1、执行js
[JOBridge bridge];//初始化
[JOBridge evaluateScript:script];//执行js

这两句就可以把JS代码跑起来了。

2、扩展C方法(需要的时候再使用)

OC方法一般都是可以直接调用的,C方法使用之前需要挖坑。通过C方法OC化,已经使挖坑变得比较简单了。扩展有三种方式:

第一种不替换存储字典,放在全局默认C方法容器对象下,调用registerObject:name:needTransform:并向js中注册对象JC,js中用JC.test()来访问。其中needTransform表示是否使用通用调用桥处理参数和调用,如果需要的话,之前需要调用JOMapCFunction注册OC的方法并给以签名。

#if __arm64__

@interface JOCPlugin : JOCFunction
@end
void test(id obj) { NSLog(@"%@", obj); }


static NSMutableDictionary *JOTest;

@implementation JOCPlugin
+ (void)load {
    [self registerPlugin];
}

+ (void)initPlugin {//重写本方法,JOBridge初始化时会调用本方法注册
    JOMapCFunction(test, JOSigns(void, id));
    [self registerObject:JOMakeObj([self class]) name:@"JC" needTransform:YES];
}
@end
#endif

第二种替换存储字典(需要重写pluginStore方法),比如这里将“test”方法和对应签名关系,放在字典JCTest中,在js里用JCTest.test()访问。needTransform同理。

#if __arm64__
@interface JOCPluginTest : JOCFunction
@end

void test(id obj) { NSLog(@"%@", obj); }

static NSMutableDictionary *JOTest;

@implementation JOCPluginTest
+ (void)load {
    JOTest = [NSMutableDictionary dictionary];
    [self registerPlugin];
}

+ (void)initPlugin {
    JOMapCFunction(test, JOSigns(void, id));
    [self registerObject:JOMakeObj([self class]) name:@"JCTest" needTransform:YES];
}

+ (NSMutableDictionary *)pluginStore {
    return JOTest;
}
@end
#endif
3、扩展全局常量

这里放在JCTest1下(需要重写pluginStore方法),在js中用JCTest1.RGB(), JCTest1.RED_VALUE 访问。这种方式也可以提供方法调用,其不使用通用调用桥处理参数,所以需要自己解析参数组装参数,较为麻烦,但可以比较灵活的提供一些特殊的方法。

#if __arm64__
@interface JOCPluginTest1 : JOPluginBase
@end


static NSMutableDictionary *JOTest1;

@implementation JOCPluginTest1
+ (void)load {
    JOTest1 = [NSMutableDictionary dictionary];
    [self registerPlugin];
}

+ (void)initPlugin {
    [self pluginStore][@"RGB"] = ^(JSValue *jsvalue) {
        uint32_t hex = [jsvalue toUInt32] ;
        return JOMakeObj([UIColor colorWithRed:(((hex & 0xFF0000) >> 16))/255.0 green:(((hex & 0xFF00) >> 8))/255.0 blue:((hex & 0xFF))/255.0 alpha:1.0]);
    };
    [self pluginStore][@"RED_VALUE"] = @(0xFF0000);
    [self registerObject:[self pluginStore] name:@"JGTest1" needTransform:NO];

}

+ (NSMutableDictionary *)pluginStore {
    return JOTest1;
}
@end
#endif

JS端:

JS的语法我尽量让其类似于JSPatch,减少学习成本,但是还是有很许多不一样的地方,而且内容比较多,一点一点说明。

本博客为原创为 吸血鬼de晚餐,转载随意,但请标明出处。

全局方法

__weak/__strong/__bridge_id/__bridge_pointer:这些方法也就不用多说了。

JOC方法

主功能对象JOC,包含最常见和通用功能,其不走JOBridge的通用调用桥。

1、interface:最重要的方法,其用于注册类,替换方法等,所有功能都依赖其实现。语法JOC.interface("类名:父类名<协议名>", 属性字典, 对象方法字典, 类方法字典);实例如下

;JOC.interface('UserView : UIView', {
    watchView:['id','strong'],
 },
 {
     initWithFrame_:function(frame) {
                //创建对象返回
     },
},
null);

协议最多32个,也可以省略。属性字典,前者属性名字,后面是属性关键字,暂不支持非OC对象,所以全部使用

['id','strong']就行了。方法字典,key就是方法名字,下划线_会替换成:__替换成_。js调用本方法语法为userView.initWithFrame_(frame);

2、class:通过该方法访问已有的类Class。例如:JOC.class("UIView").new()。可以创建一个UIView对象。Foundation和UIKit常用的类都已经作为常量开放给js,直接使用类名即可访问,例如:UIView.new();

具体参见:

#define JO_UIKit_Classes @[@"UIImage",@"UIColor",@"UIView",@"UITableView",@"UILabel",@"UIButton",\
@"UIImageView",@"UIApplication",@"UITableViewCell",@"UIAlertView",@"UINavigationController",\
@"UIViewController",@"UIFont",@"UIScreen",@"UIScrollView",@"UINavigationItem",@"UINavigationBar",\
@"UIWebView",@"UIWindow",@"UITextView",@"UITextField",@"UITapGestureRecognizer",@"UITabBarController",\
@"UITabBar",@"UISwitch",@"UISlider",@"UISearchBar",@"UIProgressView",@"UICollectionView",\
@"UICollectionViewCell",@"UIBarButtonItem",@"UIAppearance",@"UIAlertController"]

#define JO_Foundation_Classes @[@"NSTimer",@"NSMutableArray",@"NSArray",@"NSDictionary",@"NSMutableDictionary",\
@"NSString",@"NSUserDefaults",@"NSURL",@"NSURLRequest",@"NSData",@"NSDate",@"NSCharacterSet",@"NSMutableString",\
@"NSDateFormatter",@"NSError",@"NSException",@"NSFileManager",@"NSJSONSerialization",@"NSLock",@"NSNotification",\
@"NSRegularExpression",@"NSSet",@"NSThread",@"NSURLResponse",@"NSValue",@"NSAttributedString",@"NSURLSession",\
@"NSPredicate",@"NSTask",@"NSLocale",@"NSInvocation",@"NSMethodSignature",@"NSMutableAttributedString",@"NSBundle"]

3、storeObject:将新对象存储对象容器中。这个需要举例具体说明。

initWithFrame_:function(frame) {
    var inited = self.JOSUPER_initWithFrame_(frame);
    if (inited) {
        JOC.storeObject(self, inited);
        return self;
    }
    return null;
 },

通过调用父类的JOSUPER_initWithFrame_创建得到了一个新的对象inited,但是因为是在js语言中,其无法直接赋值给self,所以需要通过storeObject来间接赋值给self。js中拿到的OC对象或数据都是经过包装的,比如这里的self和inited都是由JOObj对象,其内部引用着self。storeObject就是将JOObj中的引用对象改为inited所引用的对象。

4、retain/release:手动计数器管理,不用多说

5、new:快捷对象创建,实例:JOC.new("UIView"),可以用来代替JOC.class("UIView").new()或者JOC.class("UIView").alloc().init()

6、structToObject:有些情况下需要访问结构体内部的数据,需要将其转成js能识别的对象。比如:CGRect转成{'x':0,'y':0,'width':375,'height':44}。目前支持几种常见的结构体:CGRect,CGPoint,CGSize,NSRange,CGAffineTransform,UIEdgeInsets,UIOffset。如果js不访问结构体内部数据,则不需要调用该方法,直接当普通参数传递即可。

7、getPointerValue/setPointerValue:获取指针对象JOPointerObj中包装的指针值,同理赋值也一样。实例:

array.enumerateObjectsUsingBlock_(['@@@Q*',function(obj, idx, stop) 
{
     JOC.log('obj: ' + JOC.getString(obj) + ' idx: ' + idx + ' stop: ' + stop);
     JOC.setPointerValue(stop, true);
}]);

调用NSArray的枚举,在回调中把stop设置为true。

8、getPointerDoubleValue/setPointerDoubleValue:和上面的同理,数据类型为浮点数。

9、malloc/free:分配/释放内存。实例:

var ptr = JOC.malloc(8);
JOC.setPointerDoubleValue(ptr, 123.56);
JOC.log('double value: ' + JOC.getPointerDoubleValue(ptr));
JOC.free(ptr);

这里分配了一个8Byte的内存空间,调用setPointerDoubleValue设置值,然后输出,最后调用free。其主要用途是用来解决指针参数传递子函数,子函数修改数据的问题。但限于autoreleasepool的问题 ,对于OC对象无法支持,目前只支持基础类型。

例如:

var changePtr = function(ptr) {
    JOC.setPointerValue(ptr, 100);
};
function() {
    var ptr = JOC.malloc(8);
      changePtr(p);
    JOC.free(ptr);
}();

10、array:创建OC数组。JOC.array(['Bill', 'UserInfo'])

11、selector:获取selector,参数selector名字字符串。

12、log:日志输出

本博客为原创为 吸血鬼de晚餐,转载随意,但请标明出处。

新增对象方法:

有签名的情况下替换方法,只需要在 JOC.interface("类名:父类名<协议名>", 属性字典, 对象方法字典, 类方法字典); 中的 对象方法字典 添加对应的key->function就行了,如果想新写一个方法怎么办呢?添加一个key->array[sign string, function],实例如下:

FitIphone5_:['d@:d', function(value) {
        return JOG.SCREEN_WIDTH > 320 ? value : value - 10;
}],

这里新增了一个名为FitIphone5:方法,然后js通过对应对象obj.FitIphone5_(width)来调用。可以看到这里和替换不同的是,FitIphone5_对应的是一个数组,第一个元素是签名字符串,第二个元素对应的函数。至于为什么搞成数组,呃…是因为懒,最初随手定义的,后来就懒得改了。

JS调用OC方法规则:

如果使用JOBridge通用调用桥,selector和参数需要遵循一些约定。

参数规则:

1、第一个参数固定的是selectorName。这个参数不用手动传递,由脚本根据规则生成,沿用了JSPatch的办法。例如:

tableView.setDelegate_(self),会被转成tableView._oc_('setDelegate_',self)

注意末尾"_"不能省略。

2、第二个参数不同的情况不太一样,有可能作为特殊参数,目前只有一种特殊情况,就是调用有匿名参数函数时进行签名,其他情况下当普通参数即可。举个例子:

var aString = NSString.JOVAR_stringWithFormat_('@@:@@@i@Bd','this is %@  %@ %d %@ %d %.2f', 'a', 'test', 90, self, true ,329.78398);

'@@:@@@i@Bd'为签名,JOBridge通用调用桥会根据该签名去解析参数,'this is %@ %@ %d %@ %d %.2f' 为格式化字符串,后面则是剩余参数。

手动签名请用简单方式,不要带参数长度,或者复杂的符号,因为这里用的是最简单的签名识别方法,复杂的识别方法还没有实现。OC对象全部用@,其他的基础类型对应就行。

3、余下的参数位都是普通参数,依次传递即可。

4、注意:为了让JOBridge运行的更快,对于Number/NSNumber,String/NSString我都沿用原数据,其可以被js和OC直接共享使用,没有转成JOObj封装对象,所以必要时需要注意其带来的局限性和副作用,比如:不能使用__bridge_pointer转指针。

5、block参数:

如果需要传递block参数,也和类新增方法语法类似,例如:

self.anLable().mas__makeConstraints_(
    ['v@@', function(make) {
        make.left().equalTo()(self.anIcon().mas__right()).valueOffset()(5);
        make.top().equalTo()(self.accountLable().mas__bottom()).valueOffset()(self.FitIphone5_(17));
        make.width().equalTo()(150);
        make.height().equalTo()(20);
    }]
);

如果只是创建局部的调用,由js自己使用,不需要传递给JOBridge,那就创建一个function直接使用即可。

6、返回block调用:

make.left().equalTo()(self.anIcon().mas__right()).valueOffset()(5);

在上面的语句中,equalTo()和valueOffset()都返回的是block,再次调用即可。容易出错,需要多加小心。

selector规则:

1、js所有对OC对象属性,方法的使用都是通过方法调用来完成的(除了字典数据)。

2、若走JOBridge通用调用桥,OC方法中所有的变成___变成_。例如:

nav.navigationBar().setBackgroundImage_forBarMetrics_(image, JOG.UIBarMetricsDefault);

3、所有的赋值操作需要调用对应的setter方法、JOSetIvarJOSetIvaI或其他有赋值功能的方法完成。等号赋值只对js变量有效。

4、调用父类方法请在前面添加前缀JOSUPER_

5、调用变长参数(匿名参数)方法添加前缀JOVAR_

6、特殊方法JOGetIvarJOSetIvar。在某些情况下,我们需要访问类的成员变量,而又不想通过getter,setter的方式,那么就需要使用这俩方法了。实例如下:
我们重写tableView的getter方法,在该方法体里面在使用getter访问自己,就会死循环,所以需要另外一种方法来完成。

tableView:function() {
    if (!self.JOGetIvar('_tableView')) {
        var rect = JC.CGRectMake(0, 64, JOG.SCREEN_WIDTH, JOG.SCREEN_HEIGHT - 64);
        var tableView = UITableView.alloc().initWithFrame_style_(rect, 
                                                               JOG.UITableViewStyleGrouped);
        self.view().addSubview_(tableView);
        tableView.setDelegate_(self);
        tableView.setBounces_(false);
        tableView.setDataSource_(self);
        tableView.setSectionFooterHeight_(4);
        tableView.setSectionHeaderHeight_(4);
        tableView.setBackgroundColor_(JOG.RGB(0xF5F5F9));
        tableView.setTableHeaderView_(
                            UIView.alloc().initWithFrame_(JC.CGRectMake(0,0,375,0.01)));
        tableView.setSeparatorStyle_(JOG.UITableViewCellSeparatorStyleNone);
        self.JOSetIvar('_tableView', tableView);
    }
    return self.JOGetIvar('_tableView');
},

以上是通过懒加载的方式创建tableView,其通过self.JOGetIvar('_tableView')来获取成员对象是否已经有值,没有则创建,完成后调用self.JOSetIvar('_tableView', tableView);赋值。

_tableView,成员变量name,如果没有手动修改名字,默认都是_+name。

6、特殊方法JOGetIvarIJOSetIvaI。这两者和上面两者类似,不同之处在于其负责操作基础数据类型。

C函数调用

JC (C方法在js中关联的全局对象)

看过JOBridge实现原理就知道,C函数会被转成OC方法来调用,然后走JOBridge通用调用桥。默认添加在了全局对象JC名下,目前选取了GCD,Foundation,UIKit,CGRect,CGPath,CGColor,CGContext,CGAffineTransform,CGImage,CGBitmapContext,Math大部分常用函数,CoreFundation方法太多就只添加了几个。如果有需要,可以参照上面的扩展C方法,自行添加。

举些例子

//ImageContext
JC.UIGraphicsBeginImageContextWithOptions(JC.CGSizeMake(r.width, r.height), false, UIScreen.mainScreen().scale());
//这是一个包裹指针的JOPointerObj对象,js不用处理,直接传给C/OC即可
var context = JC.UIGraphicsGetCurrentContext();
JC.CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor());
JC.CGContextSetLineWidth(context, 2);

//CGD
var aself = __weak(self);
JC.dispatch__async(JC.dispatch__get__main__queue(),['v@',function()
{
    JOC.log('dispatch_async is work ' + aself);
}]);
//CGRect
JC.CGRectMake(0, 0, JOG.SCREEN_WIDTH, 45);
//Math
JC.pow(10, -5 * JC.sinh(numTime * JOG.M_PI_2))
JOG (OC全局常量在js中的关联对象)

Foundation和UIKit下的绝大多数全局常量都被包含在内,顺带加上了GCD,方便使用。

JOG.M_PI_2;
//Foundation
JOG.NSFontAttributeName;
JOG.NSForegroundColorAttributeName;
JOG.NSCalendarUnitSecond;
JOG.NSUTF8StringEncoding;
//UIKit
JOG.UIStatusBarStyleDefault;
JOG.UITableViewCellAccessoryDisclosureIndicator;
JOG.UIApplicationStateBackground;
JOG.UIControlStateNormal;
//GCD
JOG.DISPATCH_SOURCE_TYPE_TIMER;
JOG.DISPATCH_QUEUE_SERIAL

另外JOG下还可以添加不走"JOBridge通用调用桥"的方法。例如:

JOG.RGB(0xFF0000);

对应实现见下方,其需要手动处理参数然后调用对应UIColor创建方法,最后调用JOGetObj包装成JOObj对象返回。

pluginStore[@"RGB"] = ^(JSValue *jsvalue) {
    uint32_t hex = [jsvalue toUInt32] ;
    return JOGetObj([UIColor colorWithRed:(((hex & 0xFF0000) >> 16))/255.0 green:(((hex & 0xFF00) >> 8))/255.0 blue:((hex & 0xFF))/255.0 alpha:1.0]);
};

null处理

对于可能为空的数据,调用方法之前,一定要检查null,否则会js报错。

本博客为原创为 吸血鬼de晚餐,转载随意,但请标明出处。

实例一:

简单的UITableView使用

;JOC.interface('JDViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>',
{
   data:['NSMutableArray','strong'],
   tableView:['UITableView','strong'],
}, {
   viewDidLoad:function() {
       var arr = JOC.class('NSMutableArray').alloc().init();
       
       arr.addObject_('这是第1个cell');
       arr.addObject_('这是第2个cell');
       arr.addObject_('这是第3个cell');
       self.setData_(arr);
       
       self.setTitle_('JDViewController');
       self.view().setBackgroundColor_(JOC.class('UIColor').whiteColor());
       
       
       var rect = JC.CGRectMake(0, 60, 375, 500);
       var r = JOC.structToObject(rect);
       JOC.log(r.x + ' ' + r.y + '  ' + r.width + ' ' + r.height);
       
       var tableView = JOC.class('UITableView').alloc()
       .initWithFrame_style_(rect,JOG.UITableViewStyleGrouped);
       tableView.setDelegate_(self);
       tableView.setDataSource_(self);
       self.view().addSubview_(tableView);
       self.setTableView_(tableView);
   },
               
   viewDidAppear_:function(animated) {
      
   },
               
   numberOfSectionsInTableView_: function(tableView) {
       return 1;
   },
               
   tableView_numberOfRowsInSection_: function(tableView, section) {
       return self.data().count();
   },
               
   tableView_cellForRowAtIndexPath_: function(tableView, indexPath) {
       JOC.log('tableVIew: '+ tableView);
       
       var cell = tableView.dequeueReusableCellWithIdentifier_('cell')
       if (!cell) {
           cell = JOC.class('UITableViewCell').alloc()
           .initWithStyle_reuseIdentifier_(JOG.UITableViewCellStyleValue1, 'cell')
       }
               
       cell.textLabel().setText_(self.data().objectAtIndex_(indexPath.row()));
       return cell;
   },
               
   tableView_heightForRowAtIndexPath_: function(tableView, indexPath) {
       return 88;
   },
               
   tableView_didSelectRowAtIndexPath_: function(tableView, indexPath) {
       var alertView = JOC.class('UIAlertView').alloc()
       .initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles_
       ('Alert',
        'cell has been clicked',
        self,
        'OK',
        null);
        
       alertView.show();
   },
});

实例二

使用CoreGraphics画一只简单的时钟。

;JOC.interface('WatchView : UIView', {
    timer:['id','strong'],
    time:['id','strong'],
    back:['id','strong'],
 },
 {
    init : function() {
        var inited = self.JOSUPER_init();
        if (inited) {
            JOC.storeObject(self, inited);
            self.setTime_(NSMutableDictionary.dictionary());

            var timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(1, self, JOC.selector('timeTick'), null, true);
            timer.fire();
            self.JOSetIvar('_timer', timer);
        }
        return self;
    },
    invalidate: ['v@:', function() {
        self.timer().invalidate();
    }],
    timeTick : ['v@:', function() {
        var calendar = JOC.class('NSCalendar').currentCalendar();
        var unitFlags = JOG.NSCalendarUnitYear | JOG.NSCalendarUnitMonth | JOG.NSCalendarUnitDay | JOG.NSCalendarUnitHour | JOG.NSCalendarUnitMinute | JOG.NSCalendarUnitSecond;
                        
        var comps = calendar.components_fromDate_(unitFlags, NSDate.date());
        var hour = comps.hour();

        self.time().setValue_forKey_(parseInt(hour/12) ? hour - 12 : hour, 'hour');
        self.time().setValue_forKey_(comps.minute(), 'min');
        self.time().setValue_forKey_(comps.second(), 'sec');

        self.setNeedsDisplay();
     }],
    drawRect_ : function(rect) {
        self.setBackgroundColor_(UIColor.blackColor());
        var back = self.back();
        var r = JOC.structToObject(rect);
        var centerX = r.width/2.0;
        var centerY = r.height/2.0;

        if (!back) {
            JC.UIGraphicsBeginImageContextWithOptions(JC.CGSizeMake(r.width, r.height), false, UIScreen.mainScreen().scale());
            var context = JC.UIGraphicsGetCurrentContext();
            JC.CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor());
            JC.CGContextSetLineWidth(context, 2);
            JC.CGContextMoveToPoint(context, centerX, centerY - 0.5);
            JC.CGContextAddLineToPoint(context, centerX, centerY + 0.5)
            JC.CGContextSetLineCap(context, 1);
            JC.CGContextStrokePath(context);
            self.drawWithContext_angle_rotateCenterX_Y_(context, JOG.M_PI_2, centerX, centerY);
            back = JC.UIGraphicsGetImageFromCurrentImageContext();
            self.setBack_(back);
            JC.UIGraphicsEndImageContext();
        }
        back = self.back();
        if (back) {
            back.drawInRect_(JC.CGRectMake(0,0,r.width, r.height));
        }
        var context = JC.UIGraphicsGetCurrentContext();
        self.drawIndicatorWithContext_angle_rotateCenterX_Y_(context, JOG.M_PI_2, centerX, centerY);
    }, 
    drawIndicatorWithContext_angle_rotateCenterX_Y_ : ['v@:*ddd',function(context, angle, x, y) {
        var time = self.JOGetIvar('_time');
        var sec = time.objectForKey_('sec');
        var min = time.objectForKey_('min');
        var hour = time.objectForKey_('hour');
        JC.CGContextSaveGState(context);
        self.rotateWithContext_angle_fromCenterX_Y_(context, angle, x, y);

        
        JC.CGContextSaveGState(context);
        JC.CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor());
        self.rotateWithContext_angle_fromCenterX_Y_(context, 2 * JOG.M_PI * sec / 60, x, y);
        JC.CGContextSetLineWidth(context, 0.5);
        JC.CGContextMoveToPoint(context, x / 3.0, y);
        JC.CGContextAddLineToPoint(context, x, y - 1);
        JC.CGContextAddLineToPoint(context, x + x / 8.0, y);
        JC.CGContextAddLineToPoint(context, x, y + 1);
        JC.CGContextAddLineToPoint(context, x / 3.0, y);
        JC.CGContextStrokePath(context);
        JC.CGContextRestoreGState(context);


        JC.CGContextSaveGState(context);
        JC.CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor());
        self.rotateWithContext_angle_fromCenterX_Y_(context, 2 * JOG.M_PI * min / 60 + 2 * JOG.M_PI * sec / 60.0 / 60.0, x, y);
        JC.CGContextSetLineWidth(context, 0.5);
        JC.CGContextMoveToPoint(context, x / 2.0, y);
        JC.CGContextAddLineToPoint(context, x, y - 3);
        JC.CGContextAddLineToPoint(context, x + x / 10.0, y);
        JC.CGContextAddLineToPoint(context, x, y + 3);
        JC.CGContextAddLineToPoint(context, x / 2.0, y);
        JC.CGContextStrokePath(context);
        JC.CGContextRestoreGState(context);

        JC.CGContextSaveGState(context);
        JC.CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor());
        self.rotateWithContext_angle_fromCenterX_Y_(context, 2 * JOG.M_PI * hour / 12 + 2 * JOG.M_PI * min / 60.0 / 12.0, x, y);
        JC.CGContextSetLineWidth(context, 0.5);
        JC.CGContextMoveToPoint(context, x / 1.5, y);
        JC.CGContextAddLineToPoint(context, x, y - 5);
        JC.CGContextAddLineToPoint(context, x + x / 15.0, y);
        JC.CGContextAddLineToPoint(context, x, y + 5);
        JC.CGContextAddLineToPoint(context, x / 1.5, y);
        JC.CGContextStrokePath(context);
        JC.CGContextRestoreGState(context);

        JC.CGContextRestoreGState(context);

    }],

    drawWithContext_angle_rotateCenterX_Y_ : ['v@:*ddd',function(context, angle, centerX, centerY) {
        JC.CGContextSaveGState(context);
        self.rotateWithContext_angle_fromCenterX_Y_(context, angle, centerX, centerY);
        JC.CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor());
        var font = UIFont.fontWithName_size_('Helvetica', centerX/6);
        var stringAttrs = NSDictionary.dictionaryWithObjects_forKeys_(JOC.array([font, UIColor.whiteColor()]) , JOC.array([JOG.NSFontAttributeName, JOG.NSForegroundColorAttributeName]));

        for (var i = 0; i < 60; ++i) {
            var width = 0;
            var x = 0;
            var len = 0;
            var attrStr = null;
            if (i % 15 == 0) {
                width = centerX/30;
                x = 0;
                len = centerX/10;
                var str = NSString.JOVAR_stringWithFormat_('@@:@q','%d', (parseInt(i/5) != 0 ? parseInt(i/5) : 12));
                attrStr = NSAttributedString.alloc().initWithString_attributes_(str, stringAttrs);
            } else if (i % 5 == 0) {
                width = centerX/50;
                x = centerX/30;
                len = centerX/10;
                attrStr = NSAttributedString.alloc().initWithString_attributes_(NSString.JOVAR_stringWithFormat_('@@:@q','%d', parseInt(i/5)), stringAttrs);
            } else {
                width = centerX/100;
                x = centerX/20;
                len = centerX/10;
            }
            JC.CGContextSetLineWidth(context, width);
            JC.CGContextMoveToPoint(context, x, centerY);
            JC.CGContextAddLineToPoint(context, len, centerY);
            JC.CGContextStrokePath(context);

            if (attrStr) {
                attrStr.drawAtPoint_(JC.CGPointMake(centerX/5, centerY - centerX/15));
            }
            self.rotateWithContext_angle_fromCenterX_Y_(context, 2 * JOG.M_PI / 60.0 , centerX, centerY);
        }
        JC.CGContextRestoreGState(context);
    }],
  
    rotateWithContext_angle_fromCenterX_Y_ : ['v@:*ddd',function(context, angle, x, y) {
        JC.CGContextTranslateCTM(context, x, y);
        JC.CGContextRotateCTM(context, angle);
        JC.CGContextTranslateCTM(context, -x, -y);
    }]
 });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271