普通视图

发现新文章,点击刷新页面。
昨天以前掘金专栏-公众号iOS逆向

MyBatis-plus小课堂:@Select的参数传递

我正在参加「掘金·启航计划」

引言

  • @Select的参数传递
  • wrapper自定义sql: 使用条件构造器作为参数

I 预备知识

1.1 JDBC

Java Database Connectivity):一种用于执行 SQL 语句的 Java API,它由一组用 Java 编程语言编写的类和接口组成,JDBC 可做三件事:

  • 与数据库建立连接,
  • 发送 SQL 语句,
  • 处理结果。

MyBatis和JDBC最显著的区别是SQL语句配置化,通过xml文件定义SQL语句。

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

1.2 MyBatis的xml配置文件可用自己定义的数据类型

@Select(“select * from Type where id = #{id, jdbcType=BIGINT} and code= #{code, jdbcType=VARCHAR}”)  
Type selectTypeById(@Param(“id”) Long id, @Param(“code”) String code);

Associated JDBC type can be specified by two means:

  • Adding a jdbcType attribute to the typeHandler element (for example: jdbcType=“VARCHAR”).
 <select id='getMeetingnoByCompanyid' parameterType="java.lang.Integer"
        resultType="java.lang.String">
        select a.meetingno
        from xxx a
        where a.companyid = #{companyid, jdbcType=BIGINT}
</select>



  • Adding a @MappedJdbcTypes annotation to your TypeHandler class specifying the list of JDBC types to associate it with. This annotation will be ignored if the jdbcType attribute as also been specified.

1.3 MyBatis JdbcType 与Oracle、MySql数据类型对应关系

Mybatis JdbcType Oracle MySql
JdbcType ARRAY
JdbcType BIGINT BIGINT
JdbcType BINARY
JdbcType BIT BIT
JdbcType BLOB BLOB TEXT
JdbcType BOOLEAN
JdbcType CHAR CHAR CHAR
JdbcType CLOB CLOB CLOB–>修改为TEXT
JdbcType CURSOR
JdbcType DATE DATE DATE
JdbcType DECIMAL DECIMAL DECIMAL
JdbcType DOUBLE NUMBER DOUBLE
JdbcType FLOAT FLOAT FLOAT
JdbcType INTEGER INTEGER INTEGER
JdbcType LONGVARBINARY
JdbcType LONGVARCHAR LONG VARCHAR
JdbcType NCHAR NCHAR
JdbcType NCLOB NCLOB
JdbcType NULL
JdbcType NUMERIC NUMERIC/NUMBER NUMERIC/
JdbcType NVARCHAR
JdbcType OTHER
JdbcType REAL REAL REAL
JdbcType SMALLINT SMALLINT SMALLINT
JdbcType STRUCT
JdbcType TIME TIME
JdbcType TIMESTAMP TIMESTAMP TIMESTAMP/DATETIME
JdbcType TINYINT TINYINT
JdbcType UNDEFINED
JdbcType VARBINARY
JdbcType VARCHAR VARCHAR VARCHAR

1.4 Mybatis JdbcType

www.mybatis.org/mybatis-3/a…

public enum JdbcType {
    ARRAY(2003),
    BIT(-7),
    TINYINT(-6),
    SMALLINT(5),
    INTEGER(4),
    BIGINT(-5),
    FLOAT(6),
    REAL(7),
    DOUBLE(8),
    NUMERIC(2),
    DECIMAL(3),
    CHAR(1),
    VARCHAR(12),
    LONGVARCHAR(-1),
    DATE(91),
    TIME(92),
    TIMESTAMP(93),
    BINARY(-2),
    VARBINARY(-3),
    LONGVARBINARY(-4),
    NULL(0),
    OTHER(1111),
    BLOB(2004),
    CLOB(2005),
    BOOLEAN(16),
    CURSOR(-10),
    UNDEFINED(-2147482648),
    NVARCHAR(-9),
    NCHAR(-15),
    NCLOB(2011),
    STRUCT(2002),
    JAVA_OBJECT(2000),
    DISTINCT(2001),
    REF(2006),
    DATALINK(70),
    ROWID(-8),
    LONGNVARCHAR(-16),
    SQLXML(2009),
    DATETIMEOFFSET(-155),
    TIME_WITH_TIMEZONE(2013),
    TIMESTAMP_WITH_TIMEZONE(2014);

    public final int typeCode;
    private static final Map<Integer, JdbcType> CODE_MAP = new ConcurrentHashMap(100, 1.0F);

    private JdbcType(int code) {
        this.typeCode = code;
    }

    public static JdbcType valueOf(int code) {
        return (JdbcType)CODE_MAP.get(code);
    }

    static {
        JdbcType[] var0 = values();
        int var1 = var0.length;

        for(int var2 = 0; var2 < var1; ++var2) {
            JdbcType type = var0[var2];
            CODE_MAP.put(type.typeCode, type);
        }

    }
}

II @Select的参数传递

2.1 普通类型传递

案例1

@Select(“select * from Type where id = #{id, jdbcType=BIGINT} and code= #{code, jdbcType=VARCHAR}”)  
Type selectTypeById(@Param(“id”) Long id, @Param(“code”) String code);

案例2

    @Select("select an.* from sys_announcement an  ${ew.customSqlSegment} and an.id not in (select  a.id from sys_announcement a  inner join sys_announcement_read r on r.announcement_id=a.id where r.user_id = #{user_id,jdbcType=BIGINT})  order by an.create_time desc")
    List<SysAnnouncement> listUnRead(@Param(Constants.WRAPPER) LambdaQueryWrapper<SysAnnouncement> lambda,@Param("user_id") Long userId);

使用

    List<SysAnnouncement> listUnRead(@Param(Constants.WRAPPER) LambdaQueryWrapper<SysAnnouncement> lambda,@Param("user_id") Long userId);

2.2 使用条件构造器作为参数

  1. mapper.java/Service.java定义接口方法
  2. 添加 @Param(Constants.WRAPPER)形参和${ew.customSqlSegment}值参

${ew.customSqlSegment}值参 以where关键字开头,@Select语句如果有其他查询条件,必须放在${ew.customSqlSegment}之后。

    @Select("select  a.* from sys_announcement a  inner join t_sys_announcement_read r on r.announcement_id=a.id  ${ew.customSqlSegment} order by a.create_time desc")
    List<SysAnnouncement> listRead(@Param(Constants.WRAPPER) LambdaQueryWrapper<SysAnnouncement> lambda);

使用

        LambdaQueryWrapper<SysAnnouncement> lambda = new LambdaQueryWrapper<>();
        lambda.apply(input.getSendChannel() != null, "an.send_channel like {0}", "%"+input.getSendChannel()+"%");
        lambda.apply(input.getStartTime() != null, "an.create_time > {0}", input.getStartTime());
        lambda.apply(input.getEndTime() != null, "an.create_time > {0}", input.getEndTime());
        lambda.apply(input.getState() != null, "an.state = {0}", input.getState());
                list = tSysAnnouncementService.listUnRead(lambda, LoginHelper.getUserId());

2.3 案例

mybatis-plus小课堂:多表查询【案例篇】(apply 拼接 in SQL,来查询从表某个范围内的数据)kunnan.blog.csdn.net/article/det…

数据库小技能:mysql的DB规范

我正在参加「掘金·启航计划」

I DB 规范

MySQL 支持的类型大致可以分为:数值日期/时间字符串(字符)类型。

1.1 字段名类型

字段名 类型 备注
id bigint 主键雪花ID
create_time datetime 创建时间
create_id bigint 创建用户id
update_time datetime 修改时间
update_id bigint 修改用户id
数量类型 int
金额类型 decimal(10,2)数据库存元
枚举类型 varchar(n)大写
布尔类型 varchar(n)是1否0
字符串类型 varchar(n)
费率 decimal(10,4)费率可能2个小数位以上要给4个小数位
alter table t_voucher_coupon
add create_time datetime null comment '创建时间';
alter table t_voucher_coupon
add update_time datetime null comment '更新时间';
alter table t_voucher_coupon
add create_id bigint null comment '创建用户id';
alter table t_voucher_coupon
add update_id bigint null comment '修改用户id';
alter table t_equipment_position add create_name varchar(100) null comment '创建人';
alter table t_equipment_position add update_name varchar(100) null comment '操作人';

TMapper.xml

        <result column="create_name" property="createName" />
        <result column="update_name" property="updateName" />

entity

    @ApiModelProperty(value = "创建人")
    @TableField("create_name")
    private String createName;
    
    @ApiModelProperty(value = "操作人")
    @TableField("update_name")
    private String updateName;

代理商信息

alter table t_voucher_merchant
add facilitator_id bigint not null comment '代理商id(直属)';

alter table t_voucher_merchant
add facilitator_name varchar(500) null comment '代理商名称(直属)';

alter table t_voucher_merchant
add facilitator_top_id bigint null comment '一级代理商id';

alter table t_voucher_merchant
add facilitator_top_name varchar(500) null comment '一级代理商名称';


1.2 字段命名

同一个字段在各个表做到统一

XXX_no XXX编号
XXX_id xxxid
company_id 企业号
merchant_id 商户号
merchant_code 第三方商户号
merchant_name 商户名称
facilitator_id 代理商id(直属)
facilitator_name 代理商名称(直属)
facilitator_top_id 一级代理商id
facilitator_top_name 一级代理商名称
trade_no 交易流水号
trade_no3 三方交易流水号
out_trade_no 下游交易流水号
office_id 微信支付宝交易流水号
sref_no 参考号
remark 说明,备注
tags_type 对象类型(PT:平台,JG:机构,DLS:代理商,QY:企业,SH:商户)
terminal_code 交易终端号
state 状态
base_rate 基础费率
extra_rate 附加费率
base_fee 基础手续费
extra_fee 附加手续费
province 省份id
province_name 省份名称

1.3 表名模块前缀

trans_ 交易
report_trans_ 交易报表
mer_ 商户
fac_ 服务商
risk_ 风控
sys_ 系统
policy_ 政策
tms_ 终端

II 简单的excel 导入导出

用阿里的easyexcel

see also

数据库小技能:字段处理(BigDecimal 元转分,与0比较大小,bigDecimal格式化注解)

我正在参加「掘金·启航计划」

引言

数据库小技能:mysql的DB规范ianhttps://blog.csdn.net/z929118967/article/details/129803014

I 字段前处理:检测

@JsonFormat注解:主要是后台到前台的时间格式的转换

@DataFormat注解:主要是前后到后台的时间格式的转换

    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date blog_date;// 博客发布日期

1.1 格式校验

多个代理商编号时,请用英文逗号,分隔

try {
            facilitatorIds4input= Arrays.asList(input.getFacilitatorIds().split(","))
                    .stream().map(s -> Long.parseLong(s)).collect(Collectors.toList());
        } catch (Exception e) {
            throw CommonException.create(ServerResponse.createByError("代理商id,多个代理商编号时,请用英文逗号,分隔"));
        }

使用注解进行格式校验

public class ApiPubSigPayRequest extends ApiBaseRequest{
    @NotBlank(message = "订单日期不能为空")
    @Pattern(regexp = "(^[0-9]*$)",message = "订单日期格式有误")
    private String orderDate;//订单日期 yyyyMMddHHmmss

    @DecimalMin(value = "1" , message = "订单金额必须大于1分")
    private Long payAmt;//订单金额 分

    @DecimalMin(value = "1" , message = "订单金额必须大于1分")
    private Long totAmt;//总金额 分

    @NotBlank(message = "交易码不能为空")
    private String transCode;//交易码

    @NotBlank(message = "微信/支付宝小程序appId不能为空")
    private String subAppid;

    @NotBlank(message = "微信/支付宝用户id不能为空")
    private String subOpenid;

    private String subject;//订单标题

}

1.2 检测唯一性

如果唯一性的其中一个字段时可选的,统一保存""空串,便于比较。

        if(position.getEquipmentSn() ==null){// 统一保存""空串,便于比较
            position.setEquipmentSn("");
        }
        if (!(position.getMerchantId().equals(input.getMerchantId()) && position.getEquipmentSn().equals(input.getEquipmentSn()))) {
            // 检测唯一性
            if (getPosition(input.getMerchantId(), input.getEquipmentSn()) != null) {
                throw CommonException.create(ServerResponse.createByError("规则已经存在"));
            }
        }

1.3 日期范围查询

请求参数LocalDate

    @ApiModelProperty(value = "开始时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startTime;

    @ApiModelProperty(value = "结束时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endTime;

LocalDate 转LocalDateTime

        if (input.getStartTime() != null) {
            lambda.apply("create_time > {0}",input.getStartTime().atStartOfDay());
        }
        if (input.getEndTime() != null) {
            lambda.apply("create_time < {0}",input.getEndTime().plusDays(1).atStartOfDay());
        }

II 字段后处理

2.1 List转换成用逗号分割的字符串

            String exidsStr = exFacilitator.stream().map(i->i.getFacilitatorId().toString()).collect(Collectors.joining(","));

2.2 按照枚举分组数据

        List<RiskFacilitatorNetLimitRateDto> rates = new ArrayList<>();
        // 按照结算周期枚举分组
        List<ESettlementCycle> cycles = Arrays.asList(ESettlementCycle.class.getEnumConstants());
        cycles.forEach(item -> {
            RiskFacilitatorNetLimitRateDto dto = new RiskFacilitatorNetLimitRateDto();
            dto.setSettlementCycle(item);
            dto.setRateList(rateList.stream().filter(i -> i.getSettlementCycle() == item).collect(Collectors.toList()));
            rates.add(dto);
        });
        return rates;

III BigDecimal处理

3.1 BigDecimal与0比较大小

如果0表示不限制 ,推微信总上限。

        if(activity.getMaxAmountByDay().compareTo(BigDecimal.ZERO)==0){//BigDecimal与0比较大小 -1 小于0,1 大于0
            stockUseRule.setMaxAmountByDay(stockUseRule.getMaxAmount());
        }else{
            stockUseRule.setMaxAmountByDay(activity.getMaxAmountByDay().multiply(new BigDecimal(100)).longValue());
        }

3.2 BigDecimal 元转分

        stockUseRule.setMaxAmount((activity.getMaxAmount().multiply(new BigDecimal(100)).longValue()));//乘以100(单位:分)

3.3 自定义DecimalFormat注解

新增bigDecimal格式化注解CustomDecimalFormat,默认格式#.###,可用于费率字段。

blog.csdn.net/z929118967/…

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
//Jackson的注解继承注解
 @JacksonAnnotationsInside
// 为该注解指定一个自定义的json序列化解析器
@JsonSerialize(using = DecimalFormatSerialize.class)
public @interface CustomDecimalFormat {
    //0 整数或小数部分少位数补0
    //# 开头的0和末尾的0不显示,只要有可能就把数字拉上这个位置。
//    % 乘100然后加%
//     .小数分隔符
//     ,:整数分隔符

    String value() default "#.###";
    boolean showDecimalSeparator() default true;
    boolean zeroDisplay() default false;
}

iOS小技能:UITableView的适配 (iOS10/iOS14/iOS16.0)

本文正在参加「金石计划」

引言

如果按照开发规范写代码,不会存在关于UITableView的适配问题。

  • 如果按照规范使用UITableViewHeaderFooterView,就不会存在iOS16横竖屏切换场景的页脚问题。
  • 如果按照规范添加UITableViewCell的子试图到UITableViewCellContentView,就不会存在iOS14被遮挡的问题。
  • 如果将tableView:viewForFooterInSection: 和tableView:heightForFooterInSection:方法都注释掉,就不会存在iOS10即使heightForFooterInSection高度返回0 也会显示视图的问题。

I iOS16.0 横竖屏切换适配

调用完转屏方法以后,view需要重新更新frame

1.1 获取当前屏幕横竖屏状态

  • (UIInterfaceOrientation)interfaceOrientation {// 获取当前屏幕横竖屏状态 if (@available(iOS 13.0, *)) { return [UIApplication sharedApplication].windows.firstObject.windowScene.interfaceOrientation; } return [[UIApplication sharedApplication] statusBarOrientation]; }

1.2 iOS16.0调完转屏方法后,需要重新更新view的frame

问题:从电子签名界面回来之后,页脚的frame不正确

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
        UIButton *nextBtn = [ControlManager getButtonWithFrame:CGRectMake(KWratio(30), KWratio(25), kWidth - KWratio(60), KWratio(45)) fontSize:displayFontSize(15.0f) title:@"提交" titleColor:[UIColor whiteColor] image:[UIImage new] backgroundColor:BASE_RED_COLOR cornerRadius:KWratio(5)];
return xxx;
}

原因:iOS16.0调用完转屏方法以后,如果不是使用自动布局,view需要重新更新frame。

解决方法:自定义页脚和页眉(UITableViewHeaderFooterView代替 titleForHeaderInSection)

1.3 自定义页脚

UITableViewHeaderFooterView代替 titleForHeaderInSection

blog.csdn.net/z929118967/…

  1. 注册FooterView
        [_tableView registerClass:[CRMNextBtnHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"CRMNextBtnHeaderFooterView"];
  1. 返回FooterView
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    CRMNextBtnHeaderFooterView *footerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"CRMNextBtnHeaderFooterView"];

//    footerView.models = self.viewModel.passwordRuleDescModel;

    return footerView;
}
  1. 实现FooterView

@interface CRMNextBtnHeaderFooterView : UITableViewHeaderFooterView

@property (nonatomic , weak) UIButton *nextBtn;

@property (nonatomic , weak) UIButton *cancelBtn;


@end

II 解决UITableViewCell兼容问题(iOS14适配)

kunnan.blog.csdn.net/article/det…

2.1 问题分析

iOS14 UITableViewCell的子试图不能点击或者滑动等手势响应问题,发现有问题的cell基本都是直接添加到Cell的,通过Xcode自带的DebugViewHierarchy视图分析发现问题的原因是:被系统自带的UITableViewCellContentView遮挡在底部了

2.2 解决方案

需要改规范的做法

cell.contentView.addSubView(tempView1)

全局修改适配

//
//  UITableViewCell+CRMaddSubView.m
//  Housekeeper
//
//  Created by mac on 2020/9/18.
//  Copyright © 2020 QCT. All rights reserved.
//

#import "UITableViewCell+CRMaddSubView.h"

@implementation UITableViewCell (CRMaddSubView)
+ (void)load {
    // Swizzle addSubView
    [UITableViewCell sensorsdata_swizzleMethod:@selector(addSubview:) withMethod:@selector(kunnan_addSubview:)];
    
}

- (void)kunnan_addSubview:(UIView *)view {

    
    
    if  ([view isKindOfClass:NSClassFromString(@"UITableViewCellContentView")]) {//允许 addSubView UITableViewCellContentView 
        
        [self kunnan_addSubview:view];//实现方法,因为已经进行了 swizzle,相当于调用原来的方法
        

        
    } else {//
        
        [self.contentView addSubview:view];
        
    }



}



@end

III iOS10 系统关于UITableView的适配问题

因为目前基本找不到iOS10的真机了,要测试iOS10 可以下载模拟器。

  1. tableView numberOfRowsInSection:QCTReceiptsubFilterViewSection4KeyTypeTitle] 的使用执行顺序在iOS10的很特殊,不能在在方法中 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section调用,否则会造成死循环。
  2. heightForFooterInSection 在iOS10 即使高度返回0 也会显示视图

3.1 代理方法的执行顺序

tableView:numberOfRowsInSection 不能在方法中 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ 调用,否则会造成死循环。

3.2 尾部视图的展示

在iOS10 中不显示viewForFooterInSection的正确做法是,将tableView:viewForFooterInSection: 和tableView:heightForFooterInSection:方法都注释掉


//- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
//    
//    return 0;
//    
//    return kAdjustRatio(92+10);
//}
//
//- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
//     *footerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@""];
//    
//    footerView.type =  self.type;
//    footerView.models = self.viewModel.datas[section];
//    return footerView;
//}

iOS小技能:短信验证码的Checklist、格式校验、获取验证码处理流程(限制60s)

本文正在参加「金石计划」

前言

短信验证码处理的实际应用:注册登陆界面获取验证码功能

  1. 获取验证码(限制60s):获取成功之后验证码输入框得到焦点,并开始计时器
  2. 格式校验
  3. 短信验证码的Checklist

I、实际应用的例子: 获取验证码(限制60s)

1.1 点击获取验证码处理

请求接口获取验证码,获取成功之后,开始到60S倒计时

  • 监听点击事件,并处理获取成功之后的倒计时
        UITapGestureRecognizer *cutTap = [[UITapGestureRecognizer alloc] init];
        [[cutTap rac_gestureSignal] subscribeNext:^(id x) {
            
            
            NSLog(@"获取验证码");
            
            
            if(weakSelf.model.rightBtnblock){
                
                // 参数为block
                
                
                weakSelf.model.rightBtnblock(
                                              
                                              
                                              ^(id  _Nonnull sender) {
                                
                //                                  []
                                                  [self.textF becomeFirstResponder];
                                                  [self startCountdown];//test code

                                                  
                            }// 参数
                                           );
                
            }
            
            
            
            
            
        }];
        [tmpView addGestureRecognizer:cutTap];
        

  • 请求短信验证码:SendValidateCode

 
/** 本方法的参数类型时block,是获取完短信验证码的回调
 ,回调主要是执行60S倒计时
 
 */
- (void)SendValidateCode:(void (^)(id sender))tmp {

        [QCTNetworkHelper Post:post parameters:params success:^(id  _Nonnull responseObj) {
            
            
            if(tmp){// 开始计时器
                tmp(nil);
            }
//
            
            
            
        } failure:nil bizFailure:nil isShowLoadingDataGif:YES];
        

1.2、计时方法处理

  • 去掉输入框的内容,短信验证码获取成功调用这个方法


#pragma mark - 计时方法, 去掉输入框的内容,短信验证码获取成功调用这个方法
//开始倒计时
- (void)startCountdown {
//
//    //1\ 清空文本输入框
    [self updateText:nil]; //
//
    _textF.userInteractionEnabled = YES;
    
    self.countdownTime = 60;
    
    self.rightBtn.enabled = NO;
    
    
    [self.rightBtn setTitle:[NSString stringWithFormat:@"%dS",self.countdownTime] forState:UIControlStateDisabled];
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}


//
//结束倒计时
- (void)stopCountdown {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    [self.rightBtn setTitle:@"重新发送" forState:UIControlStateNormal];
//    //    self.renewBtn.userInteractionEnabled = YES;
    self.rightBtn.enabled = YES;
    
    self.rightBtn.layer.borderColor = RGB(255,83,85).CGColor;
    [self.rightBtn setTitleColor:RGB(255,83,85) forState:UIControlStateNormal];
    
}



//
- (void)countdownAction:(NSTimer *)timer {
    self.countdownTime --;
    if (self.countdownTime < 0) {
        self.countdownTime = 0;
        [self stopCountdown];
    } else {
        [self.rightBtn setTitle:[NSString stringWithFormat:@"%dS",self.countdownTime] forState:UIControlStateDisabled];
    }
}
//
//
//
//
//
- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(countdownAction:) userInfo:nil repeats:YES];
    }
    return _timer;
}


II 格式正则校验

2.1 短信验证码格式正则校验

+ (BOOL)isSMSshouldChangeCharactersInRange:(NSString*)str{
    //匹配以0开头的数字
    
    //^\d{6}$"
    
//    NSPredicate * predicate0 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^[0][0-9]+$"];
    
    
    //
    NSPredicate * predicate1 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^[0-9]{0,6}$"];
    
    
    
    
    return  [predicate1 evaluateWithObject:str] ? YES : NO;
    
    
}


2.2 金额校验格式正则校验

需求背景:商户进件新增、编辑、变更;费率设置支持到小数点后3位,如微信支付宝费率0.385;d0费率0.015

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
    NSInteger len = range.length > 0?([textField.text length] - range.length): ([textField.text length] + [string length]);
    
    if(len > 20){// 限制20位
        return false;
    }
    
    NSMutableString * futureString = [NSMutableString stringWithString:textField.text];
    if(range.length > 0){//允许删除
        [futureString replaceCharactersInRange:range withString:string];
    }else if(range.length<=0){
        [futureString  insertString:string atIndex:range.location];
    }
    if(futureString.length<1){//允许删除全部
        return YES;
    }
    if(jjkFdTextF == textField){// 支持2位小数
        return [QCT_Common isAmoutshouldChangeCharactersInRange:futureString.copy ];
    }
    //支持三位小数
    return [QCT_Common isAmoutshouldChangeCharactersInRange3:futureString.copy];
    
}

支持三位小数

/**
 前端商户费率填写支持三位小数
 */
+ (BOOL)isAmoutshouldChangeCharactersInRange3:(NSString*)str{
    //匹配以0开头的数字
    NSPredicate * predicate0 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^[0][0-9]+$"];
    //匹配两位小数、整数
    NSPredicate * predicate1 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^(([1-9]{1}[0-9]*|[0])\.?[0-9]{0,3})$"];
    return ![predicate0 evaluateWithObject:str] && [predicate1 evaluateWithObject:str] ? YES : NO;
    
}

III、短信验证码的Checklist

  • 一次一用
  • 发送频率控制(建议60s获取一次)
  • 验证码有效期(建议60s内有效,发短信时进行友好提示)
  • 杂度(短信验证码建议6位数字)
  • 安全提示:是否是个人自己操作等风险提示信息
  • 在前端校验(客户端的校验只能作为辅助手段,很容易被绕过),必须使用服务端代码对输入数据进行最终校验
  • 短信验证码需要限制频率使用

例如:每天一个手机号码只允许发送5次,防止被黑客恶意消耗短信

  • 不同场景的短信验证码不可通用
  • 单个短信验证码限制有效验证次数
  • 验证码需要对应手机号不可通用

iOS小技能:【设备日志查看工具】syslog、deviceconsole和socat

本文正在参加「金石计划」

前言

本文介绍iOS设备日志查看工具syslog、deviceconsole和socat,如果上述工具都不满意,你也可以使用Mac系统自带的console控制台进行查看。

>

I syslog

1.1 安装syslog

在cydia搜索syslogd to /var/log/syslog安装即可

1.2 syslog用法

syslog是把系统日志写入到/var/log/syslog文件里,用法很简单,执行tail -f /var/log/syslog就能看到了

如果需要过滤某一应用的日志,只需加上grep即可,比如过滤微信

tail -f /var/log/syslog |grep WeChat

II socat

2.1 安装

  • 在iOS设备安装

使用 APT 0.6 Transitional 安装socat 几乎所有流行的黑客工具都可以在 BigBoss Recommendation tools这个包中找到 ( APT 0.6 Transitional, Git, GNU Debugger, less, make, unzip, wget 和 SQLite 3.x)

apt-get install socat

如果找不到安装包的时候,运行一下 apt-get update, 获得最新的包列表.

2.2 连接到系统日志的sock文件

socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock

  • 进入到命令行交互界面,这时可以输入help查看帮助
iPhone:~ root# socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock

========================
ASL is here to serve you
> 

2.3 日志的查看

输入watch查看,输入stop停止

2.4 清除日志文件数据

cat /dev/null >/var/log/syslog

III deviceconsole

自从iOS8之后,我就习惯使用Mac系统自带的console ,后来发现有些同事的Mac中console 版本低,没有device 选项;于是乎,就推荐他们使用deviceconsole

  • deviceconsole --help
➜  bin git:(master) ✗ deviceconsole --help
Usage: deviceconsole [options]
Options:
 -d      Include connect/disconnect messages in standard out
 -u <udid>    Show only logs from a specific device
 -p <process name>  Show only logs from a specific process

Control-C to disconnect
Mail bug reports and suggestions to <ryan.petrich@medialets.com>
  • knlog -help

    Usage: knlog [options]
    Options:
    -i | --case-insensitive     Make filters case-insensitive
    -f | --filter <string>      Filter include by single word occurrences (case-sensitive)
    -x | --exclude <string>     Filter exclude by single word occurrences (case-sensitive)
    -p | --process <string>     Filter by process name (case-sensitive)
    -u | --udid <udid>          Show only logs from a specific device
    -s | --simulator <version>  Show logs from iOS Simulator
         --debug                Include connect/disconnect messages in standard out
         --use-separators       Skip a line between each line
         --force-color          Force colored text
         --message-only          Display only level and message
    Control-C to disconnect
    
  • 编译之后的可执行文件 knlog

IV shell 脚本完成对日志文件的提取之sed 学习

sed 是一种在线编辑器,它一次处理一行内容。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

文件内容并没有改变,除非你使用重定向存储输出。

4.1 ## sed的参数

    sed [-nefr] [动作]
选项与参数:
-n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e :直接在命令列模式上进行 sed 的动作编辑;
-f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
-r :sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法)
-i :直接修改读取的文件内容,而不是输出到终端。

动作说明: [n1[,n2]]function
n1, n2 :不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』

function:
a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 
————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z929118967/article/details/49489527

4.2 数据的搜寻并替换

`sed 's/要被取代的字串/新的字串/g'`

    /etc>ifconfig -a | grep inet 
        inet 172.16.49.224 netmask 0xffffff00 broadcast 172.16.49.255
        inet 192.168.10.4 netmask 0xffffff00 broadcast 192.168.10.255
        inet 127.0.0.1 netmask 0xff000000 broadcast 127.255.255.255
        inet6 ::1%1/0
————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z929118967/article/details/49489527

see also

更多服务和咨询请关注#公号:iOS逆向

iOS小技能:核心动画(CoreAnimation)

本文正在参加「金石计划」

引言

预备知识1: RootLayer

在创建一个UIView对象时,UIView内部会自动创建一个图层(即CALayer对象);通过UIView对象的layer属性可以访问这个层,通常称这个层为RootLayer。 iOS小技能:CALayerblog.csdn.net/z929118967/…

预备知识2. 关于CALayer(可移植性) QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用,但是UIKit只能在iOS中使用。所以: 为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef

  • CALayer是定义在QuartzCore框架中的(CoreAnimation)

  • CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的

  • UIColor、UIImage是定义在UIKit框架中的

本文核心内容:

  • CALayer的position、anchorPoint属性的作用
  • 核心动画基本概念
  • 基本动画
  • 关键帧动画
  • 动画组
  • 转场动画

I 隐式动画

所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画。 隐式动画:当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果;而这些属性称为可动画属性(Animatable Properties)。 几个常见的Animatable Properties:

  1. bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
  2. backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
  3. position:用于设置CALayer的位置。修改这个属性会产生平移动画效果

关闭隐式动画(使用CATransation关闭隐式动画): 可以通过动画事务(CATransaction)关闭默认的隐式动画效果


[CATransaction begin];
[CATransaction setDisableActions:YES];
self.myview.layer.position = CGPointMake(10, 10);
[CATransaction commit];

II 核心动画

核心动画的执行过程都是在后台操作的,不会阻塞主进程;核心动画直接作用于CALayer,而非UIView

2.1 是所有动画对象的父类

负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类

CAAnimation的继承结构

2.2 属性

duration:动画的持续时间 repeatCount:重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT repeatDuration:重复时间 nremovedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards fillMode:决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之后 beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间 timingFunction:速度控制函数,控制动画运行的节奏 delegate:动画代理

(粗体代表来自CAMediaTiming协议的属性)

2.3 动画填充模式(fillMode )

填充模式的枚举值

要想fillMode有效,最好设置removedOnCompletion = NO



CA_EXTERN NSString * const kCAFillModeForwards//当动画结束后,layer会一直保持着动画最后的状态
CA_EXTERN NSString * const kCAFillModeBackwards//在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。
CA_EXTERN NSString * const kCAFillModeBoth//上面两个的合成,动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
CA_EXTERN NSString * const kCAFillModeRemoved //这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态

速度控制函数(CAMediaTimingFunction)


1.kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
2.kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
3.kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
4.kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。

动画的代理方法

@interface NSObject (CAAnimationDelegate)
/* Called when the animation begins its active duration. */
- (void)animationDidStart:(CAAnimation *)anim;
/* Called when the animation either completes its active duration or
 
 * is removed from the object it is attached to (i.e. the layer). 'flag'
 
 * is true if the animation reached the end of its active duration
 
 * without being removed. */
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end

III 开发步骤

  1. 获取到CALayer(CoreAnimation是直接作用在CALayer上的,并非UIView。)
  2. 初始化CAAnimation对象,并设置一些动画相关属性
  3. 通过调用CALayer的addAnimation: forKey: 方法来添加CAAnimation到CALayer中,这样就能开始执行动画了。
  4. 通过调用CALayer的removeAnimationforKey: 方法来停止CALayer的动画。

CALayer上动画的暂停和恢复



#pragma mark 暂停CALayer的动画
-(void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime =
[layer convertTime:CACurrentMediaTime() fromLayer:nil];
//让CALayer的时间停止走动
  layer.speed = 0.0;
//让CALayer的时间停留在pausedTime这个时刻
layer.timeOffset = pausedTime;
 
}
 
 
 
 
 
 
 
#pragma mark 恢复CALayer的动画
 
 
-(void)resumeLayer:(CALayer*)layer{
CFTimeInterval pausedTime = layer.timeOffset;
//1. 让CALayer的时间继续行走
  layer.speed = 1.0;
//2. 取消上次记录的停留时刻
  layer.timeOffset = 0.0;
//3. 取消上次设置的时间
  layer.beginTime = 0.0;    
//4. 计算暂停的时间(这里也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
//5. 设置相对于父坐标系的开始时间(往后退timeSincePause)
  layer.beginTime = timeSincePause;
 
 

IV CAAnimation的子类

4.1 CAPropertyAnimation

是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的两个子类:

  1. CABasicAnimation
  2. CAKeyframeAnimation

keyPath属性说明:通过指定CALayer的一个属性名称为keyPath(NSString类型),并且对CALayer的这个属性的值进行修改,达到相应的动画效果。 比如,指定@“position”为keyPath,就修改CALayer的position属性的值,以达到平移的动画效果

4.2 CABasicAnimation(基本动画)


是CAPropertyAnimation的子类

属性说明:

  1. fromValue:keyPath相应属性的初始值
  2. toValue:keyPath相应属性的结束值

动画过程说明:

随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValuekeyPath内容是CALayer的可动画Animatable属性。

如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。

4.3 CAKeyFrameAnimation(关键帧动画)

也是CAPropertyAnimation的子类,与CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值。

CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation

属性说明:

  1. values:上述的NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
  2. path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略
  3. keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的

4.4 CAAnimationGroup(动画组)

动画组,是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行 1.属性说明:

nanimations:用来保存一组动画对象的NSArray
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间

4.5 CATransition(转场动画)


CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点

UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果

动画属性:

  1. type:动画过渡类型
  2. subtype:动画过渡方向
  3. startProgress:动画起点(在整体动画的百分比)
  4. endProgress:动画终点(在整体动画的百分比)

  1. 使用UIView动画函数实现转场动画——单视图

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 

duration:动画的持续时间

view:需要进行转场动画的视图

options:转场动画的类型

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

  1. 使用UIView动画函数实现转场动画——双视图

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview
 

duration:动画的持续时间

options:转场动画的类型

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

4.6 CADisplayLink (时钟机制)

CADisplayLink是一种以屏幕刷新频率触发的时钟机制,每秒钟执行大约60次左右 CADisplayLink是一个计时器,可以使绘图代码与视图的刷新频率保持同步,而NSTimer无法确保计时器实际被触发的准确时间。

使用方法:

  1. 定义CADisplayLink并制定触发调用方法
  2. 将显示链接link添加到主运行循环队列

通常保障一个View只拥有一个link

- (CADisplayLink *)link{
 
    if (nil == _link) {
 
        _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
        [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
 
    return _link;
 
}
- (void)startRotating{
 
    /** 核心动画的缺点是改变不了真是的属性*/
 
//    CABasicAnimation *animation = [CABasicAnimation animation];
 
//    //设置动画对象属性
 
//   
 
//    [self.roattionImageView.layer addAnimation:animation forKey:nil];
 
    [self.link setPaused:NO];
 
     
}
 
 
- (void) update{
 
    [self.roattionImageView setTransform:CGAffineTransformRotate(self.roattionImageView.transform, angle2Radian(45/60.0))];//60 次转45弧度,每次转45/60 弧度
 
    
}
 
- (void) stopRotating{
 
    [self.link setPaused:YES];
}
 

V iOS Core Animation&CALayer 的使用例子

kunnan.blog.csdn.net/article/det…

JavaScript小技能:变量

本文正在参加「金石计划」

引言

如果我们没有变量,我们就不得不写大量的代码去枚举和检查输入的名字,然后去显示它们,这样做显然是低效率和不可行的 。

一个变量,就是一个用于存放数值的容器。

变量不是数值本身,它们仅仅是一个用于存储数值的容器。你可以把变量想象成一个个用来装东西的纸箱子。

I 变量的声明

变量是存储值的容器,在 JavaScript 中声明一个新变量的方法是使用关键字letconstvar,let 和 const 关键字允许你创建块作用域的变量。JavaScript 中可以定义重名变量,如果后面定义的变量没有初始化则会按照前面定义的输出 。

如果声明了一个变量却没有对其赋值,那么这个变量的类型就是 undefined。

1.1 let

//let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
let a;
let name = 'Simon';

let x,y,z=3;//只有最后一个变量z 被赋值了3

//给多个变量赋值
//Longhand
let a, b, c;
a = 5;
b = 8;
c = 12;
//Shorthand 简写
let [a, b, c] = [5, 8, 12];

// myLetVariable 在这里 *不能* 被引用

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
  // myLetVariable 只能在这里引用
}

// myLetVariable 在这里 *不能* 被引用

1.2 const

const 声明一个不可变的常量,这个常量在定义域内总是可见的。

const Pi = 3.14; // 设置 Pi 的值

1.3 var

var 变量名称 使用 var 声明的变量在它所声明的整个函数都是可见的。

var a;
var name = "simon";
// myVarVariable 在这里 *能* 被引用

for (var myVarVariable = 0; myVarVariable < 5; myVarVariable++) {
  // myVarVariable 整个函数中都能被引用
}

// myVarVariable 在这里 *能* 被引用

//JavaScript 与其他语言的(如 Java)的重要区别是在 JavaScript 中语句块(blocks)是没有作用域的,只有函数有作用域。因此如果在一个复合语句中(如 if 控制结构中)使用 var 声明一个变量,那么它的作用域是整个函数(复合语句在函数中)。 

//但是从 ECMAScript Edition 6 开始将有所不同的, let 和 const 关键字允许你创建块作用域的变量。

II 变量类型

可以为变量设置不同的数据类型。

2.1 数据基本类型

  1. number (数字类型), 采用“遵循 IEEE 754 标准的双精度 64 位格式("double-precision 64-bit format IEEE 754 values")表示数字。在 JavaScript(除了BigInt)当中,并不存在整数/整型 (Integer)。可以使用内置函数 parseInt() 将字符串转换为整型,该函数的第二个可选参数表示字符串所表示数字的基(进制):
parseInt("123", 10); // 123
parseInt("010", 10); // 10
parseInt("11", 2); // 3,把一个二进制数字字符串转换成整数值
//parseFloat() 只应用于解析十进制数字
//一元运算符 + 也可以把数字字符串转换成数值:
+ "42";   // 42
+ "010";  // 10
+ "0x10"; // 16
//parseInt() 和 parseFloat() 函数会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符前所有数字字符组成的数字。
//但是运算符 "+"对字符串的转换方式与之不同, 只要字符串含有无法被解析成数字的字符,该字符串就将被转换成 NaN。
  1. boolean 布尔类型 ,分别是 true 和 false(两者都是关键字)。
//将变量转换成布尔类型
//1. false、0、空字符串("")、NaN、null 和 undefined 被转换为 false
//2. 除了以上情况,其他值被转换为 true。
//可以使用 Boolean() 函数进行显式转换:
Boolean(''); // false
Boolean(234); // true
//JavaScript 会在需要一个布尔变量时隐式完成这个转换操作,比如在 if 条件语句中。
  1. symbol (符号)(ES2015 新增)

ES2015 新增

从 Symbol() 返回的 symbol 值都是唯一的,能作为对象属性的标识符;

developer.mozilla.org/zh-CN/docs/…

2.2 Object (对象类型)

  1. Function (函数),特殊的对象,函数也可以被保存在变量中,并且像其他对象一样被传递。
  2. Array ( 数组)类型
  3. Date (日期)
   var d = new Date();

//1)    获得当前年份    d.getYear()
//2)    获得年份的全称    d.getFullYear()
//3)    获得月份    d.getMonth()
//4)    获得日期    d.getDate()
//5)    获得星期    d.getDay()
//6)    获得时间    d.getHours()
//7)    获得分钟    d.getMinutes()
//8)    获得秒钟    d.getSeconds()

  1. Math 内置对象 Math(数学对象),用以处理更多的高级数学函数和常数。
Math.sin(3.5);
var circumference = 2 * Math.PI * r;     
//1)       ceil()    向上取整
//2)       floor()   向下取整
//3)       round()   四舍五入

  1. RegExp(正则表达式),特殊的对象。
  2. string (字符串类型),JavaScript 中的字符串是一串Unicode 字符序列。它们是一串 UTF-16 编码单元的序列,每一个编码单元由一个 16 位二进制数表示。每一个 Unicode 字符由一个或两个编码单元来表示。
 var str = new String("abc");
//通过访问字符串的 length(编码单元的个数)属性,可以得到它的长度。
"hello".length; // 5 
//字符串也有 methods(方法)能让你操作字符串和获取字符串的信息。
"hello".charAt(0); // "h"  charAt(下标) 
"hello, world".replace("world", "mars"); // "hello, mars"
"hello".toUpperCase(); // "HELLO"
//          indexOf()
//          substring()
//          concat()
//          split()

2.3 特殊类型

  1. undefined (未定义)类型, 一个未被赋值的变量就是 undefined 类型,undefined 实际上是一个不允许修改的常量。
  2. null ( non-value)空类型 , 只有显示声明null才能使用
  3. NaN : (Not a Number 的缩写),如果给定的字符串不存在数值形式,函数会返回一个特殊的值 NaN。
parseInt("hello", 10); // NaN
"test"/123
//把 NaN 作为参数进行任何数学运算,结果也会是 NaN:
NaN + 5; //NaN
//使用内置函数 isNaN() 来判断一个变量是否为 NaN:
isNaN(NaN); // true
  1. 内置的 Error(错误)类型

特殊值:Infinity(正无穷)和 -Infinity(负无穷)

//使用内置函数 isFinite() 来判断一个变量是否是一个有穷数, 如果类型为Infinity, -Infinity 或 NaN 则返回 false:
isFinite(1/0); // false
isFinite(Infinity); // false
isFinite(-Infinity); // false
isFinite(NaN); // false

isFinite(0); // true


III 详解对象类型

JavaScript 中的一切(除了核心类型,core object)都是对象,JavaScript 中的对象,可以简单理解成“名称 - 值”对(而不是键值对)。 “名称”部分是一个 JavaScript 字符串,“值”部分可以是任何 JavaScript 的数据类型——包括对象。

3.1 Function

函数用来封装可复用的功能,函数通常包括参数,参数中保存着一些必要的数据。它们位于括号内部,多个参数之间用逗号分开。document.querySelector 和 alert 是浏览器内置的函数,随时可用。

let myVariable = document.querySelector('h1');
alert('hello!');

定义和声明函数

  function   函数名称(参数列表){
      
    函数体
     }
 //一个 JavaScript 函数可以包含 0 个或多个已命名的变量。
 var fun = new Function("a","b"," return a+b;");//return 语句返回一个值并结束函数
 //JavaScript 允许你创建匿名函数
 var fun = function (a,b){
      return a+b;
           }
 // 注意:Function 类型没有重载,并且 fun 和fun(1,2)结果完全不同;

匿名函数: 因为它没有名字,匿名函数还有另一种我们称之为箭头函数的写法,箭头函数使用() =>代替 function ()

//匿名函数
document.querySelector('html').onclick = function() {
    alert('别戳我,我怕疼。');
}

//箭头函数
document.querySelector('html').addEventListener('click', () => {
  alert('别戳我,我怕疼。');
});

//Longhand
function add(num1, num2) {
   return num1 + num2;
}
//Shorthand
const add = (num1, num2) => num1 + num2;

函数体中有一个名为 arguments 的内部对象,类似于数组的对象,包括了所有被传入的参数。

function avg() {//可以接收任意个数的参数
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
avg(2, 3, 4, 5); // 3.5

使用剩余参数...args 来替换 arguments 的使用,function avg(firstValue, ...args) 会把传入函数的第一个值存入 firstValue,其他的参数存入 args。

function avg(...args) {//所有被传入该函数的参数都被变量 args 所持有。
  var sum = 0;
  for (let value of args) {//将 for 循环替换为 for...of 循环来参数args
    sum += value;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5

JavaScript 允许你通过任意函数对象apply() 方法来传递给它一个数组作为参数列表。

函数也是对象

apply(thisArg, argsArray)
//1. thisArg在 func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
//2. 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或  undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。

//传给 apply() 的第二个参数是一个数组,它将被当作 avg() 的参数列表使用
avg.apply(null, [2, 3, 4, 5]); // 3.5

const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);

JavaScript 允许以递归方式调用函数,递归在处理树形结构(比如浏览器 DOM)时非常有用。

var charsInBody = (function countChars(elm) {
    if (elm.nodeType == 3) { // 文本节点
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
})(document.body);//命名立即调用的函数表达式(IIFE——Immediately Invoked Function Expression)

3.2 Object

对象是存储在单个分组中的相关功能的集合。

创建一个空对象

var obj = new Object();// js方式创建一个对象
var obj = {};//对象字面量(object literal)

JSON 格式创建一个对象

   var oo = {
        name:"hello",
        age:123,
        getName:function(){
       return oo.name;
            }
       
    }
   alert(oo.getName());

js方式创建一个对象

   var obj = new Object();
   obj.name = "hello";//对象的属性可以通过链式(chain)表示方法访问
// 括号表示法 (bracket notation)
   obj['name'] = 'Simon';
   obj.for = 'Simon'; // 语法错误,因为 for 是一个预留关键字。从 ECMAScript 5 开始,预留关键字可以作为对象的属性名
   obj["for"] = 'Simon'; // 工作正常
   obj.age = 123;
   obj.getName = function (){
       return obj.name;
            }
   alert(obj.name);
   alert(obj.getName());

3.3 Array 数组

Java中的数组,固定长度,存储同一类型数据,连续内存空间。 js中的数组可变长,存储数据不固定,存储空间不一定连续。

JavaScript 中的数组是一种特殊的对象,与普通对象类似以数字为属性名,但只能通过[] 来访问

   var arr = new Array();
    arr[0]  = 123;
    arr[1] = "hello";
    arr[5] = "aaaa";

通过数组字面量(array literal)创建数组

var arr2 = [1,2,3,"hello",,,"aa",];
alert(arr2.length);// IE中长度为8,火狐7

使用数组解构赋值来交换两个变量

//为了交换两个变量,通常使用第三个变量。
let x = 'Hello', y = 55;
//Longhand
const temp = x;
x = y;
y = temp;
//Shorthand
[x, y] = [y, x];

Array.length 并不总是等于数组中元素的个数

var a = ["dog", "cat", "hen"];
a[100] = "fox";
a.length; // 101

a.push(item);//在数组后追加元素


ES2015 引入了更加简洁的for...of循环,可以用它来遍历可迭代对象,例如数组:

for (const currentValue of a) {
  // Do something with currentValue
}
//不推荐使用 for...in 循环历数组,因为它遍历的是数组的索引。如果直接向 Array.prototype 添加了新的属性,使用这样的循环这些属性也同样会被遍历。
for (var i in a) {
  // 操作 a[i]
}

ECMAScript 5 增加了另一个遍历数组的方法,forEach()

["dog", "cat", "hen"].forEach(function(currentValue, index, array) {
  // 操作 currentValue 或者 array[index]
});

数组的属性 :length, 获得数组长度。

数组的API:

方法名称 描述
a.toString() 返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。
a.toLocaleString() 根据宿主环境的区域设置,返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。
a.concat(item1[, item2[, ...[, itemN]]]) 返回一个数组,这个数组包含原先 a 和 item1、item2、……、itemN 中的所有元素。 var arr1 = arr.concat(arr2);//将两个数组进行拼接操作,返回的是拼接好的数组
a.join(sep) 返回一个包含数组中所有元素的字符串,每个元素通过指定的 sep 分隔。 arr2.join("-");
a.pop() 删除并返回数组中的最后一个元素。
a.push(item1, ..., itemN) 将 item1、item2、……、itemN 追加至数组 a。
a.reverse() 数组逆序(会更改原数组 a)。
a.shift() 删除并返回数组中第一个元素。
a.slice(start, end) 返回子数组,以 a[start] 开头,以 a[end] 前一个元素结尾。
a.sort([cmpfn]) 依据可选的比较函数 cmpfn 进行排序,如果未指定比较函数,则按字符顺序比较进行简单排序,默认只能排序0-9 ; 如果排序数字复杂,则必须对sort方法进行修改: arr.sort(function(a,b){return a-b;});
a.splice(start, delcount[, item1[, ...[, itemN]]]) 从 start 开始,删除 delcount 个元素,然后插入所有的 item。splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
a.unshift(item1[, item2[, ...[, itemN]]]) 将 item 插入数组头部,返回数组新长度
const months = ['Jan', 'March', 'April', 'June'];
months.splice(1, 0, 'Feb');
// inserts at index 1
console.log(months);
// expected output: Array ["Jan", "Feb", "March", "April", "June"]

months.splice(4, 1, 'May');
// replaces 1 element at index 4
console.log(months);
// expected output: Array ["Jan", "Feb", "March", "April", "May"]

developer.mozilla.org/zh-CN/docs/…

see also

公众号:iOS逆向

JavaScript小技能:运算符:blog.csdn.net/z929118967/…

iOS小技能:链式编程在iOS开发中的应用blog.csdn.net/z929118967/…

iOS小技能:和uni-app、unity的融合方案

本文正在参加「金石计划」

引言

项目背景:iOS app内嵌H5和 AR功能,AR使用unity2020开发。

技术点:unity导出的目标工程Unity-iPhone.xcodeproj 与原生进行融合

目前的方案:uniapp 桥接IOS, ios 桥接unity, uniapp 采用插件的形式进行iOS原生代码开发, 然后集成为ipa文件。

I 实现思路

思路1: uniapp 桥接IOS,ios 桥接unity,然后集成为ipa文件。

Unity发布到iOS平台得到的是一个Xcode工程,uniapp 这边是个本地资源包。

思路2: 原生APP集成Unity导出的Xcode工程(需要将导出的工程封装为静态库)zhuanlan.zhihu.com/p/103759507www.jianshu.com/p/00dcac5b2…

思路3: Unity3D 嵌入iOS原生代码www.jianshu.com/p/82e34d9a1…

思路4: Unity导出的web工程部署到服务器,原生APP加载对应的H5地址。(视加AR不支持导出Web)

小程序的AR是图片识别,视加AR是空间识别; 小程序是在屏幕上,app可以做到3D的跟随图片。

如果有更好的思路和相关文章,欢迎留言交流。

II 桥接

2.1 IOS中调用Unity的方法

在这里插入图片描述

在这里插入图片描述

使用unityengine.dll提供的C接口UnitySendMessage 第1个char* 表示接受该消息的GameObject的name, 第2个表示该GameObject的脚本中接受消息的函数名, 第3个表示传递的参数。

    //参数1 场景中模型的名字
    //参数2 脚本名称方法
    //参数3 想unity传递一个char类型的数据
   UnitySendMessage("iOSSendMessageToUnity""ChangeCameraDirection""");

结合后unity里面的你先打开后是进入这个场景(SelectScene) 物体名SceneManager 脚本名SceneSelect 函数ChangeScene(string str) 传参数ZhenWuMiaoAR就是真武庙;或者传参数modelShow就是沙盘。

2.2 uniapp 桥接iOS

除了 uni-app 框架内置的跨端 API,各端自己的特色 API 也可通过条件编译 自由使用。因此uniapp可直接使用原生API进行发布通知,实现通信。 在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

III uni-app运行原理

kunnan.blog.csdn.net/article/det…

3.1 js引擎

浏览器的js引擎,就是jscore或v8的基础上新增了一批浏览器专用API,比如dom;

node.js引擎,则是v8基础上补充一些电脑专用API,比如本地io;

uni-app的App端和小程序端的js引擎,其实是在jscore上补充了一批手机端常用的JS API,比如扫码。

3.2 运行原理

uni-app 在非H5端运行时,从架构上分为逻辑层和视图层两个部分。

  1. 逻辑层负责执行业务逻辑,也就是运行js代码。

逻辑层是运行在一个独立的jscore里的,它不依赖于本机的webview,所以一方面它没有浏览器兼容问题,可以在Android4.4上跑es6代码,另一方面,它无法运行window、document、navigator、localstorage等浏览器专用的js API。

  1. 视图层负责页面渲染。

h5和小程序平台,以及app-vue,视图层是webview。

而app-nvue的视图层是基于weex改造的原生渲染视图。

3.3 编译器

vue2:uni-app编译器基于wepback实现

vue3:uni-app编译器基于Vite实现,编译速度更快

HBuilderX 3.3.0+ , uni-app在App/H5/小程序全平台支持Vue 3.0开发,且全平台支持Vite编译器。

Webpack1
   |
   |
Rollup 出现(推崇 ESM 规范,可以实现 tree shaking, 打包出来的代码更干净)
   |
   |
Webpack2(也实现了 tree shaking, 但是配置还是太繁琐了)
   |
   |
Parcel (号称 0 配置)
   |
   |
Webpack4(通过 mode 确定 development 和 production 模式,各个模式有自己的默认配置)
   |
   |
Webpack5(持久化缓存、module federation)
   |
   |
Esbuild(采用 go 语言开发,比 Webpack 更快)
   |
   |
Vite(推崇 ESM 规范,开发模式采用 nobundle,更好的开发体验)

组件模块化成为前端开发的主流模式,以 React 和 Webpack 为例:将一个应用涉及到的所有的功能拆分为一个个组件,一个组件对应一个源文件,然后通过 Webpack 将这些源文件打包。在开发过程中,可通过 Webpack 开启一个 local server,实时查看代码的运行效果。

IV 常见问题

4.1 支持 ARCore 的设备

developers.google.cn/ar/devices#…

4.2 uni-app和原生App混合开发问题

首先务必确认uni-app和原生代码,谁是主谁是从的问题。

uniapp.dcloud.io/hybrid.html

  1. 如果你的应用是uni-app开发的,需要扩展一些原生能力,那么首先去插件市场看看有没有现成的插件,如果没有,就自己开发原生插件。
  2. 如果你的App是原生开发的,部分功能栏目想通过uni-app实现,有2种方式: a. 在原生App里集成uni小程序sdk,然后即可运行用uni-app框架开发的小程序前端项目(小程序应用资源包wgt)。nativesupport.dcloud.net.cn/README b. 如果不想集成原生sdk,那就把uni-app代码发布成H5方式,在原生App里通过webview打开。

see also

公众号:iOS逆向

iOS小技能:Makefile的使用(Makefile的规则、部署脚本、config管理ssh连接)

本文正在参加「金石计划」

前言

make是一个命令工具,是一个解释makefile中指令的命令工具。其本质是文件依赖,Makefile文件制定编译和链接所涉及的文件、框架、库等信息,将整个过程自动化。 一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。

例子: 使用别名配置IP

I 预备知识

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。那么先来了解下什么是编译?

  1. 编译: 把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译。
  2. 链接: 把大量的Object File合成执行文件

源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。

每个源文件都应该对应于一个中间目标文件(OBJ文件)

1.1 编译

在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。

只有所有的语法正确,编译器才可以编译出中间目标文件。至于函数与变量的声明的正确,就是指头文件的所在位置。

1.2 链接

主要是链接函数和全局变量,所以可以使用这些中间目标文件来链接我们的应用程序。

在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error)。

II Makefile的规则

    target... : prerequisites ...
          command
          ...
          ...
         -------------------------------------------------------------------------------
  • 例子

THEOS_DEVICE_IP=iphone#5C9 #配置IP的host 别名
TARGET = iphone:latest:8.0
ARCHS = armv7 arm64
THEOS=/opt/theos
THEOS_MAKE_PATH=$(THEOS)/makefiles
include $(THEOS)/makefiles/common.mk

TWEAK_NAME =
Taoke_FILES = Taoke.xm 

Taoke_FRAMEWORKS = UIKit, Foundation, Security, IOKit, JavaScriptCore

include $(THEOS_MAKE_PATH)/tweak.mk

after-install::
install.exec "echo '' > /var/log/syslog"
install.exec "killall -9 WeChat"
    install.exec "killall -9 Moon"

  • target也就是一个目标文件,可以是Object File ,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

  • prerequisites就是要生成那个target所需要的文件或是目标

  • command也就是make需要执行的Shell命令

  • make clean清除所有的目标文件,以便重编译。

一个简单的例子

ARCHS = armv7 arm64
TARGET = iphone:latest:7.0

include theos/makefiles/common.mk

TWEAK_NAME = iOSREProject
iOSREProject_FILES = Tweak.mk
iOSREProject_PRIVATE_FRAMEWORK = BaseBoard
incluce $(THEOS_MAKE_PATH)/tweak.mk

after-install:: install.exec "killall -9 SpringBoard"

注意:BaseBoard这个private framework只存在于8.0以上的版本的SDK中,在IOS7 里并没有,所以这个tweak在IOS 7 中因为不会找到framework 而无法进行工作。这种情况可以通过弱引用( “makefile weak linking”)或dlopen()、dlsym() 和 dlclose()系列函数动态调用private framework来解决。

  • 链接Match-o对象(Match-O object)

Theos 采用GUN Linker来链接Mach-O对象,包括.dylib、 .a 和 .o 。 例如,要链接libsqlite3.0.dylib 、libz.dylib 和dylib1.0像下面这样写就OK

iOSREProject_LDFLAGS = -lz -lsqlite3.0 -dylib1.o 

  • 开启arc: 不用自己手动进行内存管理
$(TWEAK_NAME)_CFLAGS += -fobjc-arc

  • 单独为单个文件指定mrc
ZKSwizzle/ZKSwizzle.m_CFLAGS = -fno-objc-arc

III 集成第三方库

3.1 编译参数指定一些编译链接参数、搜索路径

TweakDemo_CFLAGS = -fobjc-arc -Wno-int-to-void-pointer-cast -Wno-int-to-pointer-cast -I./BookLib/include -F./BookFramework # 指定头文件位置
ZKSwizzle/ZKSwizzle.m_CFLAGS = -fno-objc-arc # 设置单个文件的MRC
TweakDemo_LDFLAGS = -L./BookLib -F./BookFramework # 链接
TweakDemo_LIBRARIES = BookLib #.a
TweakDemo_FRAMEWORKS = BookFramework # framke

  • 导入头文件直接调用
// 这个第三方库或者头文件也可以放到 /opt/theos/对应的目录
#import <BookLib/BookLib.h> //.a
#import <BookFramework/BookFramework.h>// framework

3.2 使用 ASIHTTPRequest 的例子

 20 RESOURCE_DIR = Resources
#编译 ----------------------------ASIHTTPRequest--------------------
 32 service_FILES += ASIHTTPRequest/ASIAuthenticationDialog.m
 33 service_FILES += ASIHTTPRequest/ASIDownloadCache.m
 34 service_FILES += ASIHTTPRequest/ASIFormDataRequest.m
 35 service_FILES += ASIHTTPRequest/ASIHTTPRequest.m
 36 service_FILES += ASIHTTPRequest/ASIInputStream.m
 37 service_FILES += ASIHTTPRequest/ASINetworkQueue.m
 38 service_FILES += ASIHTTPRequest/Reachability.m
 39 
 40 #编译 ----------------------------Encoder---------------------------
 41 service_FILES += Encoder/CrypHelper.m
 42 service_FILES += Encoder/GTMBase64.m
 43 service_FILES += Encoder/NSData+Base64.m
 44 service_FILES += Encoder/NSString+MD5Addition.m
 45 service_FILES += Encoder/UIDevice+IdentifierAddition.m
 46 
 47 #编译 ----------------------------TBXML-----------------------------
 48 service_FILES += TBXML/NSDataAdditions.m
 49 service_FILES += TBXML/TBXML.m
 50 
 51 #编译 ----------------------------Regex-----------------------------
 52 service_FILES += Regex/RegexKitLite.m
 53 
 54 #编译 ----------------------------Constant--------------------------
 55 service_FILES += Constant/Constant.m
 56 service_FILES += Constant/UserDefaults.m
 57 

#连接单元
 87 #
 88 #连接 ----------------------------framework--------------------------
 89 service_FRAMEWORKS = Foundation UIKit QuartzCore Foundation CoreFoundation CoreGraphics  MobileCoreServices
 90 service_FRAMEWORKS += SystemConfiguration  CFNetwork CoreTelephony CoreLocation MapKit
 91 
 92 
 93 #连接 ----------------------------dylib------------------------------
 94 service_LDFLAGS = -lxml2 -lz -licucore -lgcc -lgcc_eh -lstdc++ 
 95 service_LDFLAGS += -L./BaiduMap/inc -R./BaiduMap/inc/ -lbaidumapapi
 96 
 97 #编译器 ----------------------------------------------------------
 98 #service_CFLAGS += -std=c99 
 99 #service_CFLAGS += -all_load
100 
101 #资源文件 ----------------------------------------------------------
102 internal-package::
103    ifneq ($(wildcard $(RESOURCE_DIR)/*.png), )
104     mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(TWEAK_NAME)
105     cp $(RESOURCE_DIR)/*.png $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(TWEAK_NAME)/
106    endif
107    ifneq ($(wildcard $(RESOURCE_DIR)/*.lproj), )
108     mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(TWEAK_NAME)
109     rsync -avC $(RESOURCE_DIR)/*.lproj $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(TWEAK_NAME)
110    endif
111    ifneq ($(wildcard $(RESOURCE_DIR)/$(TWEAK_NAME).plist), )
112     mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(TWEAK_NAME)
113     cp $(RESOURCE_DIR)/$(TWEAK_NAME).plist $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/$(TWEAK_NAME)
114    endif
115 

20行存放资源文件的本地目录名称:可以自定义 主要是在生成包拷贝时使用。

  • CFLAGS 指定头文件(.h文件)的路径
CFLAGS=-I/usr/include -I/path/include
 TWEAK_NAME_CFLAGS += -I./include

有时候LDFLAGS指定-L虽然能让链接器找到库进行链接,但是运行时链接器却找不到这个库,如果要让软件运行时库文件的路径也得到扩展,那么我们需要增加这两个库给"-Wl,R":

LDFLAGS = -L/var/xxx/lib -L/opt/mysql/lib -Wl,R/var/xxx/lib -Wl,R/opt/mysql/lib

IV deploy 部署脚本

#!/bin/sh
cd `dirname $0` 
make clean
make package install

scp ./taoke.lua iphone:/private/var/mobile/Media/TouchSprite/lua/#配置IP的host 别名

rm -f ./debs/*
exit 0

4.1 layout的应用

应用场景: 新增图片、文件等资源文件,或者二进制可执行文件;然后在代码中调用使用它们。这个时候可以使用layout目录指定文件到特定的路径。

  • 获取文件的代码如下
NSString* resourcePath = @"/Library/Application Support/TweakDemo/";
NSDictionary* resourceDict = [[NSDictionary alloc] initWithContentsOfFile:[resourcePath stringByAppendingPathComponent:@"resource.plist"]];
NSLog(@"resourceDict:%@", resourceDict);

4.2 使用ssh config配置文件来管理ssh连接

config 文件:设置IP

blog.csdn.net/z929118967/…

# Private 192.168.2
Host iphone
HostName  192.168.2.131
User root 
IdentityFile ~/.ssh/id_rsa_Theos125

Host gitlab.cn
HostName  gitlab.cn
User git
IdentityFile ~/.ssh/id_rsa_q



# Private github
Host github.com
HostName  github.com
User git
IdentityFile ~/.ssh/id_rsa

一个SSH的认证文件可以重复使用到不同的host

devzkndeMacBook-Pro:.ssh devzkn$  ssh-copy-id -i id_rsa_Theos125.pub root@192.168.2.150

V 总结

# 系统配置部分    export THEOS=/opt/theos/
#   RESOURCE_DIR = Resources  存放资源文件的本地目录名称:可以自定义 主要是在生成包拷贝时使用  #资源文件 ----------------------------------------------------------102 internal-package::
# 调试设备的IP地址
THEOS_DEVICE_IP=iphone150    #5C9
TARGET = iphone:latest:8.0
ARCHS = armv7 arm64
THEOS=/opt/theos
THEOS_MAKE_PATH=$(THEOS)/makefiles
# 引入常用的一般模板
include $(THEOS)/makefiles/common.mk
# 工程名称
TWEAK_NAME =
# :常用的一个宏设置是否显示NSLog 区分是debug还是release 也可以自己尝试设置其他宏变量
DEBUG = 0

# 编译所需要的的源文件
Taoke_FILES = Taoke.xm 
# sdk中的framework
Taoke_FRAMEWORKS = UIKit, Foundation
#CXXFLAGS 表示用于 C++ 编译器的选项。
ADDITIONAL_CCFLAGS  = -Qunused-arguments
# gcc等编译器会用到的一些优化参数 也可以在里面指定库文件的位置;例如需要的一些系统静态库和第三方静态库 例如,要链接libsqlite3.0.dylib 、libz.dylib 和dylib1.0  TWEAK_NAME_LDFLAGS = -lz -lsqlite3.0 -dylib1.o   或者使用TWEAK_NAME_OBJ_FILES += libcurl.a  指定库文件的位置的用法:LDFLAGS=-L/usr/lib -L/path/to/your/lib -WL,R 运行时库文件的路径也得到扩展-Wl,R/var/xxx/lib
ADDITIONAL_LDFLAGS  = -Wl,-segalign,4000
# CFLAGS 表示用于 C 编译器的选项, 际上涵盖了编译和汇编两个步骤 编译器参数设置 #service_CFLAGS += -std=c99  #service_CFLAGS += -all_load   RedRobert_CFLAGS += -Wno-error 忽略的编译错误  TWEAK_NAME_CFLAGS += -I./include
ADDITIONAL_CFLAGS = -Werror -Wobjc-method-access
# 引入tweak模板
include $(THEOS_MAKE_PATH)/tweak.mk

after-install::
    install.exec "echo '' > /var/log/syslog"
    install.exec "killall -9 WeChat"
    install.exec "killall -9 Moon"

see also

iOS 逆向: 批量部署Tweak插件到iOS设备的方案(how to host cydia repo?)

kunnan.blog.csdn.net/article/det…

iOS小技能:1、yalu102 激活了之后,无法连接ssh的解决方案 2、Reveal的基本使用3、Passionfruit 的实现原理

本文正在参加「金石计划」

前言

  1. 无法连接ssh的解决方案
  2. Reveal的安装、配置及使用
  3. Passionfruit 的实现原理
  4. Mac上Nodejs环境搭建

I 使用yalu102 激活了之后,无法连接ssh的解决方案

yalu102此次越狱工具默认安装了 SSH.采用了 Dropbear 取代 Openssh。#/usr/local/bin/dropbear

-部署安装使用yalu102时,修改dropbear.plist的信息: ProgramArguments的127.0.0.1:22 直接改为22。

如果部署完成,直接修改沙盒的信息的话,记得重启设备。

1.1 获取直接修改对应的配置信息

  • ps -e |grep yalu*
iPhone:~ root# ps -e |grep yalu*
 1174 ??         0:00.80 /var/containers/Bundle/Application/B831448D-BCD0-4F29-BDA6-9FC03903D30C/yalu102.app/yalu102
  • plutil plist 内容
iPhone:/var/containers/Bundle/Application/B831448D-BCD0-4F29-BDA6-9FC03903D30C/yalu102.app root# plutil dropbear.plist
{
   KeepAlive = 1;
   Label = ShaiHulud;
   Program = "/usr/local/bin/dropbear";
   ProgramArguments =     (
       "/usr/local/bin/dropbear",
       "-F",
       "-R",
       "-p",
       22
   );
   RunAtLoad = 1;
}
  • dropbear 参数
 iPhone:/var/containers/Bundle/Application/B831448D-BCD0-4F29-BDA6-9FC03903D30C/yalu102.app root# ps -e |grep dropbear
  228 ??         0:00.05 /usr/local/bin/dropbear -F -R -p 22

1.2 利用wget 安装scp( 解决:sh: scp: command not found)

#两台服务器都要安装scp才能传文件
#wget + 空格 + 要下载文件的url路径
# cydia里面安装wget
#  安装scp,默认安装在当前目录
wget mila432.com/scp
ldid -S scp
# chmod +x scp
chmod 777 scp
mv scp /usr/bin/scp
# 或者使用curl: curl -O mila432.com/scp ./scp
  • dyld: Library not loaded: /usr/lib/libssl.0.9.8.dylib 重新安装openssl
  • 浏览器下载scp
 find . -name "scp" 

II Reveal的安装、配置及使用

目前自己经常使用的是AFlexLoader

2.1. Reveal Loader安装


首先我们打款越狱设备的Cydia,然后在搜索中输入Reveal Loader,并且进行安装即可,下方是安装后的效果。这一步比较简单,安装后重启SpringBoard即可。

iPhone:~ root# cd /Library/RHRevealLoader
-sh: cd: /Library/RHRevealLoader: No such file or directory

进行第二步骤,导入libReveal.dylib

2.2.导入libReveal.dylib


Mac上的Reveal自带了两个库,一个是libReveal.dylib,一个是Reveal.framework。 在未越狱的设备上使用的是后者,本文使用的是前者。

这两个文件位于Reveal中的iOS Library中。Reveal菜单->Help->Show Reveal Library in Finder ->iOS Library。通过上述目录就可以找到我们需要的文件。

 /Users/devzkn/Downloads/kevin-software/ios-Reverse_Engineering/Reveal/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib

mkdir

devzkndeMacBook-Pro:python-client devzkn$ ssh iphone
iPhone:~ root# mkdir /Library/RHRevealLoader
iPhone:~ root# cd /Library/RHRevealLoader
iPhone:/Library/RHRevealLoader root

scp

devzkndeMacBook-Pro:python-client devzkn$ scp  /Users/devzkn/Downloads/kevin-software/ios-Reverse_Engineering/Reveal/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib iphone:/Library/RHRevealLoader/

iPhone:~ root# ls -l /Library/RHRevealLoader/libReveal.dylib
-rwxr-xr-x 1 root admin 3850232 Oct 19 16:17 /Library/RHRevealLoader/libReveal.dylib

2.3、Reveal的使用


  • 选择Reveal的App

在设置中找到Reveal的配置项,在该配置项中我们可以去选择要Reveal的App, 当然对于越狱手机,手机上安装的所有App都可以Reveal。当然也包括从AppStore下载的,也包括iOS系统自带的

  • 查看app的UI层级

Mac上Reveal查看设备上App的UI层级时是不需要使用USB进行连接的,但要保证你的iOS设备与你的Mac在同一个局域网内

  • 注意事项

记得打开对应的app,保证你查看的app 处于运行状态

III Passionfruit 的实现原理

  • 我在使用Passionfruit 的时候,安装步骤碰到的问题是fatal error: 'frida-core.h' file not found,具体的请看Q&A。

  • 安全审计的工具 我觉得iNalyzer 已经过时了,推荐这款Passionfruit;

  • Passionfruit 通过frida注入代码到目标应用实现了个“动态分析iOS应用”的图形界面。

3.1 实现原理

Passionfruit 通过 frida 注入代码到目标应用实现功能,再通过 node.js 服务端消息代理与浏览器通信,用户通过访问网页即可对 App 实现常规的检测任务。 在这里插入图片描述

3.2 安装

3.2.1 安装前准备

  • brew install libimobiledevice
devzkndeMacBook-Pro:passionfruit devzkn$ brew install libimobiledevice

  • brew install yarn
devzkndeMacBook-Pro:passionfruit devzkn$  brew install yarn

  • install npm
brew install npm

3.2.2 运行 npm install 根据 package.json 文件安装依赖。

  • save frida then $ npm install
devzkndeMacBook-Pro:passionfruit devzkn$ npm install
> Passionfruit@0.0.3 postinstall /Users/devzkn/code/demo/passionfruit
> cd gui && (yarn || npm install)
yarn install v1.3.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
✨  Done in 231.08s.
up to date in 235.627s

  • npm run build
devzkndeMacBook-Pro:passionfruit devzkn$ npm run build

> Passionfruit@0.0.3 build /Users/devzkn/code/demo/passionfruit
> frida-compile agent -o _agent.js && cd gui && (yarn run build || npm run build)

yarn run v1.3.2

  • npm start
devzkndeMacBook-Pro:passionfruit devzkn$ npm start

> Passionfruit@0.0.3 start /Users/devzkn/code/demo/passionfruit
> cross-env NODE_ENV=production node .

listening on http://localhost:31337

3.3 Q&A

  • prebuild-install http 404 https://github.com/frida/frida/releases/download/10.6.13/frida-v10.6.13-node-v59-darwin-x64.tar.gz
devzkndeMacBook-Pro:passionfruit devzkn$ npm install --save frida@latest

> frida@10.6.28 install /Users/devzkn/code/demo/passionfruit/node_modules/frida
> prebuild-install || node-gyp rebuild

prebuild-install info begin Prebuild-install version 2.3.0
prebuild-install info looking for local prebuild @ prebuilds/frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info looking for cached prebuild @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install http request GET https://github.com/frida/frida/releases/download/10.6.28/frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install http 200 https://github.com/frida/frida/releases/download/10.6.28/frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info downloading to @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz.47369-6d6afc3ef5581.tmp
prebuild-install info renaming to @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info unpacking @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info unpack resolved to /Users/devzkn/code/demo/passionfruit/node_modules/frida/build/Release/frida_binding.node
prebuild-install info unpack required /Users/devzkn/code/demo/passionfruit/node_modules/frida/build/Release/frida_binding.node successfully
prebuild-install info install Successfully installed prebuilt binary!
+ frida@10.6.28
added 32 packages in 18.574s

  • Unable to launch iOS app: timeout

启动应用程序失败之后,装置就重启了。这个问题 有点类似Failed to spawn: unable to launch iOS app: timeout

临时解决方式: 手动启动app ,还是可以正常分析的

更多具体的分析请看这里: frida Failed to spawn 解决方案

IV Mac上Nodejs环境搭建

  1. 安装包安装:nodejs下载网址 http://nodejs.cn/download/
  2. 二进制安装:
brew install nodejs

brew install npm

see also

修改汇编的方式: 选中行,选择菜单栏的Modify > Assemble Instruction...,将jne修改成je,然后点击Assemble and Go Next。

公众号:iOS逆向

JavaScript预备知识

本文正在参加「金石计划」

前言

一个有具体功能的完整网页,一般由3部分组成:

  1. HTML(内容和结构): HyperText Markup Language,超文本标记语言。用来结构化网页内容并赋予内容含义,例如定义段落、标题和数据表,或在页面中嵌入图片和视频。

  2. css(样式): Cascading Style Sheets层叠样式表是一种样式规则语言,允许我们精确地设计HTML的样式,例如设置背景颜色和字体,在多个列中布局内容。

  3. JavaScript(交互效果) :JavaScript 是一种符合ECMAScript规范的脚本编程语言,可以用来创建动态更新的内容,控制多媒体,制作图像动画。

脚本语言是为了缩短传统编程语言从编写-编译-运行这个过程而开发的一种简单类型语言。

I 预备知识

1.1 术语:解释(interpret)和 编译 (compile)

编译原理 : https://blog.csdn.net/z929118967/article/details/123778003 在解释型语言中,代码自上而下运行,且实时返回运行结果。

js代码由浏览器执行前,不需要将其转化为其他形式,代码将直接以文本格式(text form)被接收和处理。

编译型语言需要先将代码转化(编译)成另一种形式才能运行。

C++,Objective C都是编译语言,必须先通过编译器生成机器码,然后才能由计算机运行。

Objective-C与swift都采用Clang作为编译器前端,编译器前端主要进行语法分析,语义分析,生成中间代码,在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。

在这里插入图片描述 编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化,根据不同的系统架构生成不同的机器码。

在这里插入图片描述

1.2 语言特点

  1. JavaScript 是轻量级解释型语言。

浏览器接受到 JavaScript 代码,并以代码自身的文本格式运行它。

技术上,几乎所有 JavaScript 转换器都运用了一种叫做即时编译(just-in-time compiling)的技术;当 JavaScript 源代码被执行时,它会被编译成二进制的格式,使代码运行速度更快。尽管如此,JavaScript 仍然是一门解释型语言,因为编译过程发生在代码运行中,而非之前。

JavaScript能被浏览器进行解释,是一种解释性语言。受浏览器的影响,在不同的浏览器可能表现的效果不一样,存在浏览器差异。

  1. 与大多数编程语言不同,JavaScript 没有输入或输出的概念。它是一个在宿主环境(host environment)下运行的脚本语言,任何与外界沟通的机制都是由宿主环境提供的。

浏览器是最常见的宿主环境,但Node.js 的服务器端环境中也包含 JavaScript 解释器,所以JavaScript 也可用作服务器端语言。

  1. JavaScript的语法来源于 Java 和 C,所以这两种语言的许多语法特性同样适用于 JavaScript。
  2. JavaScript 是一种“动态类型语言”(弱类型数据语言),这意味着不需要指定变量将包含什么数据类型,如果你声明一个变量并给它一个带引号的值,浏览器就会知道它是一个字符串:
let myString = 'Hello';
//提供了一个函数typeof用于检测数据属于哪个类型
//1.typeof 变量名
//2.typeof(变量名)
typeof myString;
  1. JavaScript 通过原型链而不是类来支持面向对象编程 ,JavaScript 同样支持函数式编程。

函数也可以被保存在变量中,并且像其他对象一样被传递。


document.querySelector('html').onclick = function() {
    alert('别戳我,我怕疼。');
}

document.querySelector('html').addEventListener('click', () => {
  alert('别戳我,我怕疼。');
});

//Longhand
function add(num1, num2) {
   return num1 + num2;
}
//Shorthand 箭头函数
const add = (num1, num2) => num1 + num2;
//`匿名函数`: 因为它没有名字,匿名函数还有另一种我们称之为`箭头函数`的写法,箭头函数使用` () => `代替 `function ()`:

函数式编程思想:把操作尽量写成一系列嵌套的函数或者方法调用。

函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)

iOS小技能:链式编程在iOS开发中的应用https://blog.csdn.net/z929118967/article/details/75219317

1.3 JavaScript的使用场所

使用场所:任何的HTML页面、所有的动态页面,通过 DOM API动态修改 HTML 和 CSS 来更新用户界面(user interface)。

没有动态更新内容的网页叫做“静态”页面,所显示的内容不会改变。

  1. 前端验证,通过验证提高数据的完整性以及安全性。
<input type="submit" value="确定" class="guessSubmit">

const guessSubmit = document.querySelector('.guessSubmit');
guessSubmit.addEventListener('click', checkGuess);
function checkGuess() {
//...
}
  1. 操纵html元素,响应用户的各种操作,提高用户体验性。
<input type="text" id="guessField" class="guessField">

const guessField = document.querySelector('.guessField');
  guessField.focus();//让光标在页面加载完毕时自动放置于 <input> 输入框内,这意味着玩家可以马上开始第一次猜测,而无需点击输入框。提高了可用性,为使用户能投入游戏提供一个良好的视觉线。
  1. ajax核心技术之一

ajax: 在浏览器中运行的js脚本,通过http请求异步地访问服务器组件,服务器组件返回xml文件或者json格式的数据,js接收后通过解析xml或json来局部刷新页面,提高用户体验。

  1. 获取浏览器的一些相关信息

1.4 脚本调用策略

HTML 元素是按其在页面中出现的次序调用的,如果用 JavaScript 来管理页面上的元素(更精确的说法是使用 文档对象模型 DOM),若 JavaScript 加载于欲操作的 HTML 元素之前,则代码将出错。

  1. 内部 JavaScript的解决方案
//监听浏览器的 "DOMContentLoaded" 事件,即 HTML 文档体加载、解释完毕事件
//可能会带来显著的性能损耗
document.addEventListener("DOMContentLoaded", function() {
//  . . .
});
  1. 外部JavaScript的解决方案(推荐)

async 属性可以解决调用顺序问题,它告知浏览器在遇到<script>元素时不要中断后续 HTML 内容的加载。

不依赖于本页面的其它任何脚本时,async 是最理想的选择。

<script src="script.js" async></script>

defer 属性,脚本将按照在页面中出现的顺序加载和运行:

<!--添加 defer 属性的脚本将按照在页面中出现的顺序加载-->
<script defer src="js/vendor/jquery.js"></script>

<script defer src="js/script2.js"></script>

<script defer src="js/script3.js"></script>

脚本调用策略小结:

  • 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 async。
  • 如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。

II 应用程序接口(Application Programming Interfaces)

在这里插入图片描述

  1. 第三方 API 并没有默认嵌入浏览器中,一般要从网上取得它们的代码和信息,比如地图 API 可以在网站嵌入定制的地图。

  2. 浏览器 API 内建于 web 浏览器中,它们可以将数据从周边计算机环境中筛选出来,还可以做实用的复杂工作,比如DOM API。

  • 文档对象模型 API(Document Object Model Application Programming Interfaces ) 能通过创建、移除和修改 HTML,为页面动态应用新样式等手段来操作 HTML 和 CSS。
  • 地_理位置 API(Geolocation API) 获取地_理信息。
  • 画布(Canvas) 和 WebGL API 可以创建生动的 2D 和 3D 图像。
  • HTMLMediaElementWebRTC 等影音类 API 。

2.1 BOM(Browser Object Model,浏览器对象模型)

整个浏览器窗口是一个顶层window对象

  1. 函数 alert() 警告框 prompt() 对话框 confirm() 确认框 window.open("URL"); setTimeout();超时之后调用目标函数 clearTimeout();超时之后清除目标函数 focus() 获得焦点 setInterval(,) (以毫秒计)调用执行函数/表达式 setInterval(code,millisec[,"lang"]) clearInterval() 取消对 code 的周期性执行,由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
//setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。
//setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
window.setInterval(function() {}, 18000)//18S

前端小技能:利用action-type按钮事件实现批量删除 https://blog.csdn.net/z929118967/article/details/123222483

  1. 浏览器对象 Navigator对象
  1. 属性: appName、 appVersion 、 History 历史记录对象
  2. 函数:go(url);
  1. 地址栏对象 Location

属性:href 通过改变地址栏访问目标地址

  1. 文档对象 Document

2.2 DOM(Document Object Model,文档对象模型)

由W3C定义的一组规范一组API 用来操作HTML对象

  1. 直接获得标签对象

1) document.getElementById("id属性值") 通过ID来获得对应的标签对象

    <iframe id="ifr" width=400 height=500></iframe>
        document.getElementById("ifr").src = url[j];

2) document.getElementsByTagName("标签名称") 根据标签名称来获得一组标签,返回数组对象 3) document.getElementsByName("name属性值");根据name属性值来获得一组标签,返回数组对象

  1. 间接获得标签对象

1) 父标签.childNodes 获得当前标签的所有孩子节点,返回数组对象 2) 父标签.firstChild 获得第一个孩子节点 3) 父标签.lastChild 获得最后一个孩子节点 4) 标签.nextSibling 获得下一个兄弟节点 5) 标签.previousSibling 获得前一个兄弟节点

  1. 创建节点对象

1) document.createElement("标签名称") 创建一个对应的标签对象 2) document.createTextNode("文本值"); 创建一个文本节点对象

  1. 操作标签

1) 父标签.appendChild(子节点); 将一个标签追加到父标签当中 2) 父标签.removeChild(子节点); 删除子节点 3) 父标签.insertBefore(newElement,targetElement); 在目标元素之前插入一个新元素 4) 父标签.replaceChild(newElement,oldElement); 用新元素替换掉旧的元素

  1. 其他操作

1)标签.style.样式属性 = 属性值; h1.style.backgroundColor = "red"; 2) 标签.innerHTML 获得或者设置元素的标签体 3) 文本节点.nodeValue 获得文本节点值 4) 标签.parentNode 获得父节点 5) this 表示当前标签对象

see also

公众号:iOS逆向

demo: https://codepen.io/zhangkn/pen/BaLyerq?editors=1111

JavaScript小技能: Ajax

本文正在参加「金石计划」

I 术语

1.1 术语:箭头函数

匿名函数: 因为它没有名字

//Longhand
function add(num1, num2) {
   return num1 + num2;
}

匿名函数还有另一种我们称之为箭头函数的写法,箭头函数使用() =>代替 function ()

//Shorthand 箭头函数
const add = (num1, num2) => num1 + num2;

1.2 术语:回调函数和Promise链

  1. 回调函数是一个被传递到另一个函数中的会在适当的时候被调用的函数,如事件处理程序就是一种特殊类型的回调函数。

由于嵌套回调导致处理错误变得非常困难,代码也更难阅读和调试,所以JavaScript 中的异步编程是基于 Promise实现。

  1. Promise是一个由异步函数返回的可以向我们指示当前操作所处的状态的对象。在基于 Promise 的 API 中,异步函数会启动操作并返回 Promise 对象。然后你可以将处理函数附加到 Promise 对象上,当操作完成时(成功或失败),这些处理函数将被执行。

在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象可以让我们操作最终完成时对其进行处理(无论成功还是失败)。

例子:fetch() 一个基于 Promise 的、用于替代 XMLHttpRequest 的方法。

const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');

console.log(fetchPromise);
//将一个处理函数传递给 Promise 的 then() 方法。
fetchPromise.then( response => {
  console.log(`已收到响应:${response.status}`);
});

console.log("已发送请求……");

"pending" 状态意味着操作仍在进行中。

  1. Promise 链:链式使用 Promise
const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');

fetchPromise
  .then( response => {
    return response.json();//json() 也是异步的,response.json() 返回的是 Promise对象
  })
  .then( json => {
    console.log(json[0].name);
  });

Promise 对象提供了一个 catch() 方法来支持错误处理。当异步操作成功时,传递给 then() 的处理函数被调用,而当异步操作失败时,传递给 catch() 的处理函数被调用。

如果将 catch() 添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。于是,我们就可以将一个操作实现为几个连续的异步函数调用,并在一个地方处理所有错误。

const fetchPromise = fetch('bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');

fetchPromise
  .then( response => {
    if (!response.ok) {
      throw new Error(`HTTP 请求错误:${response.status}`);
    }
    return response.json();
  })
  .then( json => {
    console.log(json[0].name);
  })
  .catch( error => {
    console.error(`无法获取产品列表:${error}`);
  });

1.3 JSON(JavaScript Object Notation)

是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。

  1. JSON用于在网站上表示和传输数据
////通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。
//创建一个新的 XMLHttpRequest 并监听它的 loadend 事件

request.open('GET', requestURL);
request.responseType = 'text'; // now we're getting a string!
request.send();
//以事件处理程序属性形式关联事件处理器
request.onload = function() {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
}
//通过DOM Level 2 Events 函数 addEventListener()关联事件处理器
  xhr.addEventListener('loadend', () => {
    log.textContent = `${log.textContent}完成!状态码:${xhr.status}`;
  });


  1. JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
//JSON 格式创建一个对象
   var oo = {
        name:"hello",
        age:123,
        getName:function(){
       return oo.name;
            }
       
    }

  1. JSON 和 JS 对象互转: 利用浏览器内建的 JSON进行转换数据
//parse(): 以文本字符串形式接受 JSON 对象作为参数,并返回相应的对象。
var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //结果是 {a: 'Hello', b: 'World'}
//stringify(): 接收一个对象作为参数,返回一个对应的 JSON 字符串。
var json = JSON.stringify({a: 'Hello', b: 'World'}); //结果是 '{"a": "Hello", "b": "World"}'

  1. 深拷贝多级对象
//深拷贝多级对象
const cloneObj = JSON.parse(JSON.stringify(obj));//JSON.stringify 对象的时候,包含 function, undefined or NaN 值的属性会从对象中移除。
//Shorthand for single level object
let obj = {x: 20, y: 'hello'};
const cloneObj = {...obj};
//————————————————
//版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https://blog.csdn.net/z929118967/article/details/126142071

II Ajax(Asynchronous JavaScript and XML)

Ajax是一种在2005年由Google推广开来的编程模式,可以创建更好、更快以及更友好的web应用程序。

在这里插入图片描述Asynchronous JavaScript and XML基于javascript和HTTP请求,以异步地方式实现局部HTML的刷新。

2.1 从服务器获取数据

  1. XMLHttpRequest (通常缩写为 XHR)现在是一个相当古老的技术 - 它是在 20 世纪 90 年代后期由微软发明的,并且已经在相当长的时间内跨浏览器进行了标准化。

因为微软 Edge 浏览器的受宠, IE 已经没人管了

let request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'text';

request.onload = function() {
  poemDisplay.textContent = request.response;
};

request.send();

  1. Fetch API 基本上是 XHR 的一个现代替代品——它是最近在浏览器中引入的,它使异步 HTTP 请求在 JavaScript 中更容易实现
fetch(url).then(function(response) {
  response.text().then(function(text) {
    poemDisplay.textContent = text;
  });
});

2.2 缓存问题

如果是get方式请求,浏览器会将请求的数据缓存起来,如果下次访问的地址没变,浏览器就不会发送真正的请求,会将缓存的数据显示给用户。

解决方式:

  1. 可以在地址后面加上一个随机数。
  2. 使用post方式发送请求。
❌
❌